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


15

問題

ファイルからデータを読み取るためのメソッドDataSourceを提供するクラスReadData(および他のクラスもありますが、物事をシンプルにしましょう)があるとし.mdbます。

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");

ライブラリを使用するさまざまなクライアントで。なんてこった、そもそもファクトリーメソッドを使用しなかったのか?


解決策

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

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

    欠点:すべてのデータソースメソッド(などReadData)を定型コードでルーティングする必要があります。定型コードが好きではありません。冗長であり、コードが乱雑です。

見逃したエレガントなソリューションはありますか?


5
#3の問題をもう少し詳しく説明できますか?それは私にはエレガントに思えます。(または、後方互換性を維持しながら得る限りエレガントです。)
pdr

APIを公開するインターフェイスを定義し、ファクトリでインターフェイスを実装するクラスの対応するインスタンスを作成することで、古いコードと新しいコードのラッパーを作成することで、既存のメソッドを再利用します。
トーマス

@pdr:1.メソッドシグネチャへのすべての変更は、もう1か所で行う必要があります。2. Implクラスを内部または非公開にすることができます。これは、クライアントがXmlデータソースなどでのみ利用可能な特定の機能にアクセスしたい場合に不便です。または、それらを公開することもできます。つまり、クライアントは同じことを行うために2つの異なる方法を使用できるようになります。
ハインツィー

2
@Heinzi:オプション3を好みます。これは標準の「ファサード」パターンです。本当にすべてのデータソースメソッドを実装に委任する必要があるのか、それとも一部だけを委任するのかを確認する必要があります。「DataSource」に残っている一般的なコードがまだ残っているのでしょうか?
Doc Brown

newクラスオブジェクトのメソッドではないのは残念です(そのため、クラス自体をサブクラス化(メタクラスとして知られる手法new)し、実際に行うことを制御できます)が、それはC#(またはJava、C ++)の動作方法ではありません。
ドナルドフェローズ

回答:


12

古い、あまりにも一般的な名前を段階的に廃止できるようにする2番目のオプションのバリアントを探します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) { ... }
}

ここでの唯一の欠点は、新しいベースクラスが最も明確な名前を持つことができないことです。その名前は、元のクラスに対して既に要求されており、後方互換性のためにそのように残る必要があるためです。他のすべてのクラスには説明的な名前が付いています。


1
+1、質問を読んだときに頭に浮かんだものです。私はOPのオプション3がより好きですが。
Doc Brown

すべての新しいコードを新しい名前空間に入れると、基本クラスに最もわかりやすい名前を付けることができます。しかし、それが良いアイデアかどうかはわかりません。
svick

基本クラスには、接尾辞「Base」が必要です。クラスDataSourceBase
スティーブン

6

最善の解決策は、オプション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 { ... }
}

この方法により、コードの重複を回避し、さらに拡張することができます。


+1、素敵なオプション。ただし、明示的にインスタンス化することでXmlDataSource固有の機能にアクセスできるため、オプション2を引き続き使用しますXmlDataSource
ハインジ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.