7

Open-Closed Principleは次のように述べています。

ソフトウェア エンティティ (クラス、モジュール、関数など) は、拡張用に開いている必要がありますが、変更用に閉じている必要があります。

現在、ドメインを設計しており、ドメイン エンティティにかなりの動作を含めています。私はドメイン イベントを使用し、依存関係をメソッドに注入しているので、エンティティが外部の影響に結合されていないことを確認してください。ただし、クライアントが後でより多くの機能を必要とする場合は、OCP に違反し、これらのドメイン エンティティをクラックして機能を追加する必要があります。振る舞いが豊富なドメイン エンティティは、どうすればオープン クローズド プリンシプルと調和して生活できるでしょうか?

4

5 に答える 5

5

クラスを設計するときは、オープンクローズ原則(OCP)を念頭に置いておくと便利ですが、クラスをすぐに「変更のためにクローズ」にすることが常に実用的または望ましいとは限りません。単一責任の原則(SRP)は、実際にははるかに役立つと思います。クラスが1つのことだけを実行する限り、その1つのことの要件が変更された場合は変更してもかまいません。

さらに、SRPは時間の経過とともにOCPにつながります。クラスを頻繁に変更することに気付いた場合は、最終的にはクラスをリファクタリングして、変更部分が別のクラスに分離されるようにし、元のクラスをより閉じたものにします。

于 2012-09-04T03:12:41.280 に答える
0

簡単な答えは、次のことを確認することです。

  1. それはタスクを実行するだけでなく、問題が見つかった場合に後で変更する必要がないようにします。オープン/クローズの原則はバグをカバーしていませんが、変更のみです。
  2. それは交換を奨励します、すなわちそれは賢明なインターフェースを実装します。コンポーネントは、変更するのではなく、別のコンポーネントに切り替える必要があります。
  3. 構成可能にするには、変更される可能性のあるハードコードされたパラメーターを構成可能にする必要があります。
  4. 変更される可能性のあるコードを外部化し、制御の反転を使用して、変更が必要になる可能性のある機能をエクスポートします。

多くの場合、重量物を持ち上げるコンポーネントにオープン/クローズの原則を実装する方がはるかに簡単ですが、アプリケーションフロー制御とビジネスロジックが最大の変更を必要とすることが多いアプリケーションの一部に同じ原則を適用するのははるかに困難です。構成によってワークフローを実行するのが理想的です。

ドメイン駆動設計は、優れたプログラミング設計原則と一致させるのが難しい場合があります。DDDの重要な部分は、低レベルの概念をビジネスドメインから遠ざけ、ビジネスモデルをその言語で高レベルに保つための言語と理論的根拠です。ユーザーとビジネスの使用、別名「ユビキタス言語」は、ビジネスモデルのソフトウェア/技術的な汚染を防ぎます。

于 2012-09-08T01:07:03.927 に答える
0

これは、具体的な例がないと説明するのが難しいものです。Robert Martin の著書「Agile Software Development, Principles, Patterns, and Practices」を読むことをお勧めします。この本は、Open-Close 原則の由来でもあります。

豊富な動作を持つドメイン オブジェクトは、Open-Close 原則と競合しません。それらに動作がない場合、合理的な拡張機能を作成することはできません。オープンクローズの原則を適用する鍵は、将来の変化を予測し、役割を果たし、単一の責任を維持するための新しいインターフェイスを作成することです。

開閉原理を実際のコードに適用する話をします。うまくいけば、それは役に立ちます。

最初にメッセージを送信する Sender クラスがありました。

package com.thinkinginobjects;

public class MessageSender {

private Transport transport;

public void send(Message message) {
    byte[] bytes = message.toBytes();
    transport.sendBytes(bytes);
}
}

ある日、10 件のメッセージをまとめて送信するように依頼されました。簡単な解決策は次のとおりです。

パッケージcom.thinkinginobjects;

public class MessageSenderWithBatch {

private static final int BATCH_SIZE = 10;

private Transport transport;

private List<Message> buffer = new ArrayList<Message>();

public void send(Message message) {
    buffer.add(message);
    if (buffer.size() == BATCH_SIZE) {
        for (Message each : buffer) {
            byte[] bytes = each.toBytes();
            transport.sendBytes(bytes);
        }
                    buffer.clear();
    }
}
}

しかし、私の経験では、これで終わりではないかもしれないと教えてくれました。メッセージをバッチ処理する別の方法が必要になると予想しました。したがって、バッチ戦略を作成し、送信者にそれを使用させました。ここで開閉原理を適用していたことに注意してください。将来、新しい種類のバッチ戦略があった場合、私のコードは (新しい BatchStrategy を追加することによって) 拡張可能ですが、(既存のコードを変更しないことによって) 変更に近いものになります。ただし、Robert Martin が彼の著書で述べているように、コードがいくつかの種類の変更に対して開かれている場合、それは他の種類の変更にも近いものです。誰かが将来送信された後にコンポーネントに通知したい場合、私のコードはこの種の変更に対して開かれていません。

package com.thinkinginobjects;

public class MessageSenderWithStrategy {

private Transport transport;

private BatchStrategy strategy;

public void send(Message message) {
    strategy.newMessage(message);
    List<Message> messages = strategy.getMessagesToSend();

    for (Message each : messages) {
        byte[] bytes = each.toBytes();
        transport.sendBytes(bytes);
    }
    strategy.sent();
}
}

package com.thinkinginobjects;

public class FixSizeBatchStrategy implements BatchStrategy {

private static final int BATCH_SIZE = 0;
private List<Message> buffer = new ArrayList<Message>();

@Override
public void newMessage(Message message) {
    buffer.add(message);    
}

@Override
public List<Message> getMessagesToSend() {
    if (buffer.size() == BATCH_SIZE) {
        return buffer;
    } else {
        return Collections.emptyList();
    }
}

@Override
public void sent() {
    buffer.clear(); 
}

}

ストーリーを完成させるために、数日後、バッチメッセージを 5 秒ごとに送信するよう要求を受けました。私の推測は正しかったので、コードを変更する代わりに拡張機能を追加することで要件に対応できます。

package com.thinkinginobjects;

public class FixIntervalBatchStrategy implements BatchStrategy {

private static final long INTERVAL = 5000;

private List<Message> buffer = new ArrayList<Message>();

private volatile boolean readyToSend;

public FixIntervalBatchStrategy() {
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    executorService.scheduleAtFixedRate(new Runnable() {

        @Override
        public void run() {
            readyToSend = true;

        }
    }, 0, INTERVAL, TimeUnit.MILLISECONDS);
}

@Override
public void newMessage(Message message) {
    buffer.add(message);
}

@Override
public List<Message> getMessagesToSend() {
    if (readyToSend) {
        return buffer;
    } else {
        return Collections.emptyList();
    }
}

@Override
public void sent() {
    readyToSend = false;
    buffer.clear();
}
}
  • 免責事項: コード例は www.thinkingInObjects.com に属します
于 2012-09-08T00:38:02.387 に答える
0

答えは簡単です。ファクトリ メソッドとインターフェイス + コンポジションです。

拡張可能とは、新しいサブクラスを使用して新しい機能を追加することを意味します。

これを有効にするには、ファクトリを使用してドメイン オブジェクトを作成する必要があります。私は通常、リポジトリをファクトリとして使用します。

具象ではなくインターフェイスに対してコーディングすると、新しい機能を簡単に追加できます。

  • Iユーザー
  • IManager : IUser (いくつかのマネージャー機能を追加)
  • ISupervisor : IUser (スーパーバイザー機能を追加)

機能自体は、構成を使用して含める小さなクラスにすることができます。

public class ManagerSupervisor : User, IManager, ISupervior
(
    public ManagerSupervisor()
    {
        // used to work with the supervisor features.
        // without breaking Law Of Demeter
        _supervisor = new SuperVisorFeatures(this);

    }
)
于 2012-09-04T08:12:18.560 に答える
0

あなたはドメイン イベント パターンで正しい軌道に乗っています。

クラスをクラックする必要はありません。追加の条件付きイベント ハンドラーを追加するだけです。

ドメイン イベントとイベント ハンドラーの関係は 1 対多にすることができ、ドメイン イベントの引数の具体的な型に基づいてハンドラーを起動できます。

于 2012-09-08T00:50:29.350 に答える