固い原則への堅実なガイド

  • link:/category/programming/ [プログラミング]

1. 前書き

このチュートリアルでは、*オブジェクト指向設計のソリッド原則*について説明します。
まず、ソフトウェアを設計する際に、それらが発生した理由とそれらを考慮する必要がある理由を調査することから始めます。 次に、ポイントを強調するために、いくつかのサンプルコードとともに各原則の概要を説明します。

2. 固い原則の理由

SOLIDの原則は、最初にRobert Cによって概念化されました。 Martinの2000年の論文で、_https://fi.ort.edu.uy/innovaportal/file/2032/1/design_principles.pdf [デザインの原則とデザインパターン]。 SOLIDの頭字語に私たちを。 そして、過去20年間で、これら5つの原則はオブジェクト指向プログラミングの世界に革命をもたらし、ソフトウェアの作成方法を変えました。
それで、SOLIDとは何ですか?より良いコードを書くのにどのように役立ちますか? 簡単に言うと、MartinとFeathersの設計原則により、保守性が高く、理解しやすく、柔軟なソフトウェアを作成することができます。 その結果、*アプリケーションのサイズが大きくなるにつれて、アプリケーションの複雑さを軽減*し、将来の頭痛の種を大幅に減らすことができます!
次の5つの概念が、SOLIDの原則を構成しています。
  1. S 単一の責任

  2. O pen / Closed

  3. L iskov置換

  4. **インターフェース分離

  5. **依存関係の反転

    これらの言葉のいくつかは気が遠くなるかもしれませんが、いくつかの簡単なコード例で簡単に理解できます。 次のセクションでは、これらの各原則の意味を深く掘り下げ、それぞれを説明する簡単なJavaの例を示します。

3. 単一の責任

単一の責任原則で物事を始めましょう。 予想されるように、この原則は、*クラスには1つの責任しか持たせないことを示しています。 さらに、変更する理由は1つだけです。*
*この原則は、より良いソフトウェアの構築にどのように役立ちますか?*その利点のいくつかを見てみましょう。
  1. テスト – 1つの責任を持つクラスのテストははるかに少なくなります
    事件

  2. 低カップリング –単一のクラスの機能が少ない
    依存関係が少ない

  3. 組織 –小さく、よく組織されたクラスは検索が簡単です
    モノリシックなものより

    たとえば、簡単な本を表すクラスを考えてみましょう。
public class Book {

    private String name;
    private String author;
    private String text;

    //constructor, getters and setters
}
このコードでは、_Book_のインスタンスに関連付けられた名前、作成者、およびテキストを保存します。
テキストをクエリするメソッドをいくつか追加しましょう。
public class Book {

    private String name;
    private String author;
    private String text;

    //constructor, getters and setters

    // methods that directly relate to the book properties
    public String replaceWordInText(String word){
        return text.replaceAll(word, text);
    }

    public boolean isWordInText(String word){
        return text.contains(word);
    }
}
これで、_Book_クラスが正常に機能し、アプリケーションに好きなだけ本を保存できます。 しかし、テキストをコンソールに出力して読むことができない場合、情報を保存することは何が良いでしょうか?
風に注意して、印刷メソッドを追加しましょう。
public class Book {
    //...

    void printTextToConsole(){
        // our code for formatting and printing the text
    }
}
ただし、このコードは、前述の単一の責任原則に違反しています。 混乱を修正するには、テキストの印刷のみに関係する別のクラスを実装する必要があります。
public class BookPrinter {

    // methods for outputting text
    void printTextToConsole(String text){
        //our code for formatting and printing the text
    }

    void printTextToAnotherMedium(String text){
        // code for writing to any other location..
    }
}
驚くばかり。 印刷の義務を軽減するクラスを開発しただけでなく、テキストを他のメディアに送信するために__BookPrinter ___クラスを活用することもできます。
電子メール、ロギング、その他のいずれであっても、この1つの懸念に特化した別のクラスがあります。

4. 拡張用にオープン、変更用にクローズ

さて、「O」の時間です。正式には* open-closed原則*として知られています。 簡単に言えば、*クラスは拡張のために開かれ、修正のために閉じられる必要があります。* *そうすることで、*既存のコードを修正し、そうでなければ幸せなアプリケーションで潜在的な新しいバグを引き起こすことを止めます。
もちろん、*ルールの1つの例外は、既存のコードのバグを修正するときです*。
簡単なコード例を使用して、この概念をさらに詳しく見てみましょう。 新しいプロジェクトの一部として、__Guitar ___classを実装したと想像してください。
それは完全に本格的であり、ボリュームノブさえ持っています:
public class Guitar {

    private String make;
    private String model;
    private int volume;

    //Constructors, getters & setters
}
アプリケーションを起動すると、誰もが気に入っています。 しかし、数か月後、__Guitar __は少し退屈だと判断し、素晴らしい炎のパターンでそれをもう少し「ロックンロール」に見せることができました。
この時点で、__Guitar ___classを開いてフレームパターンを追加するだけの誘惑に駆られるかもしれませんが、アプリケーションで発生する可能性のあるエラーを誰が知っているのでしょうか。
代わりに、オープンクローズの原則に固執し、単に__Guitar __class *を拡張しましょう:
public class SuperCoolGuitarWithFlames extends Guitar {

    private String flameColor;

    //constructor, getters + setters
}
__Guitar ___classを拡張することにより、既存のアプリケーションが影響を受けないことを確認できます。

5. リスコフ置換

リストの次はリスコフ置換です。これはおそらく5つの原則の中で最も複雑なものです。 簡単に言えば、*クラス_A_がクラス_B_のサブタイプである場合、プログラムの動作を中断することなく、__B ___で__A __を置き換えることができるはずです。*
この概念を理解するために、コードに直接ジャンプしましょう。
public interface Car {

    void turnOnEngine();
    void accelerate();
}
上記では、すべての車が満たすべき2つのメソッドを備えた単純な__Car ___interfaceを定義しています。エンジンをオンにし、加速します。
インターフェースを実装して、メソッドにいくつかのコードを提供しましょう。
public class MotorCar implements Car {

    private Engine engine;

    //Constructors, getters + setters

    public void turnOnEngine() {
        //turn on the engine!
        engine.on();
    }

    public void accelerate() {
        //move forward!
        engine.powerOn(1000);
    }
}
コードで説明しているように、オンにできるエンジンがあり、出力を上げることができます。 しかし、待ってください、その2019、そしてElon Muskは忙しい人です。
私たちは今、電気自動車の時代に生きています。
public class ElectricCar implements Car {

    public void turnOnEngine() {
        throw new AssertionError("I don't have an engine!");
    }

    public void accelerate() {
        //this acceleration is crazy!
    }
}
エンジンのない車をミックスに投入することで、プログラムの動作を本質的に変更しています。 これは*リスコフ置換の露骨な違反であり、以前の2つの原則*よりも修正が少し難しい*です。
可能な解決策の1つは、_Car_のエンジンのない状態を考慮したインターフェイスにモデルを作り直すことです。

6. インターフェース分離

SOLIDの「I」はインターフェースの分離を表し、*より大きなインターフェースをより小さなインターフェースに分割することを意味します。 そうすることで、実装するクラスが関心のあるメソッドのみを考慮する必要があることを保証できます。*
この例では、動物園管理者として手を試します。 より具体的には、クマの囲いで作業します。
クマの番人としての役割の概要を説明するインターフェースから始めましょう。
public interface BearKeeper {
    void washTheBear();
    void feedTheBear();
    void petTheBear();
}
熱心な動物園管理者として、私たちは愛するクマを洗って食べさせてくれます。 しかし、私たちはそれらを愛petすることの危険性を認識しています。 残念ながら、私たちのインターフェースはかなり大きく、クマをかわいがるコードを実装する以外に選択肢はありません。
*これを修正するには、大きなインターフェイスを3つの個別のインターフェイスに分割します。*
public interface BearCleaner {
    void washTheBear();
}

public interface BearFeeder {
    void feedTheBear();
}

public interface BearPetter {
    void petTheBear();
}
インターフェースの分離のおかげで、重要なメソッドのみを自由に実装できます。
public class BearCarer implements BearCleaner, BearFeeder {

    public void washTheBear() {
        //I think we missed a spot...
    }

    public void feedTheBear() {
        //Tuna Tuesdays...
    }
}
そして最後に、私たちは危険なものを狂った人々に任せることができます:
public class CrazyPerson implements BearPetter {

    public void petTheBear() {
        //Good luck with that!
    }
}
さらに進んで、先ほどの例から__link:#s [BookPrinter] __classを分割して、同じ方法でインターフェースの分離を使用することもできます。 単一の__print ___methodで_Printer_インターフェイスを実装することにより、__ConsoleBookPrinter __and __OtherMediaBookPrinter __classesを個別にインスタンス化できます。

7. 依存関係反転

*依存性反転の原理は、ソフトウェアモジュールの分離を指します。 この方法では、低レベルモジュールに依存する高レベルモジュールの代わりに、両方が抽象化に依存します。*
これを実証するために、古い学校に行って、コード付きのWindows 98コンピューターを実現しましょう。
public class Windows98Machine {}
しかし、モニターとキーボードのないコンピューターは何が良いのでしょうか? コンストラクターにそれぞれ1つ追加して、すべての__Windows98Computer __weのインスタンス化が__Monitor __と_StandardKeyboard_で事前にパックされるようにします。
public class Windows98Machine {

    private final StandardKeyboard keyboard;
    private final Monitor monitor;

    public Windows98Machine() {
        monitor = new Monitor();
        keyboard = new StandardKeyboard();
    }

}
このコードは機能し、__Windows98Computer __class内で_StandardKeyboard_と_Monitor_を自由に使用できます。 問題が解決しました? かなりありません。 * __ StandardKeyboard ___and __Monitor __を___new __keywordで宣言することにより、これら3つのクラスを緊密に結合しました。*
これにより、__Windows98Computer ___がテストしにくくなるだけでなく、必要に応じて__StandardKeyboard ___classを別のものに切り替える機能も失われます。 そして、_Monitor_クラスにも固執しています。
より一般的な_Keyboard_インターフェイスを追加し、クラスでこれを使用して、マシンを_StandardKeyboard_から切り離しましょう。
public interface Keyboard { }
public class Windows98Machine{

    private final Keyboard keyboard;
    private final Monitor monitor;

    public Windows98Machine(Keyboard keyboard, Monitor monitor) {
        this.keyboard = keyboard;
        this.monitor = monitor;
    }
}
ここでは、_Keyboard_依存関係を_Windows98Machine_クラスに追加しやすくするために、ここで依存性注入パターンを使用しています。
また、_StandardKeyboard_クラスを変更して_Keyboard_インターフェイスを実装し、_Windows98Machine_クラスへの挿入に適したものにします。
public class StandardKeyboard implements Keyboard { }
これで、クラスは分離され、_Keyboard_抽象化を通じて通信します。 必要に応じて、インターフェイスの異なる実装を使用して、マシンのキーボードのタイプを簡単に切り替えることができます。 _Monitor_クラスについても同じ原則に従うことができます。
優秀な! 依存関係を切り離し、__Windows98Machine ___を選択したテストフレームワークで自由にテストできます。

8. 結論

このチュートリアルでは、*オブジェクト指向設計のSOLIDの原則を深く掘り下げました*。
  • SOLIDの歴史と、これらの原則が存在する理由について簡単に説明しました。*

    文字ごとに、**各原則の意味を、それを違反する簡単なコード例で分解しました。 次に、コードを修正する方法**を見て、SOLIDの原則に準拠するようにしました。
    いつものように、コードはhttps://github.com/eugenp/tutorials/tree/master/patterns/solid[GitHub]で入手できます。