Java SOLID

34764 ワード

📰Java-SOLID


まず、SOLIDの原則とは何かを理解させていただきます。


純粋なJavaで簡単にOOPを練習した結果、SOILDの原則さえ分からないことに気づき、自分を振り返り、まとめ始めました。

  • SRP:単一責任原則(いずれかを変更する理由は一つのみ)
  • OCP:開放封鎖原則(自己の拡張開放、周囲の変化閉鎖)
  • LSP:リスコフ置換原則(サブタイプは常に自分の加逆タイプで置き換えられる必要がある)
  • ISP:インターフェース分離の原則(クライアントが使用しない方法に依存できない)
  • DIP:依存逆転原則(自分より変わりやすいものに頼らない)
  • 従来の高重合度と低結合度の原則をオブジェクト向けの観点から再定義した。


    結合度:クラス間の相互依存性、結合度が低い場合の再使用やメンテナンスが容易
    凝集度:モジュール内部に存在する機能、高モジュールはbook mに集中し、独立性が高く、再利用や機能の修正、メンテナンスが容易である.
    すなわち,実体原則にうまく溶け込むことができるプログラムは再構築とメンテナンスが容易で,理解が容易であり,実体原則はオブジェクト向けの四大原則(多形性,継承,抽象,パッケージ)を基礎とし,設計モデルの骨格である.

    1、SRP-単一責任原則(類は単一責任を負う)


    アルバイトがあまりいない売り場と仮定して、ハンバーガー王を例に挙げます.
    アルバイトはテーブルを片付けて、肉餅を熟知して、計算して、食材を整理しなければなりません.
    インバースアクティビティが不明になると、小さなエラーでもシステム全体の移動と管理効率が低下します.
    下図に示すように、Workerクラスは複数の機能を担当し、売り場全体を担当します.ここでfood街エラーが発生したとします
    関連する方法はcook()やserve()であり,数百の方法であれば答えは消えてしまう.
    class Worker{
    
    User(){
    this.food=null;
    }
    
    
    //Method
        counter()
    
        cook()
    
        design()
    
        cleaning()
    
        serve()
    
          
    
    }
    そのため、注文責任者、調理責任者、店頭管理者、顧客管理者など各階層は自分の活動だけを担当しなければならない.
    class Counter{
    
        constructor(name=''){
    
       this.name = name;
    
       this.payment = [card, check, coupon]
    
        }
    
        orderCheck(){ }
    
    } 
    
    
    class Cooker{
    
        constructor(name){
    
            this.name = name;
    
            this.orderMenu = [] 
    
        }
    
        cook(){ }
    
    }
    

    2.OCP-開放封鎖原則(自己拡張開放、周辺変化閉鎖)


    例えばcafeというアプリがあります
    喫茶店はコーヒーを製造、販売、宣伝できるはずだ.

    =>展開で開く


    メニューが変わってもコーヒーは作れるはずで、セールスマンが変わっても販売できるはずです.

    =>変更がオフになっています


    まず、OCPが守らないコードを見てみましょう.
    カフェAはアメニティとミントチョコレートを作っています.
    次のコードに問題がありますが、異常はありません.
    しかし、異なるカフェで新しいメニューを追加すると、makeCoffeメソッドのif文が異なり、果てしなく長くなります.
    無効なコードになり、変化の中で閉じられないコードになります
    public class Cafe_A {
        
        
        void makeCoffee(String name){
            if(name=="Americano"){
                System.out.println("아메리카노를 만들어요");
            }
            else if(name=="Latte"){
                System.out.println("라떼를 만들어요");
            }
            else{
                System.out.println("메뉴를 말해주세요");
            }
        }
    }
    public class Cafe_B {
    
    
    
        void makeCoffee(String name){
            if(name=="Americano"){
                System.out.println("아메리카노를 만들어요");
            }
            else if(name=="mint choco "){
                System.out.println("민트초코를 만들어요");
    
            }
            else{
                System.out.println("메뉴를 말해주세요");
            }
        }
    }
    
    
    public class CafeManeger {
    
        public static void main(String[] args) {
    
            Cafe_A cafeA = new Cafe_A();
            Cafe_B cafeB = new Cafe_B();
    
            cafeA.makeCoffee("Latte");
            cafeB.makeCoffee("mint choco");
    
    
    
    
        }
    }
    
    では、コードを閉じて変化に適応するにはどうすればいいのでしょうか.インタフェースを使うことです
    通常カフェでコーヒーを作るのは共通点なので、この部分はインタフェース(makeCoffee)として使えます
    インタフェースを継承するカフェA、カフェBは、カフェマネージャーから製造するコーヒーの名前をもらい、コーヒーを作る.
    これにより、ifゲートで処理しなくても、いつでもご希望のコーヒーを作ることができます.
    再整理後、インタフェースを使用して変更を行った場合、デフォルトの構造は変更されず、変更は閉じたままになります.
    public interface Cafe_home {
    
        void makeCoffe();
    }
    
    public class Cafe_A implements Cafe_home {
    
        private String name="";
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public void makeCoffe() {
            System.out.println(name+"커피를 만듭니다");
        }
    }
    public class Cafe_B implements  Cafe_home{
    
    
    
        private String name="";
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public void makeCoffe() {
            System.out.println(name+"커피를 만듭니다");
        }
    }
    public class CafeManeger {
    
        public static void main(String[] args) {
    
            Cafe_A cafeA = new Cafe_A();
            Cafe_B cafeB = new Cafe_B();
    
            cafeA.setName("Latte");
            cafeA.makeCoffe();
    
            cafeB.setName("Dutch");
            cafeB.makeCoffe();
    
    
    
    
        }
    }
    

    3.LSP-(サブタイプは常に自己の反転タイプに置き換える必要があります)


    LSPを理解するには、一般化関係(一般化は関係と呼ばれる)を考慮しなければならない.
    例えば、サルは哺乳類であり、サルと哺乳類の間には、サルisは哺乳類関係が成立しなければならない.

  • 哺乳類は産卵せず、産卵によって繁殖する.

  • 哺乳類は赤ん坊を授乳し,肺呼吸をする

  • 哺乳類は体温が一定の恒温動物で、毛や厚い皮膚で覆われている.
  • これは哺乳類の様々な特徴を説明しており、サルにも適用できるかどうかを知りたい場合は、それを入れることができます.
    つまりサルと哺乳類は行動(繁殖、養育、呼吸、体温調節、皮膚保護)が一致している.
    もう1つの例はアヒル口獣が哺乳類で唯一産卵繁殖しているが、アヒル口獣は適用されない(産卵のため)
    つまり上の関係と合わない.
    オブジェクト向けLSPを満たす場合は、親のインスタンスを子のインスタンスに置き換える必要があります.
    動物の鳴き声と食事の共通の動作をインタフェースにバインドし、それぞれ猫や犬類を作成してインタフェースを呼び出し、値を入れます.
    public interface Animal {
    
    
        void makeSoudn();
    
        void eatFood();
    
    }
    public class Cat implements  Animal{
    
    
        @Override
        public void makeSoudn() {
            System.out.println("냐옹~");
        }
    
        @Override
        public void eatFood() {
            System.out.println("냠냠냠~");
        }
    }
    public class Dog implements Animal{
    
    
        @Override
        public void makeSoudn() {
            System.out.println("멍멍멍~");
        }
    
        @Override
        public void eatFood() {
            System.out.println("강아지가 밥을 먹는다");
        }
    }
    import java.lang.annotation.Documented;
    
    public class House {
    
    
        public static void main(String[] args) {
    
            Cat c =new Cat();
    
            c.makeSoudn();
            c.makeSoudn();
            c.makeSoudn();
    
            c.eatFood();
        }
    }
    Animal Hwanに変更してもエラーは発生しません.AnimalはCatの親であるためです(LSPの原則に違反しません)
    import java.lang.annotation.Documented;
    
    public class House {
    
    
        public static void main(String[] args) {
    
            Animal hwan =new Cat();
    
            c.makeSoudn();
            c.makeSoudn();
            c.makeSoudn();
    
            c.eatFood();
        }
    }

    4.ISP:インターフェース分離の原則(クライアントは自分が使用しない方法に依存すべきではない)


    インタフェースは、インタフェースを使用するクライアントから分離する必要があります.すなわち、インタフェースは、クライアントの観点からのみ使用できる機能を提供するように分離する必要があります.
    これにより、1つの機能への変更の影響を最小限に抑えることができます.
    例えば、自動車インタフェースに分離する->運転インタフェース、メンテナンスインタフェース
    (このような分離は、修理インタフェースが変化しても運転手のクライアントに影響しません)

    5.DIP:依存逆転の原則(自分より変わりやすいものに頼らない)


    低レベルモジュールは高レベルモジュールに依存できない
    コードを見てみましょう
    次のコードで異なるPayServiceを作成する必要がある場合は、各メソッドを処理する方法を作成する必要があります.
    これは、コードの作成効率が低すぎて、後で1つのクラスですべてのメソッドを管理するため、効果があまりよくありません.
    public class SamsungPay {
        
        String payment(){
            return "삼성";
            
        }
    }
    public class PayService {
    
        private SamsungPay pay;
    
        void setPay(final SamsungPay pay){
    
            this.pay=pay;
        }
    
        String pay(){
            return pay.payment();
        }
    
    }
    インタフェースを使用してこの問題を解決
    次に、既存のPayService()を再パッケージします.これにより、次のようにPayを選択できます.
    public class PayService {
    
        private Pay pay;
        
        void setPay(final Pay pay){
            this.pay=pay;
        }
        
        String payment(){
            return pay.payment();
        }
    
    }
    
    
    
    
    public interface Pay {
    
        String payment();
    }
    
    public class Carrot implements Pay{
        @Override
        public String payment() {
            return "당근마켓";
        }
    }
    public class KaKaoPay implements Pay {
    
    
        @Override
        public String payment() {
            return "카카오";
        }
    }
    public class SamsunPay implements Pay{
        @Override
        public String payment() {
            return "삼성";
        }
    }