web-dev-qa-db-ja.com

共通のインターフェイスから1つの派生クラスのみが実装するメソッドを使用するにはどうすればよいですか?

いくつかの派生クラスによって実装された1つのインターフェース(C++で言う)があります。インターフェイスが次のようであるとしましょう:

class IBase
{
  virtual bool method_1() = 0;
  virtual long method_2(const std::string str) = 0;
  // other methods
  virtual long method_10(int value) = 0;
};

現在までに、6つの異なる派生クラス(将来はさらに増える予定)がインターフェースを実装しています。私のコードの多くのアルゴリズムでは、このインターフェースを多態的に使用しています。例えば:

// some usage
obj = getObject();
if (obj->method_1())
{
  return obj->method_2("Hello");
}
else
{
  return obj->method_10(12);
}

時間の経過とともに、1つの派生クラスだけがそれらを実装する多くのメソッドがあることがわかりました。では、Qは、これらのメソッドをインターフェイスに配置するのに適切な設計ですか、それとも派生クラスに直接配置する必要があるのですか?ただし、次のことに注意してください。

  • それらをインターフェイスに配置すると、1つの派生クラスのみがそれらを実装し、残りの派生クラスの実装は空になります。
  • それらを派生クラスに入れると、実装されたアルゴリズムはもう汎用ではなくなり、アルゴリズムの実装中に派生クラスのタイプを確認する必要があります。

この場合、先に進むための正しい方法は何ですか?

5
Gupta

1つの派生クラスのみが共通のインターフェイスからメソッドを実装する必要がある場合にどうすればよいですか?

このような状況に陥った場合、通常は設計に問題があり、リファクタリングする必要があります。

たとえば、呼び出しコードで、クラスの複数のメソッドを呼び出し、これらのメソッドの結果に基づいて決定を行います。これはおそらく間違っています。代わりに、ロジックをクラスとインターフェースにカプセル化する必要があります。

interface IShoppingBasket 
{
    void AddItem(Item item); //only one method
}

class ElectronicsShoppingBasket : IShoppinBasket
{
    public void AddItem(Item item)
    {
        if(item.Type == software) //logic is in the class not the calling code
        {
            this.softwareLicences.Add(item); //private method only in this implementation
        }
        else
        {
            this.physicalGoods.Add(item); //private method only in this implementation
        }
    }
}

IsSoftwareLicence()やAddPhysicalGoods()は、特定の実装にのみ関係しているため、公開していません。汎用インターフェースではありません。

これにより、機能の異なる複数の派生クラスを作成できますが、それらすべてに同じタスクを実行させることができます。

特定のShoppingBasket実装に追加情報が必要な場合、これは多くの場合、買い物かごの依存関係と見なすことができます。

class ElectronicsShoppingBasket : IShoppinBasket
{
    public void ElectronicsShoppingBasket (ICustomerDetailsProvider custDet)
    {
        this.custDet = custDet
    }

    public void AddItem(Item item)
    {
       if(this.custDet.GetAddress().Country == Country.NorthKorea)
       {
            throw new Exception("embargoed country"); 
       }
       .....
    }
}

ここでは、コンストラクターは継承されておらず、インターフェースの一部でもありませんが、ElectronicsShoppingBasketがある場合は常にCustomerDetailsProviderを配置する必要があります。

繰り返しになりますが、呼び出し元のコードは顧客情報を知っていたり、ロジックを持っている必要はありません

class CallingCode
{
     public CallingCode(IShoppingBasket sb) //pass me anything as long as it has AddItem
     {
         this.sb = sb;
     }

     public void AddItem(Item item)
     {
          this.sb.AddItem(item); //if you need any other details that's your problem
     }
}
12
Ewan