web-dev-qa-db-ja.com

差別化されたカウント可能オブジェクトと差別化されていないカウント可能オブジェクトの両方で機能するカウンター/在庫クラスを作成する正しいOO方法は何ですか?

あなたは豆の取引についてのビデオゲームを書いています。小豆、黒豆、ピント豆、あなたはそれに名前を付けます。誰もが知っているように、すべての豆は同じです。そのビデオゲームのトレーダーの「在庫」クラスを次のように記述します(すべてのヌルチェックをスキップします)。

    class BeansInventory{
HashMap<BeanType,Integer> amountsOwned;

public void receive(BeanType typeReceived, int amount)
{amountsOwned.put(typeReceived,amountsOwned.get(typeReceived)+amount)}

public void remove(BeanType typeRemoved, int amount)
{amountsOwned.put(typeRemoved,amountsOwned.get(typeRemoved)-amount)}

public Integer amountOwned(BeanType type)
{amountsOwned.get(type)}
}

それは何年もの間うまくいきます。その後、突然、他の誰かがコーヒー豆を取引するゲームに追加する素晴らしいアイデアを思いつきました。誰もが知っているように、それぞれのコーヒー豆は他のものとは完全に異なります。したがって、別の種類の豆としてコーヒーを追加することはできません。各コーヒー豆は独自のインスタンスです。コーヒー在庫クラスは次のようになります。

class CoffeeInventory{
    HashMap<CoffeType,List<CoffeeBeans>> coffeOwned;

    public void receive(CoffeType typeReceived, CoffeeBeans... beans)
    {coffeOwned.get(typeReceived).addAll(beans)}

    public void remove(CoffeType typeRemoved, CoffeeBeans... beans)
    {coffeOwned.get(typeRemoved).removeAll(beans)}

    public Integer amountOwned(CoffeType type)
    {amountsOwned.get(type).size()}
    }

しかし今、あなたはたくさんの問題を抱えています。同じタスクに2つのAPIがあります。すべての取引インフラストラクチャは、ピント豆とコーヒー豆のどちらを取引しているかを注意深くチェックし、「これを保存する」という同じタスクに対して、異なる在庫と異なる署名の異なるメソッドを呼び出す必要があります。

これで、ifチェックとinstanceof、およびコードの臭いの他のすべての兆候で満たされたこのコードができました。しかし、単一の単純なAPIを使用する方法を理解することはできません。私は何をするのが正しいのか分かりません。

2
CarrKnight

BeansInventoryCoffeeInventoryとは異なります。実際には、物を保管するのではなく、金額を追跡するため、「在庫」とは呼びません。さらに、CoffeeInventoryクラスには、格納されたBeanオブジェクトを返すメソッドが表示されませんが、BeansInventoryのように単純なカウンターを使用できない場合は、これが必要になると思います。

したがって、コーヒー豆と他の豆を区別するためのチェックを回避するためのクリーンな解決策は、豆の間にまったく違いがないことです!

_class BeansInventory{
  HashMap<BeanType,Integer> amountsOwned;

  public void receive(BeanType typeReceived, int amount) {
       amountsOwned.put(typeReceived,amountsOwned.get(typeReceived)+amount)
  }

  public void receive(BeanType typeReceived, Beans beans) { //beans is a list of beans
       this.receive(typeReceived, beans.size())
  }

  //...
}
_

このように、Beanがどのインベントリに挿入されるかは問題ではなく、その上に既存のAPIを変更する必要はありません。在庫間の取り扱いが異なるだけですが、動作は同じです。もちろん、それは両方がreceive(Bean bean)remove(Bean bean)contains(Bean bean)などのジェネリックメソッドを持つ_Inventory Interface_を実装する必要があることを意味します。

また、より一般的なものにすることを検討し、通常のBeanにもBeanオブジェクトの使用を開始する必要があります。たとえそれらがすべて同じであるとしても。このようにして、すべて同じように見えるBean用の特別なインベントリを作成する必要はありません。 (ただし、パフォーマンス上の利点が得られる場合があります)

1
valenterry