web-dev-qa-db-ja.com

コンストラクタの代わりにファクトリメソッドを使用する必要がありました。それを変更しても下位互換性はありますか?

問題

_.mdb_ファイルからデータを読み取るためのDataSourceメソッド(およびその他のものもあるかもしれませんが、簡単にする)を提供するReadDataというクラスがあるとします。

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

数年後、データソースとして_.xml_ファイルに加えて_.mdb_ファイルもサポートできるようにしたいと思いました。 「データの読み取り」の実装は、_.xml_ファイルと_.mdb_ファイルではまったく異なります。したがって、システムをゼロから設計する場合は、次のように定義します。

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

すばらしい、Factoryメソッドパターンの完全な実装。残念ながら、DataSourceはライブラリにあり、このようにコードをリファクタリングすると、

_var source = new DataSource("myFile.mdb");
_

ライブラリを使用するさまざまなクライアントで。 Woe is me、そもそもなぜファクトリーメソッドを使わなかったのですか?


ソリューション

これらは私が思いつくことができる解決策です:

  1. DataSourceコンストラクターがサブタイプ(MdbDataSourceまたはXmlDataSource)を返すようにします。それは私のすべての問題を解決します。残念ながら、C#はそれをサポートしていません。

  2. 別の名前を使用してください:

    _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 { ... }
    _

    これは、コードに下位互換性を維持するため(つまり、new DataSource("myFile.mdb")への呼び出しは引き続き機能するため)、私が使用することになりました。欠点:名前は、説明するほどにはわかりません。

  3. DataSourceを実際の実装の「ラッパー」にします。

    _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 { ... }
    }
    _

    欠点:Everyデータソースメソッド(ReadDataなど)は、ボイラープレートコードによってルーティングする必要があります。ボイラープレートコードは好きではありません。これは冗長であり、コードが雑然としています。

エレガントな解決策はありませんか?

15
Heinzi

DataSourceという名前の古くて汎用的な名前を段階的に廃止できるように、2番目のオプションのバリアントを探します。

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) { ... }
}

ここでの唯一の欠点は、新しい基本クラスに最も明確な名前を付けることができないことです。その名前は元のクラスですでに要求されており、下位互換性のためにその名前のままにする必要があるためです。他のすべてのクラスには説明的な名前があります。

最良の解決策は、オプション#3に近いものになります。 DataSourceはほとんどそのままにして、リーダー部分のみを独自のクラスに抽出します。

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 { ... }
}

このようにして、コードの重複を避け、さらなる拡張機能を利用できます。

6
nibra