設計模式-六大原則詳解

  設計模式它是一種代碼編程長期發展的經驗和解決某種問題的通用的思想,而且中所周知的一套代碼方法和理念。也是咱們編寫程序的基石。我平常寫代碼就比如建造房子同樣,首先咱們須要搭建好架子。而後根據這個架子慢慢的將整個大廈建起來。一樣的,咱們在編寫程序的時候,也須要遵循必定的原則和框架,這樣咱們寫出來的程序才更見健壯,開發起來也會更加省時間,提升代碼的可讀性,拓展性,重用性,靈活性等,減小開發成本。設計模式原則其實就是程序員在編程時應當遵照的原則,也是設計模式的基礎,即設計模式爲何要這樣設計的緣由。java

  設計模式六大原則:程序員

  (一)、單一職責原則編程

  (二)、接口隔離原則設計模式

  (三)、依賴倒置原則框架

  (四)、里氏替換原則ide

  (五)、開閉原則函數

  (六)、迪米特法則工具

  下面我將用說明+代碼的方式,儘可能地將6大原則說明白。測試

  1、單一職責原則this

  對於一個類/接口/方式而言之負責一個職責或職能。好比說A類負責兩個不一樣的職責,職責1和職責2,當職責1發生需求變動而修改時,有可能會形成職責2執行錯誤,這是後須要將A類拆分爲A1和A2兩個。這樣作的有點:1.下降了類的複雜性。2.提升了類的可讀性,由於一個類只負責一個職責,看起來比較有目的性。3.提升系統的可維護性,下降了當需求變動時修改程序帶來的風險。可是,若是一位的最求單一職責原則,有時候可能會形成類爆炸的問題,因此使用時須要謹慎的看待這一點,不過,接口和方法必需要遵照這一原則。

  應用實例

  咱們已交通工具爲例,每種交通工具的出行方式都有可能不同,好比飛機是在天上飛的,汽車在公路上跑,輪船在水上航行。因此咱們能夠用如下方式來實現:

  方式1:

public class Test {

    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();

        vehicle.run("汽車");

        vehicle.run("飛機");

        vehicle.run("輪船");

    }

}

class Vehicle {
    public void run(String vehicle) {
        System.out.println(vehicle + " 在公路上跑");
    }
}


//運行結果

汽車 在公路上跑
飛機 在公路上跑

輪船 在公路上跑

   方式1中run方法既負責在公路上跑,也負責在天上飛,也能夠在水上航行,很顯然違反了單一職責原則。解決的方法:將每種出行方式單獨作成一個類,每一個類中都有各自的運行方式,因此咱們引出了方式2

  方式2:

public class SingleResponsible2 {

    public static void main(String[] args) {
        RoadVehicle roadVehicle = new RoadVehicle();
        roadVehicle.run("汽車");
        AirVehicle airVehicle = new AirVehicle();
        airVehicle.run("飛機");
        WaterVehicle waterVehicle = new WaterVehicle();
        waterVehicle.run("郵輪");
    }

}

class RoadVehicle{
    public void run(String vehicle) {
        System.out.println(vehicle + " 在公路上跑");
    }
}

class AirVehicle{
    public void run(String vehicle) {
        System.out.println(vehicle + " 在天上飛 ");
    }
}
class WaterVehicle{
    public void run(String vehicle) {
        System.out.println(vehicle + " 在水上航行 ");
    }
}

  方式2遵循了單一職責原則,每一個類的run方法都只負責各自的類型。可是,咱們能夠看到這樣作須要很大的改動,對於客戶端也須要改動很大。所以,咱們能夠考慮第三種方式,改動Vehicle類型。

  方式3:

  

 
 
public class SimgleResponsible3 {

public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.airRun("飛機");
vehicle.roadRun("汽車");
vehicle.waterRun("郵輪");
}

}

class Vehicle{
public void roadRun(String vehicle) {
System.out.println(vehicle + "在路上跑");
}
public void airRun(String vehicle) {
System.out.println(vehicle + " 在天空中飛");
}

public void waterRun(String vehicle) {
System.out.println(vehicle + " 水上航行 ");
}

}
 

  這種方式原來的類修改的地方比較少,只是在原來的基礎上添加的不一樣的方法。這種方式雖然在類型不遵循單一職責原則,可是在方式是遵循了這個原則的。

  

  2、接口隔離原則

  客戶端不該該依賴它不須要的接口;一個類對另外一個類的依賴應該創建在最小的接口上(Clients should not be forced to depend upon interfaces that they don’t use.)。

  類間的依賴關係應該創建在最小的接口上(The dependency of one class to another one should depend on the smallest possible interface)。

  問題:類A經過接口I依賴類B,類C經過接口I依賴類D,若是接口I對於類A和類B來講不是最小接口,則類B和類D必須去實現他們不須要的方法。接口I中有op1,op2,op3,op4和op5這5個方法,類B和類D都實現了接口I,可是類A只須要類B的op1,op2和op3這3個方法,類C只須要類D的op1,op4和op5這3個方法。

 

 

 

   在沒有遵循接口隔離原則,是這樣設計的:

public interface Interface1 {
    void op1();
    void op2();
    void op3();
    void op4();
    void op5();
}

  

public class A {

    private Interface1 interface1;

    public A(Interface1 interface1) {
        this.interface1 = interface1;
    }

    public void depend1() {
        interface1.op1();
    }
    public void depend2() {
        interface1.op2();
    }
    public void depend3() {
        interface1.op3();
    }
}

public class B implements Interface1 {
    @Override
    public void op1() {
        System.out.println("op1");
    }

    @Override
    public void op2() {
        System.out.println("op2");
    }

    @Override
    public void op3() {
        System.out.println("op3");
    }

    @Override
    public void op4() {

    }

    @Override
    public void op5() {

    }
}

public class C {

    public C(Interface1 interface1) {
        this.interface1 = interface1;
    }

    private Interface1 interface1;

    public void depend1() {
        interface1.op1();
    }
    public void depend4() {
        interface1.op4();
    }
    public void depend5() {
        interface1.op5();
    }

}

public class D implements Interface1 {
    @Override
    public void op1() {
        System.out.println("op1");
    }

    @Override
    public void op2() {
        System.out.println("op2");
    }

    @Override
    public void op3() {
        System.out.println("op3");
    }

    @Override
    public void op4() {
        System.out.println("op4");
    }

    @Override
    public void op5() {
        System.out.println("op5");
    }
}

  

public class Test {

    public static void main(String[] args) {
        Interface1 b = new B();

        Interface1 d = new D();

        A a = new A(b);
        C c = new C(d);
        a.depend1();
        a.depend2();
        a.depend3();

        c.depend1();
        c.depend4();
        c.depend5();

    }

}

  從上面的代碼中能夠看到,A類經過接口1依賴的B類,可是A類只用到了B的1,2和3的方法,4和5是無用的。一樣的,C類只用到了D類的1,4和5的方法。這樣就違背了接口隔離的原則。那麼接下來就使用接口隔離的原則改進上面代碼;

   分析:A類和C類都使用到了op1,所以咱們能夠將op1單獨抽獎成一個接口1,而後把A類依賴的op2和op3抽象成接口2,C類依賴的op4和op5抽象成接口3,而後B類實現接口1和接口2,D類實現接口1和接口3,A類依賴B類,C類依賴D類,這樣就能夠將不用的接口隔離開,代碼以下:

  

public interface Interface1 {

    void op1();

}

public interface Interface2 {
    void op2();
    void op3();
}


public interface Interface3 {
    void op4();
    void op5();
}

public class A {

    public void depend1(Interface1 interface1) {
        interface1.op1();
    }
    public void depend2(Interface2 interface2) {
        interface2.op2();
    }
    public void depend3(Interface2 interface2) {
        interface2.op3();
    }
}

public class B implements Interface1,Interface2 {
    @Override
    public void op1() {
        System.out.println("B 類實現了 Interface1");
    }

    @Override
    public void op2() {
        System.out.println("B 類實現了Interface2");
    }

    @Override
    public void op3() {
        System.out.println("B 類實現了Interface2");
    }
}


public class C {
    public void depend1(Interface1 interface1) {
        interface1.op1();
    }
    public void depend4(Interface3 interface3) {
        interface3.op4();
    }
    public void depend5(Interface3 interface3) {
        interface3.op5();
    }
}

public class D implements Interface1,Interface3 {
    @Override
    public void op1() {
        System.out.println("D 類實現了 Interface1");
    }

    @Override
    public void op4() {
        System.out.println("D 類實現了Interface3");
    }

    @Override
    public void op5() {
        System.out.println("D 類實現了Interface3");
    }
}

public class Test {

    public static void main(String[] args) {
        A a = new A();
        a.depend1(new B());
        a.depend2(new B());
        a.depend3(new B());


        C c = new C();
        D d = new D();
        c.depend1(d);
        c.depend4(d);
        c.depend5(d);
    }

}

  接口隔離使用注意事項:使用過程咱們須要注意把控接口 的細粒度,過分的最求接口隔離,會致使接口數量爆炸,系統中接口氾濫,不利於系統的維護。接口也不能太大,違背了接口隔離的原則,靈活性比較差。適度的控制接口的細粒度,不只讓接口變得更加靈活,並且可以下降系統的耦合度等

 

  3、依賴倒置原則

  高層模塊不該該依賴底層模塊,二者都應該依賴抽象,抽象不該依賴具體,具體應該要依賴於抽象,其核心思想就是面向接口編程。

  依賴關係傳遞的三種方式:

  一、接口傳遞;二、構造方法傳遞;三、setter方式傳遞

  應用案例:

  有一天小王的領導要小王對項目中的商品按照價格從高到低排序,小王接到任務後當即開始寫代碼了,並且很快就寫出來:

public class Sort {

    public void doSort() {
        System.out.println("sort by price");
    }

}
public class Item {

    public void sort() {
        new Sort().doSort();
    }

}
View Code
public class Client {

    public static void main(String[] args) {
        Item item = new Item();
        item.sort();
    }

}

  小王通過測試後,可以運行無誤,因而提交了代碼。可是,隨着需求的變動,領導要求小王添加按照銷量排序商品。小王接到任務後又開始敲代碼了:

public class SaleSort {

    public void doSort() {
        System.out.println("sort by sale amount");
    }

}

  

public class Item {

    public void sort(int type) {
        if (type == 0) {
            new Sort().doSort();
        }else if (type == 1) {
            new SaleSort().doSort();
        }
    }

}

  

public class Client {

    public static void main(String[] args) {
        Item item = new Item();
        item.sort(1);
    }

}

  小王經過加班加點後,終於實現了領導要求了,不只能夠按照價格排序,也能夠按照銷量來排序。忽然有一天小王的領導跟他說,如今要添加多一種排序方式,小王聽到後心裏是崩潰的,內心都有刪庫跑路的想法了。

  問題分析:小王的代碼中,每次要添加新的排序方式的時候,都須要修改商品類,而且也須要修改客戶端,因此修改量很大,在修改的過程當中很容易出現問題,影響其餘的功能。這種設計方式就是違反了依賴倒置的原則,高層模塊依賴低層模塊。

代碼改進:

public interface ISort {

    void doSort();

}

  

public class PriceSort implements ISort{

    @Override
    public void doSort() {
        System.out.println("sort by price");
    }

}

  

public class SaleSort implements ISort{

    @Override
    public void doSort() {
        System.out.println("sort by sale amount");
    }

}

  

public class Item {

    private ISort sort;

    public Item(ISort sort) {
        this.sort = sort;
    }

    public void sort() {
        sort.doSort();
    }

    public void setSort(ISort sort) {
        this.sort = sort;
    }
}

  

public class Client {

    public static void main(String[] args) {
        ISort sort = new PriceSort();

        Item item = new Item(sort);
        item.sort();

        sort = new SaleSort();
        item.setSort(sort);
        item.sort();

    }

}

  代碼分析:重構了代碼以後,商品來不在依賴具體的類,而是依賴了ISort接口,如今無論領導須要添加什麼樣的排序方式,只須要新增一個ISort的實現類就能夠了,而後在客戶端只須要簡單的修改一點能夠實現新增排序方式,對原來的代碼幾乎不用修改。重構後的代碼變得更具靈活性,可拓展性。

 

  4、里氏替換原則

  定義1:若是對每個類型爲 T1的對象 o1,都有類型爲 T2 的對象o2,使得以 T1定義的全部程序 P 在全部的對象 o1 都代換成 o2 時,程序 P 的行爲沒有發生變化,那麼類型 T2 是類型 T1 的子類型。
  定義2:全部引用基類的地方必須能透明地使用其子類的對象。

  繼承包含這樣一層含義:父類中凡是已經實現好的方法(相對於抽象方法而言),其實是在設定一系列的規範和契約,雖然它不強制要求全部的子類必須聽從這些契約,可是若是子類對這些非抽象方法任意修改,就會對整個繼承體系形成破壞。而里氏替換原則就是表達了這一層含義。
繼承做爲面向對象三大特性之一,在給程序設計帶來巨大便利的同時,也帶來了弊端。好比使用繼承會給程序帶來侵入性,程序的可移植性下降,增長了對象間的耦合性,若是一個類被其餘的類所繼承,則當這個類須要修改時,必須考慮到全部的子類,而且父類修改後,全部涉及到子類的功能都有可能會產生故障。

 

  應用案例:

  

public class A {

    public int  func1(int a,int b) {
        return a+b;
    }

}

  

public class B extends A {

    //這裏無心識的重寫了父類的方法
    public int func1(int a,int b) {
        return a-b;
    }

    public int func2(int a,int b) {
        return a+b+10;
    }

}

  

public class Client {

    public static void main(String[] args) {
        A a = new A();
        System.out.println("10+20=" + a.func1(10,20));

        B b = new B();
        System.out.println("10+20=" + b.func1(10,20));//這裏本意是想10+20
        System.out.println("1 + 2 + 10=" + b.func2(1,2));


    }

}

  咱們發現本來運行正常的相減功能發生了錯誤。緣由就是類B在給方法起名時無心中重寫了父類的方法,形成全部運行相減功能的代碼所有調用了類B重寫後的方法,形成本來運行正常的功能出現了錯誤。在本例中,引用基類A完成的功能,換成子類B以後,發生了異常。在實際編程中,咱們經常會經過重寫父類的方法來完成新的功能,這樣寫起來雖然簡單,可是整個繼承體系的可複用性會比較差,特別是運用多態比較頻繁時,程序運行出錯的概率很是大。若是非要重寫父類的方法,比較通用的作法是:原來的父類和子類都繼承一個更通俗的基類,原有的繼承關係去掉,採用依賴、聚合,組合等關係代替。

 

  5、開閉原則

  定義:一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉。

  開閉原則是面向對象設計中最基礎的設計原則,它指導咱們如何創建穩定靈活的系統。開閉原則多是設計模式六項原則中定義最模糊的一個了,它只告訴咱們對擴展開放,對修改關閉,但是到底如何才能作到對擴展開放,對修改關閉,並無明確的告訴咱們。之前,若是有人告訴我「你進行設計的時候必定要遵照開閉原則」,我會覺的他什麼都沒說,但貌似又什麼都說了。由於開閉原則真的太虛了。

 

  應用實例

public class Shape {
    int type;
}

public class Circle extends Shape {

    Circle() {
        super.type = 1;
    }

}

public class Rectangle extends Shape {

    Rectangle() {
        super.type = 2;
    }

}


public class Triangle extends Shape {

    Triangle() {
        super.type = 3;
    }

}

public class GraphicEditor {

    public void drawShape(Shape shape) {
        if (shape.type == 1) {
            drawCircle();
        }else if (shape.type  == 2) {
            drawRectangle();
        }else {
            drawTriangle();
        }
    }

    private void drawRectangle() {
        System.out.println("畫矩形");
    }
    private void drawCircle() {
        System.out.println("畫圓形");
    }
    private void drawTriangle() {
        System.out.println("畫三角形");
    }

}

public class Client {


    public static void main(String[] args) {
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawShape(new Circle());
        graphicEditor.drawShape(new Triangle());
        graphicEditor.drawShape(new Rectangle());

    }

}

  上面的示例中優勢是比較容易理解,代碼簡單。可是,違背了開閉原則,即對擴展開發,對修改關閉,也就是當咱們在給類添加新的功能的時候,不要改動現有的代碼,或者儘可能少許改動。上面的代碼中,若是咱們要添加一個畫橢圓形的功能,那麼須要修改的地方比較多,在修改的過程當中有可能會影響到已有的功能。所以,咱們須要對現有的代碼進行修改,使之符合ocp原則:

  思路:將Shape類作成抽象類,並提供一個抽象的方法draw,讓子類去實現就能夠了,這樣咱們在新增新的畫圖工具是,只須要實現Shape接口,客戶端的代碼就不須要修改,這樣就達到了ocp原則。

  

public interface Shape {

    void draw();

}

public class GraphicEditor {

    public void drawShape(Shape shape) {
        shape.draw();
    }

}


public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("畫矩形");
    }
}

public class Circle implements Shape {


    @Override
    public void draw() {
        System.out.println("畫圓形");
    }
}

public class Triangle implements Shape {


    @Override
    public void draw() {
        System.out.println("畫三角形");
    }
}



public class Client {

    public static void main(String[] args) {
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawShape(new Circle());
        graphicEditor.drawShape(new Triangle());
        graphicEditor.drawShape(new Rectangle());
    }

}

  6、迪米特法則

  迪米特法則的定義是:只與你的直接朋友交談,不跟「陌生人」說話(Talk only to your immediate friends and not to strangers)。其含義是:若是兩個軟件實體無須直接通訊,那麼就不該當發生直接的相互調用,能夠經過第三方轉發該調用。其目的是下降類之間的耦合度,提升模塊的相對獨立性。迪米特法則中的「朋友」是指:當前對象自己、當前對象的成員對象、當前對象所建立的對象、當前對象的方法參數等,這些對象同當前對象存在關聯、聚合或組合關係,能夠直接訪問這些對象的方法。

  注意事項:

  1. 在類的劃分上,應該建立弱耦合的類。類與類之間的耦合越弱,就越有利於實現可複用的目標。
  2. 在類的結構設計上,儘可能下降類成員的訪問權限。
  3. 在類的設計上,優先考慮將一個類設置成不變類。
  4. 在對其餘類的引用上,將引用其餘對象的次數降到最低。
  5. 不暴露類的屬性成員,而應該提供相應的訪問器(set 和 get 方法)。
  6. 謹慎使用序列化(Serializable)功能。
相關文章
相關標籤/搜索