Il problema
Diciamo che ho una classe chiamata DataSource
che fornisce un ReadData
metodo (e forse altri, ma manteniamo le cose semplici) per leggere i dati da un .mdb
file:
var source = new DataSource("myFile.mdb");
var data = source.ReadData();
Qualche anno dopo, decido di voler supportare i .xml
file oltre ai .mdb
file come origini dati. L'implementazione per "leggere i dati" è abbastanza diversa per .xml
e .mdb
file; quindi, se dovessi progettare il sistema da zero, lo definirei così:
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 */ }
}
Fantastico, una perfetta implementazione del modello del metodo Factory. Sfortunatamente, DataSource
si trova in una libreria e il refactoring del codice in questo modo interromperebbe tutte le chiamate esistenti
var source = new DataSource("myFile.mdb");
nei vari client usando la libreria. Guai a me, perché non ho usato un metodo di fabbrica in primo luogo?
soluzioni
Queste sono le soluzioni che potrei trovare:
Fare in modo che il costruttore DataSource restituisca un sottotipo (
MdbDataSource
oXmlDataSource
). Ciò risolverebbe tutti i miei problemi. Sfortunatamente, C # non lo supporta.Usa nomi diversi:
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 { ... }
Questo è quello che ho finito per usare poiché mantiene il codice compatibile con le versioni precedenti (ovvero le chiamate
new DataSource("myFile.mdb")
funzionano ancora). Svantaggio: i nomi non sono così descrittivi come dovrebbero essere.Crea
DataSource
un "wrapper" per la vera implementazione: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 { ... } }
Svantaggio: ogni metodo di origine dei dati (come
ReadData
) deve essere instradato dal codice di boilerplate. Non mi piace il codice boilerplate. È ridondante e ingombra il codice.
C'è qualche soluzione elegante che mi è sfuggita?
new
non sia un metodo dell'oggetto class (in modo da poter sottoclassare la classe stessa - una tecnica nota come metaclassi - e controllare ciò che new
effettivamente fa) ma non è così che funziona C # (o Java o C ++).