10

次の状態パターンの実装については、疑問があります。

Order オブジェクトがあります。簡単にするために、数量、productId、価格、およびサプライヤーがあるとします。また、注文が遷移できる一連の既知の状態があります。

  • 状態 a: 注文は新規です。数量は > 0 で、productId が必要です。価格とサプライヤーはまだ割り当てられていません。
  • 状態 b: 誰かが注文をチェックします。キャンセルするか、サプライヤを割り当ててもらうことしかできません。
  • 状態 c: サプライヤーは、クライアントに請求する価格のみを記入できます。
  • 状態 d: 注文がキャンセルされました。
  1. Order.isValid() は状態間で変化します。つまり、一部の操作ができない状態です。void setQuantity
    (int q) {
    if (_state.canChangeQuantity()) this.quantity = q;
    それ以外の場合は例外をスローします。
    それとも、各状態を取得してsetQuantity
    操作を実装する必要がありますか? その場合、値はどこに格納されますか? 順番で、それとも状態で?後者の場合、各状態遷移でデータをコピーする必要がありますか?

  2. orderProcessor.process(order) は、order.IsValid をチェックし、注文を何らかの状態に遷移させ、それをデータベースに保存し、いくつかのカスタム アクションを実行するオブジェクトです (状態によっては、管理者に通知され、他の状態ではクライアントに通知されるなど)。州ごとに 1 つずつ持っています。
    StateAOrderProcessor では、注文を確認した人にメールで通知され、注文は状態 b に遷移します。
    これで、状態遷移が Order クラスの外にプッシュされます。つまり、Order には「setState」メソッドがあり、各プロセッサがそれを変更できます。外部から状態を変更するこのことは、いいことではありません。そうですか?

  3. もう 1 つのオプションは、すべての検証ロジックを各状態のプロセッサに移動することですが、注文の数量がいつ変更されたかを追跡して、その操作が現在の状態で有効かどうかを確認する必要があります。 それは私に一種の貧血を残します。

皆さんどう思いますか?このデザインをより良くするためのアドバイスをいただけますか?

4

5 に答える 5

6

これは、状態パターンの理想的なシナリオです。

状態パターンでは、状態クラスは、遷移の有効性をチェックするだけでなく、状態の遷移を担当する必要があります。さらに、注文クラスの外に状態遷移をプッシュすることは良い考えではなく、パターンに反しますが、それでも OrderProcessor クラスを操作できます。

setQuantity 操作を実装するには、各状態クラスを取得する必要があります。状態クラスは、状態の変更を伴うかどうかにかかわらず、一部の状態では有効であるが他の状態では有効でない可能性があるすべてのメソッドを実装する必要があります。

canChangeQuantity() や isValid() などのメソッドは必要ありません。現在の状態に対して無効な操作を試行するとスローされるため、状態クラスにより、注文インスタンスが常に有効な状態にあることが保証されます。

Order クラスのプロパティは、状態ではなく注文と共に保存されます。.Net では、状態クラスを Order クラス内にネストし、呼び出し時に注文への参照を提供することでこれを機能させます。これにより、状態クラスは注文のプライベート メンバーにアクセスできるようになります。.Net で作業していない場合は、言語用の同様のメカニズムを見つける必要があります。たとえば、C++ のフレンド クラスです。

状態と遷移に関するいくつかのコメント:

  • 状態 A は、注文が新規であり、数量が 0 を超えており、製品 ID があることを示しています。私にとって、これは、コンストラクターでこれらの値の両方を提供していることを意味します (インスタンスが有効な状態で開始されるようにするためですが、setQuantity メソッドは必要ありません)、または assignProduct を持つ初期状態が必要です。初期状態から状態 A に遷移する (Int32 quantity, Int32 productId) メソッド。

  • 同様に、サプライヤが価格を記入した後、状態 C から移行する最終状態を検討することもできます。

  • 状態遷移で 2 つのプロパティの割り当てが必要な場合は、遷移を明示的にするために、(setQuantity の後に setsetProductId が続くのではなく) パラメータによって両方のプロパティを受け入れる単一のメソッドの使用を検討することをお勧めします。

  • また、よりわかりやすい状態名をお勧めします。たとえば、StateD の代わりに、CanceledOrder と呼びます。

新しい状態を追加せずに、C# でこのパターンを実装する方法の例を次に示します。

 public class Order
 {
  private BaseState _currentState;

  public Order(
   Int32 quantity,
   Int32 prodId)
  {
   Quantity = quantity;
   ProductId = prodId;
   _currentState = new StateA();
  }

  public Int32 Quantity
  {
   get; private set;
  }

  public Int32 ProductId
  {
   get; private set;
  }

  public String Supplier
  {
   get; private set;
  }

  public Decimal Price
  {
   get; private set;
  }

  public void CancelOrder()
  {
   _currentState.CancelOrder(this);
  }

  public void AssignSupplier(
   String supplier)
  {
   _currentState.AssignSupplier(this, supplier);
  }

  public virtual void AssignPrice(
   Decimal price)
  {
   _currentState.AssignPrice(this, price);
  }


  abstract class BaseState
  {
   public virtual void CancelOrder(
    Order o)
   {
    throw new NotSupportedException(
     "Invalid operation for order state");
   }

   public virtual void AssignSupplier(
    Order o, 
    String supplier)
   {
    throw new NotSupportedException(
     "Invalid operation for order state");
   }

   public virtual void AssignPrice(
    Order o, 
    Decimal price)
   {
    throw new NotSupportedException(
     "Invalid operation for order state");
   }
  }

  class StateA : BaseState
  {
   public override void CancelOrder(
    Order o)
   {
    o._currentState = new StateD();
   }

   public override void AssignSupplier(
    Order o, 
    String supplier)
   {
    o.Supplier = supplier;
    o._currentState = new StateB();
   }
  }

  class StateB : BaseState
  {
   public virtual void AssignPrice(
    Order o, 
    Decimal price)
   {
    o.Price = price;
    o._currentState = new StateC();
   }
  }

  class StateC : BaseState
  {
  }

  class StateD : BaseState
  {
  }
 }

オーダー プロセッサ クラスを操作することはできますが、それらはオーダー クラスのパブリック メソッドを操作し、オーダーの状態クラスが状態遷移のすべての責任を保持します。現在の状態を知る必要がある場合 (注文プロセッサが何をすべきかを判断できるようにするため)、注文クラスと BaseState に String Status プロパティを追加し、それぞれの具体的な状態クラスにその名前を返すようにします。

于 2009-12-11T02:40:06.743 に答える
0

現在の状態オブジェクトの変更は、状態オブジェクトから直接、Orderから、さらには外部ソース(プロセッサ)からもOKで行うことができますが、珍しいことです。

Stateパターンに従って、Orderオブジェクトはすべての要求を現在のOrderStateオブジェクトに委任します。setQuantity()が状態固有の操作である場合(これはあなたの例にあります)、各OrderStateオブジェクトはそれを実装する必要があります。

于 2009-08-28T14:39:09.800 に答える
0

Order クラスに情報を格納し、Order インスタンスへのポインターを状態に渡します。このようなもの:


class Order {
  setQuantity(q) {
    _state.setQuantity(q);
  } 
}

StateA {
  setQuantity(q) {
    _order.q = q;
  }
}

StateB {
  setQuantity(q) {
    throw exception;
  }
}

于 2009-12-10T22:29:37.117 に答える
0

状態パターンが機能するためには、状態クラスが使用できるインターフェイスをコンテキスト オブジェクトが公開する必要があります。最低限、これにはchangeState(State)メソッドを含める必要があります。残念ながら、これはパターンの制限の 1 つにすぎず、パターンが常に有用であるとは限らない理由の 1 つとして考えられます。状態パターンを使用する秘訣は、状態に必要なインターフェイスをできるだけ小さく保ち、狭い範囲に制限することです。

(1)canChangeQuantityメソッドを持つことは、すべての州にsetQuantity. 一部の状態が例外をスローするよりも複雑なことを行っている場合、このアドバイスは従わない可能性があります。

(2)setStateやむを得ない方法。ただし、可能な限り範囲を限定する必要があります。Java では、これはおそらく Package スコープになり、.Net では Assembly (内部) スコープになります。

(3) 検証に関するポイントは、いつ検証を行うかという問題を提起します。場合によっては、クライアントがプロパティを無効な値に設定できるようにして、何らかの処理を行うときにのみ検証することをお勧めします。この場合、コンテキスト全体を検証する「isValid()」メソッドを持つ各状態は理にかなっています。それ以外の場合は、より即時のエラーが必要です。その場合、値を変更する前に set メソッドによって呼び出されるisQuantityValid(qty)andを作成し、false を返すと例外をスローします。isPriceValid(price)私は常にこれら 2 つの初期検証と後期検証を呼び出してきましたが、現在何をしようとしているのかを知らなければ、どちらが必要かを判断するのは簡単ではありません。

于 2009-12-08T12:55:24.280 に答える
-1

州ごとに 1 つずつ、いくつかの異なるクラスがありますか。

BaseOrder {
    //  common getters
    // persistence capabilities
}

NewOrder extends BaseOrder {
    // setters
    CheckingOrder placeOrder();
} 

CheckingOrder extends BaseOrder {
     CancelledOrder cancel();
     PricingOrder assignSupplier();
}

等々。特定の状態での命令を必要とするコードは、正しいクラスのオブジェクトのみを取得するため、状態チェックは必要ありません。任意の状態のケースで注文を操作したいだけのコードは、BaseClass を使用します。

于 2009-08-28T04:42:30.090 に答える