デザインパターン#1(7つの主要な設計原則)

簡単な説明:単一のクラス、単一のメソッド、または単一のフレームワークは、特定の機能のみを完了します。

要件:テキストファイル内の単語の数を数えます。

反例:


public class nagtiveをコピーします{ public static void main(String [] args){ try { //读取文件的内容Reader in = new FileReader(“ E:\ 1.txt”); BufferedReader bufferedReader = new BufferedReader(in);




        String line = null;
        StringBuilder sb = new StringBuilder("");

        while((line =bufferedReader.readLine()) != null){
            sb.append(line);
            sb.append(" ");
        }

        //对内容进行分割
        String[] words = sb.toString().split("[^a-zA-Z]+");
        System.out.println(words.length);

        bufferedReader.close();

    } catch (IOException e) {
        e.printStackTrace();
    }
}

}
上記のコードは単一責任の原則に違反しています。同じ方法で、ファイルの読み取りとコンテンツのセグメント化を行わせています。デマンドの変更がある場合(ロードファイルを置き換える必要がある場合は、テキストファイルの文の数がカウントされます)、メソッド全体を書き直す必要があります。

正例:


public class postive {を コピーします

public static StringBuilder loadFile(String fileLocation) throws IOException {
  
        //读取文件的内容
        Reader in = new FileReader("E:\\1.txt");
        BufferedReader bufferedReader = new BufferedReader(in);

        String line = null;
        StringBuilder sb = new StringBuilder("");

        while ((line = bufferedReader.readLine()) != null) {
            sb.append(line);
            sb.append(" ");
        }
        
        bufferedReader.close();
        return sb;
}

public static String[] getWords(String regex, StringBuilder sb){
    //对内容进行分割
    return  sb.toString().split(regex);
}

public static void main(String[] args) throws IOException {
    
        //读取文件的内容
        StringBuilder sb = loadFile("E:\\1.txt");
        
        //对内容进行分割
        String[] words = getWords("[^a-zA-Z]+", sb);
        
        System.out.println(words.length);
}

}
単一の原則に従うことで、コードの再利用性が向上すると同時に、取得されたデータが結合されなくなり、個々のニーズを完了するために使用できるという利点があります。

開始と終了の原則#
簡単な説明:拡張機能に対してオープン(新しい関数)および変更に対してクローズ(古い関数)

エンティティークラスの設計:


パブリッククラスをコピーします{ プライベート文字列prod_name; プライベート文字列prod_origin; プライベートフロートprod_price;


public String getProd_name() {
    return prod_name;
}

public void setProd_name(String prod_name) {
    this.prod_name = prod_name;
}

public String getProd_origin() {
    return prod_origin;
}

public void setProd_origin(String prod_origin) {
    this.prod_origin = prod_origin;
}

public float getProd_price() {
    return prod_price;
}

public void setProd_price(float prod_price) {
    this.prod_price = prod_price;
}

@Override
public String toString() {
    return "Pen{" +
            "prod_name='" + prod_name + '\'' +
            ", prod_origin='" + prod_origin + '\'' +
            ", prod_price=" + prod_price +
            '}';
}

} public static void main(String [] args)を
コピー
{ //製品情報を入力Pen redPen = new Pen(); redPen.setProd_name( "Hero Brand Pen"); redPen.setProd_origin( "Factory"); redPen.setProd_price (15.5f); //製品情報を出力するSystem.out.println(redPen); } 需要:商品活動、20%割引販売。








反例:エンティティークラスのソースコードでsetProd_priceメソッドを変更します

コピー
public void setProd_price(float prod_price){ this.prod_price = prod_price * 0.8f; } は、開閉の原則に違反し、ソースコードを変更し、元の価格を表示する機能を変更します。


開発時には、変更される可能性のある要件を考慮する必要があります。属性は随時変更される可能性があります。要件の変更については、開閉の原則を遵守することを前提に、開発中に拡張する必要があります。ソースコードを変更しないこと。

正例:

コピー
パブリッククラスdiscountPenペン延び{ //使用料金を設定するための書き換え方法@Override 公共ボイドsetProd_price(フロートprod_price){ super.setProd_price(prod_price * 0.8fを); } } コピーパブリッククラスポジティブ{ メインボイドのpublic static(文字列を[] args){ //製品情報を入力し、rewriteメソッドを呼び出して価格を設定しますredPen = new discountPen(); redPen.setProd_name( "Hero Brand Pen"); redPen.setProd_origin( "Factory"); redPen.setProd_price( 15.5f); //製品情報を出力するSystem.out.println(redPen); } } 開閉の原則を盲目的に遵守する必要はありません。開発シナリオと組み合わせて使用​​する必要があります。ソースコードを変更する必要がある場合は、自分で変更しますもちろん、後で簡単に要件を満たすことができます。ただし、ソースコードが他の誰かまたは他の誰かの構造によって記述されている場合、変更のリスクが非常に高くなります。現時点では、構造の整合性への損傷を防ぐために、開閉の原則を遵守する必要があります。


















インターフェース分離の原則#
簡単な説明:インターフェースを設計する場合、インターフェースの抽象化には特定の意味が必要です。設計する必要があるのは、単一の責任を持つまとまりのあるインターフェースです。「単一の一般的なインターフェースよりも、複数の特殊なインターフェースを使用するほうがよい」この原則は、「普遍的な」意味を持つインターフェースの設計を推奨するものではありません。

反例:すべての動物が動物インターフェースに必要なわけではありません。


パブリックインターフェイスをコピーします。動物{ void eat(); void fiy(); //ドジョウ:飛んでいますか?void swim(); // Da Diao:ここで泳いでいますか?} コピークラスBirdはAnimalを実装します{ @Override public void eat(){ System.out.println( "Eat with your mouth"); }









@Override
public void fiy() {
    System.out.println("用翅膀飞");
}

@Override
public void swim() {
//我是大雕不会游泳
}

}
インタフェースでの水泳()メソッドは、実際の開発では、このクラスには適していません。

良い例:インターフェースは同じレベルの特定の意味を抽象化し、それを実現するために必要なクラスに提供します。


公開インターフェースFly { void fly();をコピーします }

公開インターフェースEat { void eat(); }

公開インターフェースSwim { void swim(); } パブリッククラスのコピーBird_02はFly、Eat { @Override public void eat(){ System.out.println(“用嘴巴吃”);を実装しています。}







@Override
public void fly() {
    System.out.println("用翅膀飞");
}

//我是大雕不会游泳

}
クライアントが必要としないインターフェースでクライアントが依存するメソッドがあってはなりません。

インターフェースが大きすぎてこれが発生しない場合は、このインターフェースを分割する必要があります。インターフェースを使用するクライアントは、使用する必要があるインターフェースとインターフェース内のメソッドのみを知っている必要があります。

依存関係の逆転の原理#
簡単な説明:上位層は下位層に依存できません。すべて抽象化に依存する必要があります。

需要:人間の摂食動物

反例:


public class negtiveをコピーします{

static class Person {
    public void feed(Dog dog){
        dog.eat();
    }
}

static class Dog {
    public void eat() {
        System.out.println("主人喂我了。汪汪汪...");
    }
}

public static void main(String[] args) {
    Person person= new Person();
    Dog dog = new Dog();
    person.feed(dog);
}

}
image-20200912204644913

このとき、Person内のフィードメソッドはDogに依存しています。これは、上位メソッドの下位レイヤーに依存するクラスです。(男は犬に依存していますか?これは呪いですか?)

需要の変化があると、人間のペットは犬だけでなく猫などにもなります。このとき、上流階級を変更する必要があるため、再利用性の問題が発生し、前述の開閉の原則にも違反します。

正例:

image-20200912204707141

コピー
パブリッククラスポジティブを{ 静的クラス人{ 公共ボイド飼料(動物動物){ animal.eat()。} }




interface Animal{
    public void eat();
}

static class Dog implements Animal{
    public void eat() {
        System.out.println("我是狗狗,主人喂我了。汪汪汪...");
    }
}

static class Cat implements Animal{
    public void eat() {
        System.out.println("我是猫咪,主人也喂我了。(我为什么要说也?)喵喵喵...");
    }
}

public static void main(String[] args) {
    Person person= new Person();
    Dog dog = new Dog();
    Cat cat = new Cat();
    person.feed(dog);
    person.feed(cat);
}

}現時点
では、Person内のフィードメソッドはDogまたはCatに依存しなくなりましたが、Person、Dog、Catのいずれであっても、それらはすべてAnimalの抽象クラスに依存し、すべて抽象クラスに依存しています。

このとき、以前の上位のコードも下位のコードも需要に応じて変化しません。

依存関係の逆転の原則は、コードが具象クラスではなく抽象クラスに依存する必要があることを意味します。具象クラスではなく、インターフェイスまたは抽象クラス用にプログラムする必要があります。インターフェース指向プログラミングを通じて、抽象化は詳細に依存するべきではなく、詳細は抽象化に依存するべきです。

ディミットのルール(最小
認識原理)#簡単な説明:クラスが他のクラスについて知っている数が少ないほど、オブジェクトは他のオブジェクトについてできる限り少ない知識を持ち、友達とのみ通信し、見知らぬ人と話さないようにする必要があります。

反例:

コピー
パブリッククラスnegtive { クラスコンピュータ{ ます。public void CLOSEFILE(){ ; System.out.printlnは( "閉じるファイル")} ます。public void closeScreen(){ System.out.printlnは( "閉じる画面"); } ます。public void電源オフ( ){ System.out.println( "Power off"); } }










class Person{
    private Computer computer;

    public void offComputer(){
        computer.closeFile();
        computer.closeScreen();
        computer.powerOff();
    }
}

}現時点で
、Personはコンピュータの多くの詳細を知っています。これはユーザーにとって十分に親切ではありません。さらに、ユーザーはエラーを呼び出し、電源をオフにしてからファイルを保存する場合があります。明らかに、これは論理的ではなく、ファイルに保存されていないエラーが発生します。

実際、ユーザーにとっては、シャットダウンすることを知っていれば十分です。

良い例:パッケージの詳細

コピー
public class postive { class Computer { public void closeFile(){ System.out.println( "Close the file"); } public void closeScreen(){ System.out.println( "Close the screen"); } public void powerOff( ){ System.out.println( "Power off"); }









    public void turnOff(){  //封装细节
        this.closeFile();
        this.closeScreen();
        this.powerOff();
    }
}

class Person{
    private Computer computer;

    public void offComputer(){
        computer.turnOff();
    }
}

}
としては、先に述べたように、友人だけではなく、見知らぬ人と通信します。最初に友達と呼ばれるものを明確にしましょう:

友達とは?
フィールドクラス
メソッドの戻り値
メソッドパラメータ
インスタンスオブジェクトメソッド
オブジェクト自体の
一般的な汎用セット。
一般的に、それ自体で定義されているのは友達であり、他のメソッドによって取得されるのは友達の友達です。

しかし、友達の友達は私の友達ではありません。

反例を示すには:


public class negtiveをコピーします{

 class Market{
    private  Computer computer;
    public Computer getComputer(){
        return this.computer;
    }
}

static class Computer{
    public  void  closeFile(){
        System.out.println("关闭文件");
    }
    public  void  closeScreen(){
        System.out.println("关闭屏幕");
    }
    public  void  powerOff(){
        System.out.println("断电");
    }
}

class Person{
    private Market market;

    Computer computer =market.getComputer(); 
    // //此时的 computer 并不是 Person 的朋友,只是 Market 的朋友。
}

}
実際の開発では、ディミットの法則を完全に遵守するために、不利な点があります。

システムに多数の小さなメソッドを作成します。これらのメソッドは、間接呼び出しを渡すためのものであり、システムのビジネスロジックとは何の関係もありません。

クラス間のディミットの法則に従うことは、システムのローカル設計を単純化することです。これは、各部分が遠くのオブジェクトに直接関連しないためです。ただし、これにより、システムの異なるモジュール間の通信効率も低下し、システムの異なるモジュール間の調整も困難になります。

したがって、前任者は参考のためにいくつかの方法論を要約しました。

クラスを不変クラスとして設定することを優先します。

クラスのアクセス権を減らすようにしてください。

注意してSerializableを使用してください。

メンバーのアクセス権を最小限に抑えます。

多くのルールがありますが、理論は深い理解を必要とし、実際の戦闘は経験を必要とします。道はまだ長いです。

リヒターの置き換えの原則#
簡単な説明:親オブジェクトを使用できる場所ならどこでも、透過的に子オブジェクトに置き換える必要があります。

要件:長方形の幅を長さよりも大きい1に変更します。

反例:親クラスのrectangularの下で、ビジネスシナリオは論理的です。既存のサブクラスSquare、置換後に何が起こるか。

コピー
パブリッククラスnegtive { 静的クラス長方形{ プライベート整数幅; プライベート整数長。


    public Integer getWidth() {
        return width;
    }

    public void setWidth(Integer width) {
        this.width = width;
    }

    public Integer getLength() {
        return length;
    }

    public void setLength(Integer length) {
        this.length = length;
    }

}

static class Square extends Rectangular {
    private Integer sideWidth;

    @Override
    public Integer getWidth() {
        return sideWidth;
    }

    @Override
    public void setWidth(Integer width) {
        this.sideWidth = width;
    }

    @Override
    public Integer getLength() {
        return sideWidth;
    }

    @Override
    public void setLength(Integer length) {
        this.sideWidth = length;
    }
}


static class Utils{
    public static void transform(Rectangular graph){
        while ( graph.getWidth() <= graph.getLength() ){
            graph.setWidth(graph.getWidth() + 1);
            System.out.println("长:"+graph.getLength()+" : " +
                    "宽:"+graph.getWidth());
        }
    }
}

public static void main(String[] args) {
// Rectangular graph = new Rectangular();
    Rectangular graph = new Square();
    graph.setWidth(20);
   graph.setLength(30);

    Utils.transform(graph);
}

}
交換後、操作は無限ループになります。

上への変換中、メソッド呼び出しは新しいオブジェクトにのみ関連しているため、異なる結果になります。使用シナリオでは、置換後にビジネスロジックが影響を受けるかどうかを検討する必要があります。

これにより、リヒター置換の原則を使用する際に考慮する必要がある条件が生じます。

is-a関係はありますか?
サブクラスは親クラスの関数を拡張できますが、親クラスの元の関数を変更することはできません。
このような反例はたくさんあります。たとえば、ダチョウは鳥ではなく、先祖が古くから言っていた春、秋、戦国の時代、バイマフェイマは言っています。それらはすべて同じ真実です。

組み合わせは継承よりも優れています#
簡単な説明:他の人のコードを再利用する場合、継承は使用せず、組み合わせを使用する必要があります。

要件:追加された要素の数を記録できる組み合わせを作成します。(特定の瞬間を数えるだけでなく)

反例 #1:

コピー
{publicクラスのnegtive_1を

static class MySet extends HashSet{
    private int count = 0;

    public int getCount() {
        return count;
    }

    @Override
    public boolean add(Object o) {
        count++;
        return super.add(o);
    }
}

public static void main(String[] args) {
    MySet mySet = new MySet();
    mySet.add("111111");
    mySet.add("22222222222222");
    mySet.add("2333");


    Set hashSet = new HashSet();
    hashSet.add("集合+11111");
    hashSet.add("集合+22222222");
    hashSet.add("集合+233333");
    mySet.addAll(hashSet);

    System.out.println(mySet.getCount());
}

}
需要を解決するようで、addメソッドは正常にカウントを追加でき、addAllメソッドはメソッドでaddを呼び出すことで正常にカウントを追加できます。

問題:今後JDKバージョンが更新されると、addAllメソッドはメソッド内でaddを呼び出さなくなり、コレクションに要素を追加するためにaddAllが呼び出されたときに、カウントを追加することは不可能になりません。需要も満たされません。

HashMapは1.6 1.7 1.8で3回更新されました。

反例 #2:


public class negtive_2 {を コピーします

static class MySet extends HashSet{
    private int count = 0;

    public int getCount() {
        return count;
    }

    @Override
    public boolean add(Object o) {
        count++;
        return super.add(o);
    }

    @Override
    public boolean addAll(Collection c) {
        boolean modified = false;
        for (Object e : c)
            if (add(e))
                modified = true;
        return modified;
    }
}

public static void main(String[] args) {
    MySet mySet = new MySet();
    mySet.add("111111");
    mySet.add("22222222222222");
    mySet.add("2333");


    Set hashSet = new HashSet();
    hashSet.add("集合+11111");
    hashSet.add("集合+22222222");
    hashSet.add("集合+233333");
    mySet.addAll(hashSet);

    System.out.println(mySet.getCount());
}

}
addAllメソッドを個人的に書き換えて、addAllメソッドをaddメソッドに呼び出せるようにして、カウントを増やすことができるようにします。

ただし、まだ問題があります。

欠陥:

将来、HashSetが新しいaddSomeメソッドを追加して要素を追加する場合、それは何もしません。
addAllメソッドとaddメソッドをオーバーライドします。JDKの他のクラスの一部のメソッドがHashMapのこれら2つのメソッドに依存している場合、HashMapのこれら2つのメソッドに依存するJDKの他のクラスの一部のメソッドはエラーやクラッシュなどのリスクがあります。
現時点では、いくつかの結論を引き出すことができます。

私たちが親クラスを継承する開発チームの一員ではない場合、親クラスのコードが変更されないことを保証したり、変更が行われたときに通知する必要があることを保証する方法はありません。現時点では、要件が満たされている状態で問題が発生する場合があります。したがって、親クラスのコードを再利用するときは、ソースコード構造の変更による影響を防ぐことができる、新しいメソッドの書き換えや作成を避けてください。

つまり、コードを再利用する場合、継承よりも構成の方が優れているはずです。

正例:


public class postive {を コピーします

class MySet{
    private HashSet hashSet;

    private int count = 0;

    public int getCount() {
        return count;
    }

    public boolean add(Object o) {
        count++;
        return hashSet.add(o);
    }

    public boolean addAll(Collection c) {
        count += c.size();
        return hashSet.addAll(c);
    }
}

public static void main(String[] args) {
    negtive_2.MySet mySet = new negtive_2.MySet();
    mySet.add("111111");
    mySet.add("22222222222222");
    mySet.add("2333");


    Set hashSet = new HashSet();
    hashSet.add("集合+11111");
    hashSet.add("集合+22222222");
    hashSet.add("集合+233333");

    mySet.addAll(hashSet);

    System.out.println(mySet.getCount());
}

}
アマゾン評価www.yisuping.com

おすすめ

転載: blog.csdn.net/weixin_45032957/article/details/108576507