Das Problem
Nehmen wir an, ich habe eine Klasse namens, DataSource
die eine ReadData
Methode (und vielleicht auch andere, aber lassen Sie uns die Dinge einfach halten) zum Lesen von Daten aus einer .mdb
Datei bereitstellt :
var source = new DataSource("myFile.mdb");
var data = source.ReadData();
Ein paar Jahre später entscheide ich mich, dass ich .xml
Dateien zusätzlich zu .mdb
Dateien als Datenquellen unterstützen möchte . Die Implementierung zum "Lesen von Daten" ist für .xml
und .mdb
Dateien ganz anders ; Wenn ich das System von Grund auf neu entwerfen würde, würde ich es folgendermaßen definieren:
abstract class DataSource {
abstract Data ReadData();
static DataSource OpenDataSource(string fileName) {
// return MdbDataSource or XmlDataSource, as appropriate
}
}
class MdbDataSource : DataSource {
override Data ReadData() { /* implementation 1 */ }
}
class XmlDataSource : DataSource {
override Data ReadData() { /* implementation 2 */ }
}
Großartig, eine perfekte Implementierung des Factory-Methodenmusters. Leider DataSource
befindet sich in einer Bibliothek ein Refactoring und der Code würde so alle vorhandenen Aufrufe von brechen
var source = new DataSource("myFile.mdb");
in den verschiedenen Clients mit der Bibliothek. Weh mir, warum habe ich überhaupt keine Fabrikmethode angewendet?
Lösungen
Das sind die Lösungen, die ich finden könnte:
Lassen Sie den DataSource-Konstruktor einen Subtyp (
MdbDataSource
oderXmlDataSource
) zurückgeben. Das würde alle meine Probleme lösen. Leider unterstützt C # das nicht.Verwenden Sie unterschiedliche Namen:
abstract class DataSourceBase { ... } // corresponds to DataSource in the example above class DataSource : DataSourceBase { // corresponds to MdbDataSource in the example above [Obsolete("New code should use DataSourceBase.OpenDataSource instead")] DataSource(string fileName) { ... } ... } class XmlDataSource : DataSourceBase { ... }
Das habe ich letztendlich verwendet, da der Code abwärtskompatibel bleibt (dh Aufrufe, die
new DataSource("myFile.mdb")
noch funktionieren). Nachteil: Die Namen sind nicht so aussagekräftig, wie sie sein sollten.Erstellen Sie
DataSource
einen "Wrapper" für die eigentliche Implementierung:class DataSource { private DataSourceImpl impl; DataSource(string fileName) { impl = ... ? new MdbDataSourceImpl(fileName) : new XmlDataSourceImpl(fileName); } Data ReadData() { return impl.ReadData(); } abstract private class DataSourceImpl { ... } private class MdbDataSourceImpl : DataSourceImpl { ... } private class XmlDataSourceImpl : DataSourceImpl { ... } }
Nachteil: Jede Datenquellenmethode (wie z. B.
ReadData
) muss per Boilerplate-Code geroutet werden. Ich mag keinen Boilerplate-Code. Es ist überflüssig und überfüllt den Code.
Gibt es eine elegante Lösung, die ich verpasst habe?
new
keine Methode des Klassenobjekts ist (sodass Sie die Klasse selbst - eine als Metaklassen bezeichnete Technik - in Unterklassen unterteilen und steuern können, wasnew
tatsächlich geschieht), aber so funktioniert C # (oder Java oder C ++) nicht.Antworten:
Ich würde mich für eine Variante zu Ihrer zweiten Option entscheiden, mit der Sie den alten, zu generischen Namen auslaufen lassen können
DataSource
:Der einzige Nachteil hierbei ist, dass die neue Basisklasse nicht den offensichtlichsten Namen haben kann, da dieser Name bereits für die ursprüngliche Klasse beansprucht wurde und aus Gründen der Abwärtskompatibilität so bleiben muss. Alle anderen Klassen haben ihre beschreibenden Namen.
quelle
Die beste Lösung ist etwas, das Ihrer Option Nr. 3 nahe kommt. Behalten Sie das
DataSource
Meiste wie es jetzt ist und extrahieren Sie nur den Leserteil in seine eigene Klasse.Auf diese Weise vermeiden Sie doppelten Code und sind offen für weitere Erweiterungen.
quelle
XmlDataSource
.