Problem
Powiedzmy, że mam klasę o nazwie, DataSource
która zapewnia ReadData
metodę (i może inne, ale bądźmy prostymi) do odczytu danych z .mdb
pliku:
var source = new DataSource("myFile.mdb");
var data = source.ReadData();
Kilka lat później postanawiam, że chcę być w stanie obsługiwać .xml
pliki oprócz .mdb
plików jako źródeł danych. Implementacja „odczytu danych” jest zupełnie inna dla plików .xml
i .mdb
; dlatego gdybym zaprojektował system od zera, zdefiniowałbym go w następujący sposób:
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 */ }
}
Świetna, doskonała implementacja wzorca metody Factory. Niestety, DataSource
znajduje się w bibliotece i przefaktoryzowanie kodu w ten sposób zepsułoby wszystkie istniejące wywołania
var source = new DataSource("myFile.mdb");
w różnych klientach korzystających z biblioteki. Biada mi, dlaczego nie użyłem metody fabrycznej?
Rozwiązania
Oto rozwiązania, które mógłbym wymyślić:
Spraw, aby konstruktor DataSource zwrócił podtyp (
MdbDataSource
lubXmlDataSource
). To rozwiązałoby wszystkie moje problemy. Niestety C # nie obsługuje tego.Użyj różnych nazw:
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 { ... }
Właśnie tego użyłem, ponieważ utrzymuje on kompatybilność wsteczną (tj. Wywołania do
new DataSource("myFile.mdb")
pracy). Wada: nazwy nie są tak opisowe, jak powinny.Zrób
DataSource
„opakowanie” dla prawdziwej implementacji: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 { ... } }
Wada: każda metoda źródła danych (taka jak
ReadData
) musi być kierowana za pomocą kodu typu „kocioł”. Nie podoba mi się kod płyty. Jest zbędny i zaśmieca kod.
Czy jest jakieś eleganckie rozwiązanie, za którym tęskniłem?
new
nie jest to metoda obiektu klasy (abyś mógł podklasować samą klasę - technikę znaną jako metaklasy - i kontrolować, conew
faktycznie robi), ale nie tak działa C # (lub Java, lub C ++).Odpowiedzi:
Wybrałbym wariant drugiej opcji, który pozwala wycofać starą, zbyt ogólną nazwę
DataSource
:Jedyną wadą jest to, że nowa klasa podstawowa nie może mieć najbardziej oczywistej nazwy, ponieważ nazwa ta została już zgłoszona dla oryginalnej klasy i musi pozostać taka dla kompatybilności wstecznej. Wszystkie pozostałe klasy mają swoje opisowe nazwy.
źródło
Najlepszym rozwiązaniem będzie coś bliskiego Twojej opcji # 3. Zachowaj
DataSource
przeważnie taki, jaki jest teraz i wyodrębnij tylko część czytelnika do własnej klasy.W ten sposób unikniesz powielania kodu i będziesz otwarty na dalsze rozszerzenia.
źródło
XmlDataSource
.