JavaRush /Java Blog /Random-JA /Java のクラス設計 (SOLID) の 5 つの基本原則
Ve4niY
レベル 14

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

Random-JA グループに公開済み
クラスは、アプリケーションの構築元となるブロックです。ちょうど建物のレンガのように。授業が不十分に書かれていると、いつか問題が発生する可能性があります。 Java のクラス設計 (SOLID) の 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 to the database

        return true;
    }

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

        // Sending a letter to the client
    }
}
このようなモジュールは 3 つの理由で変更される可能性があります。第一に、注文処理ロジックが異なる場合があり、第二に、注文を保存する方法 (データベースの種類)、第三に、確認レターを送信する方法 (たとえば、電子メールの代わりに SMS を送信する必要がある) が異なります。単一責任原則は、この問題の 3 つの側面が実際には 3 つの異なる責任であることを意味します。これは、それらが異なるクラスまたはモジュールに存在する必要があることを意味します。さまざまなタイミングやさまざまな理由で変更される可能性がある複数のエンティティを組み合わせるのは、設計上の決定としては不適切であると考えられます。モジュールを 3 つの別個のモジュールに分割し、それぞれが 1 つの機能を実行するようにする方がはるかに優れています。
public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }
}

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

        // Sending a letter to the client
    }
}

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、それを拡張して、OCP 原則に違反することなく当面の問題の解決策を達成します。
public class OrderProcessorWithPreAndPostProcessing extends OrderProcessor {

    @Override
    public void process(Order order) {
        beforeProcessing();
        super.process(order);
        afterProcessing();
    }

    private void beforeProcessing() {
        // Perform some actions before processing the order
    }

    private void afterProcessing() {
        // Perform some actions after order processing
    }
}

バーバラ・リスコフ置換原則 (LSP)

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

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

        return true;
    }
}
また、一部の注文を別の方法で検証する必要があると仮定します。つまり、注文内のすべての商品の在庫があるかどうか、およびすべての商品が梱包されているかどうかを確認します。これを行うために、クラスをOrderStockValidatorclass で拡張しました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 を 返す代わりに、メソッドが例外をスローするため、LSP 原則に違反しましたIllegalStateException。このコードのクライアントはこれを期待していません。つまり、trueまたはfalseが返されることを期待しています。これにより、プログラムでエラーが発生する可能性があります。

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

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

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

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

}
この例では、私たちのクラスはOrderProcessor2 つの特定のクラスMySQLOrderRepositoryとに依存していますConfirmationEmailSender。これらのクラスのコードも示します。
public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }
}

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

        // Sending a letter to the client
    }
}
これらのクラスは抽象化とは程遠いものです。そして、DIP 原則の観点からすると、具体的な実装ではなく、将来的にそれらを使用できるようにするためのいくつかの抽象化を作成することから始める方が正しいでしょう。抽象化となる 2 つのインターフェイスMailSenderとを作成しましょう。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();

        // Sending a letter to the client
    }

}

public class MySQLOrderRepository implements OrderRepository {

    @Override
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to 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。SOLID - Java の設計原則を検討しました。OOP 全般、このプログラミング言語の基本について詳しくは、JavaRush コースで退屈ではなく何百時間も練習する必要があります。いくつかの問題を解決する時間です:) Java のクラス設計 (SOLID) の 5 つの基本原則 - 2
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION