0

実際のコードを簡略化して、要点を示す最小の例にしました。セッター/ゲッター等の不足すみません。

顧客が順番に閲覧するWebページがいくつかあると想像してください。ここでの使用例は次のとおりです。-

  • ユーザーは必要な本を選択します
  • ユーザーは、郵便または電子メールで送信するかどうか、および関連する詳細を選択します
  • システムは注文を履行します

この質問は、2つの配信方法に焦点を当てています。これは次のようにモデル化されます。

interface DeliveryDetails
{
    // Implementations of this have nothing in common other than that they
    // fulfil the same logical role.
}

class EmailDeliveryDetails implements DeliveryDetails
{
    String emailAddress;  // It really has a constructor and getter, I promise.
}

class PostalDeliveryDetails implements DeliveryDetails
{
    String streetAddress;
    String Country;
}

ここで、ページを閲覧するときにユーザーが入力した情報を表すために、次のクラスがあります。

class PurchaseData
{
    String title;
    DeliveryDetails deliveryDetails;
}

ユーザーがWebページをステップスルーすると、情報はのインスタンスに保存されますPurchaseData。ユーザーがページに戻ると、以前に入力した内容を表示できます。ユーザーが確認し、本を配達するときが来たら、またはdeliveryDetailsのインスタンスを参照します。PostalDeliveryDetailsEmailDeliveryDetails

結論として、ユーザーが自分の情報を確認すると、次のようになります。

    // Some code in a factory
    if ( purchaseData.deliveryDetails instanceof EmailDelivery )
    {
        // construct a EmailDeliveryService( purchaseData, SMTP details, etc ... )
    }
    if ( purchaseData.deliveryDetails instanceof PostalDelivery )
    {
        // construct a PostalDeliveryService( purchaseData, etc ... )
    }
}

Deliveryインターフェイスにメソッドがないのは困ります。

これは、電子メールと郵便配達の違いによって強制されます。

DeliveryDetails.deliver()実装がSMTPサーバーアドレスのようなものを静的に取得することを強制するので、それは良い方法ではないと思います。これは懸念を混乱させます(配管とユーザーが入力した情報)。

任意のタイプのものを格納する必要がある場合は、ジェネリックスが役立ちます。PurchaseData<T extends Delivery>PurchaseDataインスタンスの作成時に配信タイプが不明であるため、ジェネリック()を使用することはできません。とにかく、これは工場では役に立ちません。

この空のインターフェースは大丈夫ですか?このコードを設計するためのより良い方法はありますか?

4

4 に答える 4

1

EmailDeliveryDetails共通しPostalDeliveryDetailsて「配達」の概念しかありませんが、彼らの行動も知識も同じではありません。この場合、共通の祖先DeliveryDetailsは適していません。

個人的には、から継承ツリーを作成しますPurchaseData

于 2012-04-21T22:35:37.580 に答える
1

私にとって、との(データ)の違いはアドレスEmailDeliveryDetailsPostalDeliveryDetails要約されます。したがって、私の最初の本能は、そのデータを別のAddressクラスに抽出することです。次に、emailAddressとstreetAddressのオプションフィールドを持つ単一のアドレス、または電子メールアドレスと郵便アドレスの個別のサブクラスを持つクラス階層を使用することを決定できます。

使用するのがよりクリーンであり、実用性が概念的な「純度」よりも優れているため、オプションのフィールドを備えた単一のクラスを好みます。

アップデート

以下のコメントチェーンに基づく:

特定のクラスのプロパティ(つまり、可能な状態)の間に重複がない場合、それらを多態的に処理しようとするのは非常に厄介です。また、それらに多くの機能を組み込むつもりがない場合は、いくつかの共通のインターフェースを継承する別個のクラスでそれらを処理することはさらに困難です(あなたも指摘したように)。OTOHは、概念的にはすべて、ある種の住所データであるため、1つのクラスで処理できます。

ただし、これのほとんどは推測であることに注意してください。より詳細な情報がなければ、設計について推論することは困難です。

EmailDeliveryDetailsあなたは私が多くの振る舞いをするつもりはないという点で正しいですPostalDeliveryDetails。実際のアプリケーションでは、これらの詳細はデータベースに保持され、詳細は外部システムに送信されます。

ああ、それで、あなたは実際にそれらを多形的に扱うつもりはありません。永続化するデータのさまざまなビットにアクセスするために必要なのは、共通の「ハンドル」だけです。そして、永続性は通常、ポリモーフィズムとインターフェースを気にしません。この場合、クラスを空のインターフェースから継承させることは問題ありません。

于 2012-04-20T07:35:59.807 に答える
1

どちらの実装クラスもある種の記述子であり、実際には何もしません。基本クラスが空の場合でも、この状況でクラス階層を使用する方がクリーンだと感じます(2つのサブクラスを持つ抽象基本クラス:EmailDeliveryDetailsとPostalDeliveryDetails)。EmailDeliveryDetailsDeliveryDetailであり、deliverメソッドを実装しない限りDeliveryの実装ではありません。

于 2012-04-20T07:58:13.287 に答える
1

以下に概説するようなダブルディスパッチアプローチは機能する可能性がありますが、このシナリオではやり過ぎかもしれません。私はあなたにいくつかのオプションを与えるためだけにそれについて言及します...

class DeliveryService {
    // base class doesn't handle anything
    process(EmailDelivery details) {}
    process(PostalDelivery details) {}    
}

class EmailDeliveryService extends DeliveryService {
    process(EmailDelivery details) { /* handle */ }
}

class PostalDeliveryService extends DeliveryService {
    process(PostalDelivery details) { /* handle */ }
}

interface DeliveryDetails {
    processWith(DeliveryService service);
}

class EmailDeliveryDetails implements DeliveryDetails {
    processWith(DeliveryService service) { service.process(this); }
}

// try all services (of unknown type) on the given details (also of unknown type)
List<DeliveryService> services = configureServices();
DeliveryDetails details = getDetails();
for (DeliverySerivce service : services) details.processWith(service);

新しいタイプのDeliveryDetails(おそらくありそうもない...)が追加された場合は、DeliveryServiceを更新(空のプロセスメソッドを追加)し、新しいタイプのDeliveryDetailsで実際に何かを実行する新しいタイプのDeliveryServiceを追加する必要があります。

オプションで、processメソッドとprocessWithメソッドは、詳細が処理されているかどうかを示すブール値を返すことができます。

この場合も、このアプローチはおそらく不必要に複雑ですが、最後に発生する不明な配信タイプの処理の問題に対処します。

于 2012-04-20T07:58:18.557 に答える