web-dev-qa-db-ja.com

OOデザイン-カプセル化の質問

シナリオ

ドメインにShopというエンティティがあるとしましょう。ショップオーナー(ユーザー)として、お客様ユーザーが交換できるリワードを作成できます。

何らかの報酬作成ポリシーを設定することもできます(例:店のオーナーとして作成できる報酬の最大数:その値が4の場合->自分に4つを超える報酬を追加することはできませんお店)。

報酬作成(お店用)

次の1対Nの関係を考慮してください:Shop(1)->(N)Reward、報酬を管理する最も適切な方法は何でしょうか( ShopにはRewardのリストが含まれることを考慮に入れてください)?

IRewardCreationPolicyインターフェースによって何らかの形で抽象化され、必要に応じて使用できる報酬ポリシーがあった場合:

interface IRewardCreationPolicy {
   isAllowed(shop: Shop);
}

以下は、私が考慮に入れているいくつかの代替設計です。

代替#1

Reward管理はShopクラスによって完全に処理されます:

class Reward {
   /* props, methods... */
}

class Shop {
   private id: number;
   private name: string;
   private rewards: Reward[];

   constructor(rewardCreationPolicy: IRewardCreationPolicy) {}

   addReward(reward: Reward): void {
      if (!this.rewardCreationPolicy.isAllowed(this)) {
         // throw exception
      }
      this.rewards.Push(reward);
   }

   removeReward(id: number): void {
      /* Remove reward with the given id */
   }
}

class AddRewardUseCase {
   constructor(shopStorage: IShopStorage) {}

   execute(request: AddRewardRequest) {
      const shop = this.shopStorage.findById(request.shopId);
      shop.addReward(request.reward);
      this.shopStorage.update(shop);
   }
}

代替#2

報酬管理は、Shopクラスの外部で完全に処理されます。

class Reward {
   /* props, methods... */
}

class Shop {
   private id: number;
   private name: string;
   private rewards: Reward[];

   constructor() {}

   getRewards(): Reward[] {
      return this.rewards;
   }
}

class AddRewardUseCase {
   constructor(
      shopStorage: IShopStorage,
      rewardCreationPolicy: IRewardCreationPolicy
   ) {}

   execute(request: AddRewardRequest) {
      const shop = this.shopStorage.findById(request.shopId);

      if (!this.rewardCreationPolicy.isAllowed(shop)) {
         // throw exception
      }

      shop.getRewards().Push(request.reward);
      this.shopStorage.update(shop);
   }
}

ご質問

この場合、オブジェクト(Shop 's)のリストを保持するクラス(Reward)があります。このリストで考えられるアクションをホルダークラス内にカプセル化する必要がありますか?この場合、報酬以外に他のリストがあれば、それも所有者自身によって管理されると思います。

ここではカプセル化について話していることを理解しているので、その場合はAlternative#1が最適だと思います。これについてどう思いますか?

1
charliebrownie

依存します、より具体的には、RewardCreationPolicyがショップにリンクされていることが理にかなっているかどうかによって異なります。

このシナリオをより簡単な例に置き換えましょう。

  • あなたの問題は、別のリストのランダムなサブセットのリストを作成する必要があることです。ターゲットリストを空としてインスタンス化し、ランダムな述語関数に基づいて要素を追加します。この述語は、リスト実装に属する理由はありません。ほとんどのリストでは、述語を作成する必要はありません。

  • 問題は、サイズが4に制限され、プログラムで通常のリストとして使用されているリスト実装を提供する必要があることです。リストはそれ自体に要素を追加するかどうかを認識できる必要があります。述語はカスタムリスト実装の一部です。

したがって、ShopとRewardCreationPolicyの使用法、およびそれらの間のリンクに基づいて、実装によってそれらを関連付けることは理にかなっています。

追加のコンテキストが必要ですが、既存のコンテキストに基づいて、Shopに責任を持たせることは、より強力なカプセル化を提供し、より魅力的に聞こえます。しばらくして、報酬が適切に管理されるようにするために追加する必要のある述語がいくつかあることに気づいた場合は、そうではありません。

1
Arthur Havlicek

あなたの例から、RewardsShop集合体のトランザクション整合性境界の一部であることは明らかだと思います。

DDDの専門用語では、Aggregateは常にすべての不変のルールを満たす必要があります。 Shopオブジェクトがすべてのビジネスルール(4つの報酬の制限を含む)を常に満たすようにするには、shopオブジェクトを介して報酬を操作する以外に方法はありません。

不変条件を確立、検証、および適用する方法(現在の報酬数と制限のチェックなど)は柔軟であり、時間の経過とともに変化する可能性があります。ただし、変更できないのは、shopオブジェクトを永続化するまでに、これらの不変条件が完全に満たされるということです。

最終的な整合性/検証の1つである別のパスにつながる可能性がある唯一の条件は、ショップオブジェクトの一部として膨大な数の報酬オブジェクトをロードする必要がある場合です。あなたのケースで明確な制限を設定していて、その制限はかなり小さいので、この条件は適用できない場合があります。

1
Subhash Bhushan