Avrei dovuto usare un metodo factory invece di un costruttore. Posso cambiarlo ed essere ancora retrocompatibile?

15

Il problema

Diciamo che ho una classe chiamata DataSource che fornisce un metodo ReadData (e forse altri, ma manteniamo le cose semplici) per leggere i dati da un file .mdb :

var source = new DataSource("myFile.mdb");
var data = source.ReadData();

Alcuni anni dopo, decido di voler supportare .xml file oltre a .mdb file come origini dati. L'implementazione per "lettura dei dati" è abbastanza diversa per .xml e .mdb file; quindi, se dovessi progettare il sistema da zero, lo definirei in questo modo:

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 */ }
}

Grande, una perfetta implementazione del modello di metodo Factory. Purtroppo, DataSource si trova in una libreria e il refactoring del codice come questo potrebbe interrompere tutte le chiamate esistenti di

var source = new DataSource("myFile.mdb");

nei vari client che utilizzano la libreria. Guai a me, perché non ho usato un metodo di fabbrica in primo luogo?

Soluzioni

Queste sono le soluzioni che ho potuto trovare:

  1. Fai in modo che il costruttore DataSource restituisca un sottotipo ( MdbDataSource o XmlDataSource ). Questo risolverebbe tutti i miei problemi. Sfortunatamente, C # non lo supporta.

  2. 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 usando perché mantiene il codice compatibile all'indietro (ad esempio, le chiamate a new DataSource("myFile.mdb") funzionano ancora). Svantaggio: i nomi non sono così descrittivi come dovrebbero.

  3. Crea DataSource un "wrapper" per l'implementazione reale:

    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: il ogni metodo di origine dati (come ReadData ) deve essere instradato dal codice boilerplate. Non mi piace il codice standard. È ridondante e ingombra il codice.

C'è qualche soluzione elegante che mi è sfuggita?

    
posta Heinzi 29.04.2013 - 16:29
fonte

2 risposte

12

Vado per una variante alla tua seconda opzione che ti consente di eliminare gradualmente il vecchio nome troppo generico DataSource :

abstract class AbstractDataSource { ... } // corresponds to the abstract DataSource in the ideal solution

class XmlDataSource : AbstractDataSource { ... }
class MdbDataSource : AbstractDataSource { ... } // contains all the code of the existing DataSource class

[Obsolete("New code should use AbstractDataSource instead")]
class DataSource : MdbDataSource { // an 'empty shell' to keep old code working.
    DataSource(string fileName) { ... }
}

L'unico inconveniente è che la nuova classe base non può avere il nome più ovvio, perché quel nome era già stato rivendicato per la classe originale e deve rimanere tale per compatibilità con le versioni precedenti. Tutte le altre classi hanno i loro nomi descrittivi.

    
risposta data 29.04.2013 - 16:52
fonte
6

La soluzione migliore sarà qualcosa di simile alla tua opzione # 3. Mantieni il DataSource per lo più com'è adesso, ed estrai solo la parte del lettore nella sua classe.

class DataSource {
    private Reader reader;

    DataSource(string fileName) {
        reader = ... ? new MdbReader(fileName) : new XmlReader(fileName);
    }

    Data ReadData() {
        return reader.next();
    }

    abstract private class Reader { ... }
    private class MdbReader : Reader { ... }
    private class XmlReader : Reader { ... }
}

In questo modo, si evita il codice duplicato e si aprono ulteriori estensioni.

    
risposta data 30.04.2013 - 12:11
fonte

Leggi altre domande sui tag