web-dev-qa-db-ja.com

追加機能を提供するインターフェース実装の設計

インターフェイスの実装中に発生した設計上の問題があります。

機能PerformA()およびGetB()を提供することを約束するDeviceインターフェースがあるとしましょう。このインターフェースは、デバイスの複数のモデルに実装されます。 1つのモデルに他の実装に相当するものがない追加の機能CheckC()がある場合はどうなりますか?

私はさまざまな解決策を考え出しましたが、どれもインターフェイス設計ガイドラインに準拠していないようです。

  • CheckC()メソッドをインターフェースに追加し、その実装の1つを空のままにするには:
_interface ISomeDevice
{
    void PerformA();
    int GetB();
    bool CheckC();
}

class DeviceModel1 : ISomeDevice
{
    public void PerformA() { // do stuff }
    public int GetB() { return 1; }
    public bool CheckC() {
        bool res;
        // assign res a value based on some validation
        return res;
    }
}

class DeviceModel2 : ISomeDevice
{
    public void PerformA() { // do stuff }
    public int GetB() { return 1; }
    public bool CheckC() {
        return true; // without checking anything
    }
}
_

クラスが要求されたすべてのメソッドを実装せずに本当にインターフェースを実装するため、このソリューションは正しくないようです。

  • インターフェイスからCheckC()メソッドを省略し、それを呼び出すために明示的なキャストを使用するには:
_interface ISomeDevice
{
    void PerformA();
    int GetB();
}

class DeviceModel1 : ISomeDevice
{
    public void PerformA() { // do stuff }
    public int GetB() { return 1; }
    public bool CheckC() {
        bool res;
        // assign res a value based on some validation
        return res;
    }
}

class DeviceModel2 : ISomeDevice
{
    public void PerformA() { // do stuff }
    public int GetB() { return 1; }
}

class DeviceManager
{
    private ISomeDevice myDevice;
    public void ManageDevice(bool newDeviceModel)
    {
        myDevice = (newDeviceModel) ? new DeviceModel1() : new DeviceModel2();
        myDevice.PerformA();
        int b = myDevice.GetB();
        if (newDeviceModel)
        {
            DeviceModel1 newDevice = myDevice as DeviceModel1;
            bool c = newDevice.CheckC();
        }
    }
}
_

この解決策は、インターフェースに一貫性を持たせないようです。

  • CheckC()をサポートするデバイスの場合:CheckC()のロジックをインターフェイスに存在する別のメソッドのロジックに追加します。この解決策は常に可能であるとは限りません。

それで、そのような場合に使用される正しいデザインは何ですか?たぶん、インターフェイスの作成は完全に放棄して、別のデザインを優先する必要がありますか?

3
Limbo Exile

このソリューションを検討してください。

interface IDevice
{
    void PerformA();
    int GetB();
}

interface INewDevice : IDevice
{
    bool CheckC();
}

class DeviceModel1 : INewDevice
{
    ...
}


class DeviceModel2 : IDevice
{
    ...
}

class DeviceManager
{
    private IDevice myDevice;
    public void ManageDevice(bool newDeviceModel)
    {
        myDevice = (newDeviceModel) ? new DeviceModel1() : new DeviceModel2();
        myDevice.PerformA();
        int b = myDevice.GetB();
        if (newDeviceModel)
        {
            INewDevice newDevice = myDevice as INewDevice;
            bool c = newDevice.CheckC();
        }
    }
}

IDeviceとINewDeviceの違いを気にしない場合は、両方に共通の方法を使用できます。新しいデバイスに固有の動作が必要な場合は、新しいインターフェイスにキャストするため、特定の実装に縛られることはありません。

キャストが気になる場合は、 この質問を参照 タイプAまたはB(またはC、または...)の値を含む可能性があり、タイプセーフな方法を提供する新しいタイプを作成する方法について値のタイプが実際に何であるかに応じてアクションを実行します。名前付きパラメーターとラムダを使用するため、特に Joeyの回答 が好きです。たとえば、Either<IDevice, INewDevice>を保持するコレクションを作成してから、Matchメソッドを使用してキャストを回避できます。例えば。

IList<Either<IDevice, INewDevice>> devices = // get a list of devices
foreach (var device in devices) {
    device.Match(
        Left: oldDevice => // things to do if it's an old device
        Right: newDevice => // things to do if it's a new device
    );
}

このアプローチを使用して間違ったキャストを行うリスクはありません。

2
Doval

それは本当に大きく異なります(今ではそれは驚きです)。考慮すべき点がいくつかあります。

インターフェースの契約は何ですか?
インターフェースがperformAgetBを許可する必要があるが、device2checkCを呼び出す必要がある場合、インターフェースに準拠しません。 Cがチェック可能でなければならないとインターフェイスが定義している場合、device1は準拠しません。アルゴリズムがそのチェックを使用する場合、それは後者である可能性があります。

妥当なデフォルトの動作を定義できますか?
チェックだけの場合は、いつでもtrueを返すことができます。

オプションの操作ですか?
オプションとして定義します。インターフェイスは、例外のスローが許可されることを指定する場合があります。操作がサポートされているかどうかを返す別のメソッドCanCheckCを追加します。または、チェックが実行された場合にtrueを返し、結果を出力パラメータで返すTryCheckCを追加します。

これは新しいタイプのインターフェースですか?
一般に、デバイスがCをチェックできないが、あるクラスのデバイスはチェックできる場合、これは新しいインターフェースである可能性があります。したがって、キャストする前に、追加の操作を提供し、アルゴリズムで型チェックを使用する新しいインターフェースIDeviceWithCCheckを追加します。

1
Cephalopod