設計模式六大原則

 

 

這些原則實現的最高目標是:  系統靈活穩定易於維護、擴展需求,而不是牽一髮而動全身

.單一職責(Single Responsibility Principle,簡稱SRP ): 一個類只負責一項職責

不要存在多於一個致使類變動的緣由。通俗的說,即一個類的代碼只負責一項職責。java

 

.里氏替換原則(Liskov Substitution Principle,簡稱LSP): 子類能夠替換父類

繼承有一些優勢:編程

     1. 提升代碼的重用性,子類擁有父類的方法和屬性;
2. 提升代碼的可擴展性,子類可形似於父類,但異於父類,保留自個人特性;
缺點:侵入性、不夠靈活、高耦合
     1. 繼承是侵入性的,只要繼承就必須擁有父類的全部方法和屬性,在必定程度上約束了子類,下降了代碼的靈活性;
2. 增長了耦合,當父類的常量、變量或者方法被修改了,須要考慮子類的修改,因此一旦父類有了變更,極可能會形成
很是糟糕的結果,要重構大量的代碼。

 

由於繼承帶來的侵入性,加強了耦合性,也下降了代碼靈活性,父類修改代碼,子類也會受到影響,此時就須要里氏替換原則。設計模式

  • 子類必須實現父類的抽象方法,但不得重寫(覆蓋)父類的非抽象(已實現)方法。
  • 子類中能夠增長本身特有的方法。
  • 當子類覆蓋或實現父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入參數更寬鬆。
  • 當子類的方法實現父類的抽象方法時,方法的後置條件(即方法的返回值)要比父類更嚴格。

a.子類必須實現父類的抽象方法,但不得重寫(覆蓋)父類的非抽象(已實現)方法。ide

 

public class A {
    public void fun(int a,int b){
        System.out.println(a+"+"+b+"="+(a+b));
    }
}
 
public class B extends A{
    @Override
    public void fun(int a,int b){
        System.out.println(a+"-"+b+"="+(a-b));
    }
}
 
public class demo {
    public static void main(String[] args){
        System.out.println("父類的運行結果");
        A a=new A();
        a.fun(1,2);
        //父類存在的地方,能夠用子類替代
        //子類B替代父類A
        System.out.println("子類替代父類後的運行結果");
        B b=new B();
        b.fun(1,2);
    }
}
運行結果:
父類的運行結果
1+2=3
子類替代父類後的運行結果
1-2=-1

b.子類中能夠增長本身特有的方法。函數

 

public class A {
    public void fun(int a,int b){
        System.out.println(a+"+"+b+"="+(a+b));
    }
}
 
public class B extends A{
    public void newFun(){
        System.out.println("這是子類的新方法...");
    }
}
 
public class demo {
    public static void main(String[] args){
        System.out.print("父類的運行結果:");
        A a=new A();
        a.fun(1,2);
        //父類存在的地方,能夠用子類替代
        //子類B替代父類A
        System.out.print("子類替代父類後的運行結果:");
        B b=new B();
        b.fun(1,2);
        //子類B的新方法
        b.newFun();
    }
}
運行結果:
父類的運行結果:1+2=3
子類替代父類後的運行結果:1+2=3
這是子類的新方法...

c.當子類覆蓋或實現父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入參數更寬鬆。this

 

public class LSP {


     class A {
        public void fun(HashMap map){
            System.out.println("父類被執行...");
        }
    }
     
     class B extends A{
        public void fun(Map map){
            System.out.println("子類被執行...");
        }
    }
     
     public static void main(String[] args){
            System.out.print("父類的運行結果:");
            LSP lsp =new LSP();
            LSP.A a= lsp.new A();
            HashMap<Object, Object> map=new HashMap<Object, Object>();
            a.fun(map);
            //父類存在的地方,能夠用子類替代
            //子類B替代父類A
            System.out.print("子類替代父類後的運行結果:");
            LSP.B b=lsp.new B();
            b.fun(map);
        }

}
運行結果:
父類的運行結果:父類被執行...
子類替代父類後的運行結果:父類被執行...
符合條件
咱們應當注意,子類並不是重寫了父類的方法,而是重載了父類的方法。由於子類和父類的方法的輸入參數是不一樣的。
子類方法的參數Map比父類方法的參數HashMap的範圍要大,因此當參數輸入爲HashMap類型時,只會執行父類的方法,不會執行父類的重載方法。這符合里氏替換原則。

 
 
//將子類方法的參數範圍縮小會怎樣?
import java.util.Map;
public class A {
    public void fun(Map map){
        System.out.println("父類被執行...");
    }
}
 
import java.util.HashMap;
public class B extends A{
    public void fun(HashMap map){
        System.out.println("子類被執行...");
    }
}
 
import java.util.HashMap;
 
public class demo {
     static void main(String[] args){
        System.out.print("父類的運行結果:");
        A a=new A();
        HashMap map=new HashMap();
        a.fun(map);
        //父類存在的地方,均可以用子類替代
        //子類B替代父類A
        System.out.print("子類替代父類後的運行結果:");
        B b=new B();
        b.fun(map);
    }
}
運行結果:
父類的運行結果:父類被執行...
子類替代父類後的運行結果:子類被執行...

在父類方法沒有被重寫的狀況下,子方法被執行了,這樣就引發了程序邏輯的混亂。
因此子類中方法的前置條件必須與父類中被覆寫的方法的前置條件相同或者更寬鬆。不符合裏式替換

 

d.當子類的方法實現父類的抽象方法時,方法的後置條件(即方法的返回值)要比父類更嚴格。spa

 

public class LSP1 {
     abstract class A {
            public abstract Map fun();
        }
         
     class B extends A{
            @Override
            public HashMap fun(){
                HashMap b=new HashMap();
                b.put("b","子類被執行...");
                return b;
            }
        }
         
         public static void main(String[] args){
                LSP1 lsp =new LSP1();
                LSP1.A a=lsp.new B();
                System.out.println(a.fun());
            }

}

 

運行結果:
{b=子類被執行...}設計

若在繼承時,子類的方法返回值類型範圍比父類的方法返回值類型範圍大,在子類重寫該方法時編譯器會報錯。code

  看上去很難以想象,由於咱們會發如今本身編程中經常會違反里氏替換原則,程序照樣跑的好好的。因此你們都會產生這樣的疑問,假如我非要不遵循里氏替換原則會有什麼後果?對象

        後果就是:你寫的代碼出問題的概率將會大大增長。

 

三.依賴倒置原則(Dependence Inversion Principle,簡稱DIP)面向接口編程,多態(接口類或者抽象類)

高層模塊不該該依賴低層模塊,兩者都應該依賴其抽象;抽象不該該依賴實現類;實現類應該依賴抽象。

/**
* 大衆汽車類
* @author 葉漢偉
*/
public class DaZhong {
    public void run(){
        System.out.println("開大衆汽車");
    }
}

/**
 * 司機類,依賴具體的實現類參數,暫時只能開大衆,開不了寶馬
 * @author 葉漢偉
 */
public class Driver {
    public void drive(DaZhong daZhong){
        daZhong.run();
    }
}

public class Client {
    public static void main(String[] args){
        Driver Tom=new Driver();
        DaZhong daZhong=new DaZhong();
        Tom.drive(daZhong);
    }
}

 

/**
 * 寶馬車類
 * @author 葉漢偉
 */
public class BaoMa {
    public void run(){
        System.out.println("開寶馬車");
    }
}

 

面向接口編程,多態

實現依賴的三種方法

對象的依賴關係能夠經過三種方法來實現:

  1. 接口聲明依賴對象,接口注入
  2. 構造函數傳遞依賴對象,構造函數注入
  3. setter方法傳遞依賴對象,setter方法注入

1.接口聲明依賴對象

在接口處就聲明瞭依賴的對象。司機接口IDriver,其方法drive()的形參是ICar類型的,那麼咱們能夠說IDrive與ICar發生了依賴關係,Dazhong,baoma依賴ICar注入,依賴倒置了。接口聲明依賴的方法也叫接口注入。

/**
 * 車子接口
 * @author 葉漢偉
 */
public interface ICar {
    public void run();
}

/**
 * 大衆汽車類
 * @author 葉漢偉
 */
public class DaZhong implements ICar{
    public void run(){
        System.out.println("開大衆汽車");
    }
}

/**
 * 寶馬車類
 * @author 葉漢偉
 */
public class BaoMa implements ICar{
    public void run(){
        System.out.println("開寶馬車");
    }
}

/**
 * 司機接口
 * @author 葉漢偉
 */
public interface IDriver {

//
接口聲明依賴對象,接口注入ICar,這裏變成了car依賴ICar
    public void drive(ICar car);
}

/**
 * 司機類
 * @author 葉漢偉
 */
public class Driver implements IDriver{
//依賴接口
public void drive(ICar car){ car.run(); } } public class Client { public static void main(String[] args){ IDriver Tom=new Driver(); //Tom開大衆汽車 ICar daZhong=new DaZhong(); Tom.drive(daZhong); //Tom開寶馬 ICar baoMa=new BaoMa(); Tom.drive(baoMa); } }

 

2.構造函數傳遞依賴對象

/**
 * 司機接口
 * @author 葉漢偉
 */
public interface IDriver {
    public void drive();
}

/**
 * 司機類
 * @author 葉漢偉
 */
public class Driver implements IDriver{
private ICar car;
//經過構造函數注入依賴對象,這裏變成了car依賴ICar
public Driver(ICar car){
    this.car=car;
}
public void drive(){
   this.car.run();
   }
}

3.setter方法傳遞依賴對象

/**
 * 司機接口
 * @author 葉漢偉
 */
public interface IDriver {
public void setCar(ICar car);
    public void drive();
}

/**
 * 司機類
 * @author 葉漢偉
 */
public class Driver implements IDriver{
private ICar car;
//setter方法傳遞依賴對象,,這裏變成了car依賴ICar
 public void setCar(ICar car){ this.car=car; } public void drive(){ this.car.run(); } }

 

4、接口隔離原則(Interface Segregation Principle,簡稱ISP):類間的依賴關係應該創建在最小的接口上,不要試圖去創建一個很龐大的接口供全部依賴它的類去調用

  • 核心思想:類間的依賴關係應該創建在最小的接口上
  • 通俗來說: 創建單一接口,不要創建龐大臃腫的接口,儘可能細化接口,接口中的方法儘可能少。也就是說,咱們要爲各個類創建專用的接口,而不要試圖去創建一個很龐大的接口供全部依賴它的類去調用。
  • 問題描述: 類A經過接口interface依賴類B,類C經過接口interface依賴類D,若是接口interface對於類A和類B來講不是最小接口,則類B和類D必須去實現他們不須要的方法。
  • 需注意:
  • 接口儘可能小,可是要有限度。對接口進行細化能夠提升程序設計靈活性,可是若是太小,則會形成接口數量過多,使設計複雜化。因此必定要適度
  • 提升內聚,減小對外交互。使接口用最少的方法去完成最多的事情
  • 爲依賴接口的類定製服務。只暴露給調用的類它須要的方法,它不須要的方法則隱藏起來。只有專一地爲一個模塊提供定製服務,才能創建最小的依賴關係。

5、迪米特法則(Law of Demeter,簡稱LoD) 低耦合,一個對象應該對其餘對象保持最少的瞭解(中介者模式就是這個的應用)

  • 核心思想: 類間解耦。
  • 通俗來說: 一個類對本身依賴的類知道的越少越好。自從咱們接觸編程開始,就知道了軟件編程的總的原則:低耦合,高內聚。不管是面向過程編程仍是面向對象編程,只有使各個模塊之間的耦合儘可能的低,才能提升代碼的複用率。低耦合的優勢不言而喻,可是怎麼樣編程才能作到低耦合呢?那正是迪米特法則要去完成的。

6、開放封閉原則(Open Close Principle,簡稱OCP)儘可能經過擴展軟件實體的行爲來實現變化,而不是經過修改已有的代碼來實現變化

  • 核心思想: 儘可能經過擴展軟件實體來解決需求變化,而不是經過修改已有的代碼來完成變化
  • 通俗來說: 一個軟件產品在生命週期內,都會發生變化,既然變化是一個既定的事實,咱們就應該在設計的時候儘可能適應這些變化,以提升項目的穩定性和靈活性。

一句話歸納: 單一職責原則告訴咱們實現類要職責單一;里氏替換原則告訴咱們不要破壞繼承體系;依賴倒置原則告訴咱們要面向接口編程;接口隔離原則告訴咱們在設計接口的時候要精簡單一;迪米特法則告訴咱們要下降耦合。而開閉原則是總綱,他告訴咱們要對擴展開放,對修改關閉。

總結:

最後總結一下如何去遵照這六個原則。對這六個原則的遵照並非是和否的問題,而是多和少的問題,也就是說,咱們通常不會說有沒有遵照,而是說遵照程度的多少。任何事都是過猶不及,設計模式的六個設計原則也是同樣,制定這六個原則的目的並非要咱們刻板的遵照他們,而須要根據實際狀況靈活運用。對他們的遵照程度只要在一個合理的範圍內,就算是良好的設計。咱們用一幅圖來講明一下。

 

 

 

圖中的每一條維度各表明一項原則,咱們依據對這項原則的遵照程度在維度上畫一個點,則若是對這項原則遵照的合理的話,這個點應該落在紅色的同心圓內部;若是遵照的差,點將會在小圓內部;若是過分遵照,點將會落在大圓外部。一個良好的設計體如今圖中,應該是六個頂點都在同心圓中的六邊形

 

 

 

 

在上圖中,設計一、設計2屬於良好的設計,他們對六項原則的遵照程度都在合理的範圍內;設計三、設計4設計雖然有些不足,但也基本能夠接受;設計5則嚴重不足,對各項原則都沒有很好的遵照;而設計6則遵照過渡了,設計5和設計6都是迫切須要重構的設計。

相關文章
相關標籤/搜索