CodeGym /Java Blog /ランダム /SOLID: Java のクラス設計の 5 つの基本原則
John Squirrels
レベル 41
San Francisco

SOLID: Java のクラス設計の 5 つの基本原則

ランダム グループに公開済み
クラスはアプリケーションの構成要素です。ちょうど建物のレンガのように。クラスの作成が不十分だと、最終的に問題が発生する可能性があります。 SOLID: Java のクラス設計の 5 つの基本原則 - 1クラスが適切に記述されているかどうかを理解するには、クラスが「品質基準」をどのように満たしているかを確認できます。Java では、これらはいわゆる SOLID 原則であり、これから説明します。

Java の SOLID 原則

SOLID は、OOP とクラス設計の最初の 5 つの原則の大文字から形成された頭字語です。この原則は 2000 年代初頭にロバート マーティンによって表現され、その後マイケル フェザーズによってこの略語が導入されました。SOLID の原則は次のとおりです。
  1. 単一責任の原則
  2. オープンクローズドの原則
  3. リスコフ置換原理
  4. インターフェース分離原理
  5. 依存関係逆転の原則

単一責任原則 (SRP)

この原則は、クラスを変更する理由は決して 1 つ以上あってはならないということです。 各オブジェクトには 1 つの責任があり、それはクラス内に完全にカプセル化されています。クラスのサービスはすべて、この責任をサポートすることを目的としています。このようなクラスは、そのクラスが何を担当し、何を担当しないのかが明確であるため、必要に応じて常に簡単に変更できます。言い換えれば、結果、つまり他のオブジェクトへの影響を恐れずに変更を加えることができるようになります。さらに、テストでは 1 つの機能を他のすべての機能から分離してカバーするため、このようなコードのテストははるかに簡単になります。注文を処理するモジュールを想像してください。注文が正しく形成された場合、このモジュールはそれをデータベースに保存し、注文を確認する電子メールを送信します。

public class OrderProcessor {

    public void process(Order order){
        if (order.isValid() && save(order)) {
            sendConfirmationEmail(order);
        }
    }

    private boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }

    private void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }
}
このモジュールは 3 つの理由で変更される可能性があります。まず、注文を処理するロジックが変更される可能性があります。次に、注文の保存方法 (データベースの種類) が変更される可能性があります。第三に、確認の送信方法が変更される可能性があります (たとえば、電子メールではなくテキスト メッセージを送信する必要があるとします)。単一責任の原則は、この問題の 3 つの側面が実際には 3 つの異なる責任であることを意味します。つまり、それらは異なるクラスまたはモジュールに存在する必要があります。さまざまなタイミングやさまざまな理由で変更される可能性がある複数のエンティティを組み合わせるのは、不適切な設計上の決定であると考えられます。モジュールを 3 つの個別のモジュールに分割し、それぞれが 1 つの機能を実行する方がはるかに優れています。

public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }
}

public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}

オープンクローズド原則(OCP)

この原則は次のように説明されています。ソフトウェア エンティティ (クラス、モジュール、関数など) は拡張に対してオープンである必要がありますが、変更に対してはクローズされている必要があります。これは、クラスの既存のコードを変更せずに、クラスの外部の動作を変更できる必要があることを意味します。この原則に従って、クラスを拡張して一部の関数をオーバーライドするだけで、特定の条件に合わせてクラスを調整できるようにクラスが設計されています。これは、システムが柔軟であり、ソース コードを変更せずに変化する条件でも動作できる必要があることを意味します。注文処理に関する例を続けて、注文が処理される前と確認メールの送信後にいくつかのアクションを実行する必要があるとします。変更する代わりに、OrderProcessorクラス自体を拡張して、オープンクローズの原則に違反することなく目的を達成します。

public class OrderProcessorWithPreAndPostProcessing extends OrderProcessor {

    @Override
    public void process(Order order) {
        beforeProcessing();
        super.process(order);
        afterProcessing();
    }
    
    private void beforeProcessing() {
        // Take some action before processing the order
    }
    
    private void afterProcessing() {
        // Take some action after processing the order
    }
}

リスコフ置換原理 (LSP)

これは、前述したオープンクローズ原則のバリエーションです。これは次のように定義できます。プログラムのプロパティを変更せずに、オブジェクトをサブクラスのオブジェクトに置き換えることができます。これは、基本クラスを拡張して作成されたクラスは、クライアントの観点から機能が損なわれないように、そのメソッドをオーバーライドする必要があることを意味します。つまり、開発者がクラスを拡張してアプリケーションで使用する場合、オーバーライドされたメソッドの予期される動作を変更してはなりません。クライアントの観点から機能が損なわれないように、サブクラスは基本クラスのメソッドをオーバーライドする必要があります。次の例でこれを詳しく調べます。注文を検証し、注文内のすべての商品の在庫があるかどうかを確認するクラスがあるとします。isValid()trueまたはfalseを返すメソッド:

public class OrderStockValidator {

    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if (!item.isInStock()) {
                return false;
            }
        }

        return true;
    }
}
また、一部の注文を他の注文とは異なる方法で検証する必要があるとします。たとえば、一部の注文については、注文内のすべての商品の在庫があるかどうか、およびすべての商品が梱包されているかどうかを確認する必要があるとします。OrderStockValidatorこれを行うには、クラスを作成してクラスを拡張しますOrderStockAndPackValidator

public class OrderStockAndPackValidator extends OrderStockValidator {

    @Override
    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if ( !item.isInStock() || !item.isPacked() ){
                throw new IllegalStateException(
                     String.format("Order %d is not valid!", order.getId())
                );
            }
        }

        return true;
    }
}
しかし、ここでは、注文が検証に失敗した場合にfalse を 返す代わりに、メソッドが をスローするため、リスコフ置換原則に違反していますIllegalStateExceptionこのコードを使用するクライアントはこれを予期しません。つまり、 trueまたはfalseの戻り値を予期します。これにより、実行時エラーが発生する可能性があります。

インターフェース分離原則 (ISP)

この原則は、次のステートメントによって特徴付けられます。クライアントは、使用しないメソッドの実装を強制されるべきではありません。インターフェイス分離の原則は、「厚すぎる」インターフェイスをより小さく、より具体的なインターフェイスに分割する必要があることを意味します。これにより、小さなインターフェイスを使用するクライアントは、作業に必要なメソッドのみを認識できるようになります。その結果、インターフェイス メソッドが変更されても、そのメソッドを使用しないクライアントは変更されるべきではありません。次の例を考えてみましょう。開発者のアレックスは、「レポート」インターフェイスを作成し、次の 2 つのメソッドを追加しましたgenerateExcel()generatedPdf()。現在、クライアントはこのインターフェイスの使用を希望していますが、レポートは Excel ではなく PDF 形式でのみ使用するつもりです。この機能はこのクライアントを満足させるでしょうか? いいえ、クライアントは 2 つのメソッドを実装する必要があります。そのうちの 1 つはほとんど必要なく、ソフトウェアを設計した Alex のおかげでのみ存在します。クライアントは、別のインターフェイスを使用するか、Excel レポートのメソッドを何も実行しません。それで、解決策は何ですか?それは、既存のインターフェースを 2 つの小さなインターフェースに分割することです。1 つは PDF レポート用、もう 1 つは Excel レポート用です。これにより、クライアントは必要な機能のみを使用できるようになります。

依存関係逆転の原則 (DIP)

Java では、この SOLID 原則は次のように説明されます。システム内の依存関係は抽象化に基づいて構築されます。。上位レベルのモジュールは下位レベルのモジュールに依存しません。抽象化は詳細に依存すべきではありません。詳細は抽象化に依存する必要があります。ソフトウェアは、さまざまなモジュールが自己完結型であり、抽象化を通じて相互に接続されるように設計する必要があります。この原則の古典的な応用例は Spring Framework です。Spring Framework では、すべてのモジュールが連携して動作する個別のコンポーネントとして実装されます。これらは非常に自律的であるため、Spring Framework 以外のプログラム モジュールでも同様に簡単に使用できます。これは、クローズド原則とオープン原則の依存性のおかげで達成されます。すべてのモジュールは、別のモジュールで使用できる抽象化へのアクセスのみを提供します。例を使用してこれを説明してみましょう。単一責任の原則について言えば、次のように考えました。OrderProcessorクラス。このクラスのコードをもう一度見てみましょう。

public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}
この例では、OrderProcessorクラスは 2 つの特定のクラス、MySQLOrderRepositoryおよびに依存しますConfirmationEmailSender。これらのクラスのコードも示します。

public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }
}
これらのクラスは、いわゆる抽象化とは程遠いものです。依存関係逆転の原則の観点からすると、具体的な実装ではなく、将来的に使用できるいくつかの抽象化を作成することから始める方がよいでしょう。MailSender2 つのインターフェイス (と)を作成しましょうOrderRepository。これらは私たちの抽象化になります:

public interface MailSender {
    void sendConfirmationEmail(Order order);
}

public interface OrderRepository {
    boolean save(Order order);
}
ここで、このためにすでに準備されているクラスにこれらのインターフェイスを実装します。

public class ConfirmationEmailSender implements MailSender {

    @Override
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }

}

public class MySQLOrderRepository implements OrderRepository {

    @Override
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }
}
OrderProcessor私たちは、クラスが具体的な詳細ではなく抽象化に依存する ように準備作業を行いました。依存関係をクラス コンストラクターに追加して変更します。

public class OrderProcessor {

    private MailSender mailSender;
    private OrderRepository repository;

    public OrderProcessor(MailSender mailSender, OrderRepository repository) {
        this.mailSender = mailSender;
        this.repository = repository;
    }

    public void process(Order order){
        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }
}
現在、私たちのクラスは特定の実装ではなく抽象化に依存しています。OrderProcessorオブジェクトの作成時に必要な依存関係を追加することで、その動作を簡単に変更できます。Java の SOLID 設計原則を検討してきました。CodeGym コースでは、OOP 一般と Java プログラミングの基本について詳しく学びます (退屈なことはなく、何百時間も練習できます)。いくつかのタスクを解決する時間です:)
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION