js設計模式 設計模式基本概念 和 七大設計原則

設計模式的基本概念

在軟件工程中看,設計模式是對軟件設計中普遍存在(反覆出現)的各種問題,所提出的解決方案。設計模式不是代碼,而是某類問題的通用解決方案。設計模式代表着最佳實踐。這些解決方案是衆多軟件人員的試錯和總結。設計模式包含了面向對象的精髓,懂得了設計模式,就懂得了面向對象分析和設計的精要。

  • 設計模式的本質:

    • 提高軟件的 “可維護性”,“通用性”,“可擴展性”,並降低"軟件的複雜度"。

  • 設計模式的重要性

    • 當項目開發完成時,如果客戶提出新增功能怎麼辦?
      ==> 可擴展性
    • 當項目開發完成時,程序員離職時,需要接受維護怎麼辦?
      ==> 可維護性,可讀性, 規範性
    • 在實際項目中用過什麼設計模式?怎麼使用的?解決了哪些問題?
    • 設計模式在軟件的什麼位置?
      面向對象OOP ==> 功能模塊 (設計模式+數據結構+算法) ==> 框架(使用多種設計模式) ==> 架構(服務器集羣)

  • 設計模式的目的

    • 代碼可重用性 ==> 相同功能的代碼,不用多次編寫。
    • 代碼可讀性 ==> 編碼規範性,便於其他人閱讀和理解。
    • 代碼可擴展性 ==>當需要新增功能時,非常方便,易維護。
    • 代碼可靠性 ==>新增功能時,對原來的功能沒有影響。
    • 高內聚,低耦合 ==>對外呈現高內聚,低耦合。

  • 核心思想:

    • 找出應用中可能需要變化的地方,把它們獨立出來。不要和那些不需要的代碼混合在一起。
    • 針對接口編程,而不針對實現類編程。
    • 爲交互對象之間的松耦合設計而努力。

24種設計模式

  • 創建型模式
    • 單例模式, 工廠方法模式,抽象工廠模式,原型模式, 建造者模式
  • 結構型模式
    • 適配器模式, 橋接模式, 裝飾模式, 組合模式, 外觀模式, 享元模式,代理模式
  • 行爲型模式
    • 模板方法模式,命令模式,訪問者模式, 迭代器模式,觀察者模式,中介者模式, 備忘錄模式,解釋器模式,狀態模式,策略模式, 責任鏈模式

設計模式常用的七大原則

  • 1、單一職責原則
  • 2、接口隔離原則
  • 3、依賴倒轉原則
  • 4、里氏替換原則
  • 5、開閉原則OCP
  • 6、迪米特法則(最少知道原則)
  • 7、合成複用原則

一、單一職責原則

  • 基本概念
    對類來說,一個類只負責一項職責。如果類負責兩個不同的職責:職責A和職責B,當職責A的需求發生改變時,則類中的代碼發生改變,此時可能造成職責A和職責B的代碼發生改變而造成錯誤,以及要重新進行單元測試。所以最好是將類進行單一職責的粒度進行分解。
    • 由於有多個職責,則由於改動而需要維護的代碼更多
    • 只有當類中邏輯足夠簡單,纔可以在代碼級別違反單一原則,只有當類中方法數量足夠少時,可在方法級別上保持單一職責原則。

  • 優點
    • 降低了類的複雜度,一個類只負責一項職責。
    • 提高類的可讀性,可維護性。
    • 降低了變更代碼引起的風險。
    • 通常情況下,我們應該遵循單一職責原則,除非邏輯足夠簡單,可以在放方法級別保持單一職責原則。
#b,c表示兩類不同的功能
public class A {

	public void b(){
		...
	}
	public void c(){
		...
	}
}

==>
public class AB {
	public void b(){
		...
	}
}

public class AC {
	public void c(){
		...
	}
}



二、接口隔離原則

傳統方法的問題和接口隔離原則的改進

  • 傳統方法的問題:
    ==>類A通過接口interface依賴B, 類C通過接口interface依賴類D,如果接口interface對於類A和類C不是最小接口,那麼類B和類D就必須要去實現他們不需要的方法。

  • 接口隔離原則:
    ==>將接口interface拆分成獨立的幾個接口,類A和類C分別與他們需要的接口建立依賴關係,保持接口的最小依賴。這就是接口分離原則。
#類A通過interface E依賴類C, 其只依賴a(),b()方法
public class A {
	private E e;
	public A( E e ){
		this.e = e;
		this.e.a();
		this.e.b();
	}
}

#類C通過interface E依賴類D,且只依賴c(),d()方法
public class C {
	private E e;
	public C( E e ){
		this.e = e;
		this.e.c();
		this.e.d();
	}
}

#接口E
public interface E {
	public void a();
	public void b();
	public void c();
	public void d();
}

public class  B implements E {
	...
}

public class D implements E {
	...
}
  • 根據接口隔離原則進行改進
#類A通過interface E依賴類C, 其只依賴a(),b()方法
public class A {
	private E e;
	public A( E e ){
		this.e = e;
		this.e.a();
		this.e.b();
	}
}

#類C通過interface F依賴類D,且只依賴c(),d()方法
public class C {
	private F f;
	public C( F f ){
		this.f = f;
		this.f.c();
		this.f.d();
	}
}

public interface E {
	public void a();
	public void b();
}

public interface F {
	public void c();
	public void d();
}

public class B implements E {
	...
}

public class D implements F {
	...
}



三、依賴倒轉原則

  • 高層模塊不應該依賴底層模塊,二者都應該依賴其抽象。層之間依賴接口。
  • 抽象不應該依賴細節,細節應該依賴抽象。
  • 依賴倒轉的核心思想:面向接口編程。
  • 依賴倒轉原則是基於以下的設計理念:
    • 相對於細節的多變性,抽象的東西要穩定很多。以抽象爲基礎搭建的架構比以細節爲基礎搭建的架構要穩定的多。在java中,抽象是指接口或者抽象類,細節就是具體的實現類。
  • 使用接口或者抽象類的目的是制定好規範,而不涉及任何具體的操作,把戰線細節的任務交給他們的實現類去完成。

依賴倒轉的核心思想就是:面向接口編程。層與層之間通過接口來訪問。不要在類的內部去new所依賴的對象,而是通過依賴關係傳遞到類內部來。

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

    • 接口傳遞依賴;
    public interface IOpenAndClose {
    	public void open( TV tv );
    }
    
    public interface TV {
    	public void play();
    }
    
    public class AqyTV implements TV {
    	public void play(){
    		System.out.println("hello aqy!");
    	}
    }
    
    
    public OpenAndClose implements IOpenAndClose {
    	public void open( TV tv ) {
    		tv.play();
    	}
    
    	public static void main( String[] args ){
    		IOpenAndClose iOpenAndClose = new OpenAndClose();
    		iOpenAndClose.open( new AqyTV() );
    	}
    }
    
    • 構造方法傳遞依賴;
    public interface IOpenAndClose {
    	public void open();
    }	
    
    public interface TV {
    	public void play();
    }
    
    public class AqyTV implements TV {
    	public void play(){
    		System.out.println("hello AqyTV!");
    	}
    }
    
    public class OpenAndClose implements IOpenAndClose {
    	private TV tv;
    	public OpenAndClose( TV tv ){
    		this.tv = tv;
    	}
    	public static void main( String[] args ){
    		IOpenAndClose iOpenAndClose = new OpenAndClose(new AqyTV());
    		iOpenAndClose.open();
    	}
    }
    
    • setter方式傳遞依賴;
    public interface IOpenAndClose {
    	public void open();
    	public void setTV( TV tv );
    }	
    
    public interface TV {
    	public void play();
    }
    
    public class AqyTV implements TV {
    	public void play(){
    		System.out.println("hello AqyTV!");
    	}
    }
    
    public class OpenAndClose implements IOpenAndClose {
    	private TV tv;
    	public void setTV( TV  tv ){
    		this.tv = tv;
    	}
    	public static void main( String[] args ){
    		IOpenAndClose iOpenAndClose = new OpenAndClose(new AqyTV());
    		iOpenAndClose.open();
    	}
    }
    

依賴倒轉原則的注意事項和細節:

  • 底層模塊(被依賴或者被調用的組件)儘量使用抽象類或者接口,或者兩者都有,程序穩定性會更好。
  • 變量的聲明類型儘量是抽象類或者接口,這樣我們的變量引用和實際對象間,就存在一個緩衝層,利用實現接口的實體類進行擴展和優化。
  • 繼承時準尋里氏替換原則。


四、里氏替換原則

  • 如果對每個類型爲T1的對象O1,都有類型爲T2的對象O2,使得以T1定義的所有程序在所有的對象O1都代換爲O2時,程序的行爲沒有發生變化,那麼類型T2是類型T1的子類型。換句話說:素所有引用基類的地方都必須能透明的使用其子類的對象。即子類繼承於父類的方法,行爲保持一致,不要去重寫覆蓋。
  • 在使用繼承時,遵循里氏原則,在子類中儘量不要重寫父類的方法,這樣在版本迭代時,能保持較好的向下兼容性。
  • 里氏原則告訴我們: 繼承爲讓兩個類之間的耦合增強,如果修改父類代碼時,則會影響到子類的使用。修改子類繼承於父類的代碼,也會導致兼容性問題出現。在適當的情況下,可以通過聚合,組合,依賴來解決問題。

針對繼承父類方法,又不得不重寫父類方法的處理方案

  • 可以解除繼承關係,而是通過聚合,組合的方式來結局問題。
  • 也可以讓原有的父子繼承關係解除,讓不要重寫的方法提取到一個公共的基類(編程一個虛擬類),讓需要重寫的方法在原來的父子類中分別實現。

子類重寫父類方法的情形

public interface A {
	public void a();
	public void b();
	public void c();
	public void d();
}

public class B implements A {
	public void a(){
		System.out.println("B-a");
	}
	public void b(){
		System.out.println("B-b");
	}
	public void c(){
		System.out.println("B-c");
	}
	public void d(){
		System.out.println("B-d");
	}
}

public class C extends B {
	public void a(){
		System.out.println("B-a");
	}
	public void b(){
		System.out.println("B-b");
	}
	public void c(){
		System.out.println("C-c");
	}
	public void d(){
		System.out.println("C-d");
	}
}

遵循里氏替換原則的繼承處理

  • 將共有方法提取到公共的抽象類,不相同行爲的方法單獨實現。
public interface A {
	public void a();
	public void b();
	public void c();
	public void d();
}

public abstract class D {
	public void a() {
		System.out.println("D-a");
	}
	public void b(){
		System.out.println("D-b");
	}
	public abstract  void c();
	public abstract  void d();
}

public class B extends D {
	public void c(){
		System.out.println("B-c");
	}
	pulic void d(){
		System.out.println("B-d");
	}
}

public class C {
	public void c(){
		System.out.println("C-c");
	}
	pulic void d(){
		System.out.println("C-d");
	} 
}


五、開閉原則

  • 開閉原則: 最基礎,最重要的設計原則。
    ==> 一個軟件如類,模塊和函數應該對提供方擴展開放,對使用方修改關閉。。用抽象構建框架,用實現擴展細節。
  • 當軟件需求發生變化時,儘量通過擴展軟件實體的行爲來實現變化,而不是通過修改已有的代碼來實現變化。(擴展行爲,不要輕易去修改已有的使用方中的代碼)。
  • 編程中遵循其他原則,以及使用設計模式的目的就是爲了遵循開閉原則。提供方不允許修改代碼。通過新增來擴展新功能。使用方不允許新增或者修改。(當做第三方庫來對待使用方的代碼)。
#通過接口傳遞依賴關係,根據多態來動態新增功能
public interface A {
	public void setTV( TV tv );
}

public interface TV {
	public void play();
}

public class IqyTV implements TV {
	public void play(){
		System.out.println("hello iqyTV");
	}
}

public class B implements A {
	private TV tv;
	public void setTV( TV tv ){
		this.tv = tv;
	}
	
	public static void main( String[] args ){
		A a = new B();
		a.setTV( new IqyTV() );
		a.tv.play();
	}
}




六、迪米特法則(最少知道原則)

  • 一個對象應該對其他對象保持最少的瞭解。類與類之間的關係越密切,耦合度越大。
  • 迪米特法則:又稱爲最少知道原則,即一個類對自己依賴的類知道的越少越好。也就是說: 對於被依賴的類,不管多複雜,都儘量將邏輯封裝在類的內部,對外除了提供的必要的public方法,不對外泄露任何信息。

關於迪米特法則的簡單定義:
  • 只與直接朋友通信,且直接朋友也只提供public方法,不暴露內部邏輯。

關於直接朋友:
  • 任何兩個對象之間都有耦合關係,只要有耦合關係,就是朋友。耦合的方式有很多:依賴,關聯,組合,聚合等。其中出現在成員變量,方法參數,方法返回值中的類稱爲直接朋友。出現在方法中的局部變量中的類不是直接朋友。(陌生類最好不要以局部變量的形式出現在類的內部)


七、合成複用原則

  • 合成複用原則:
    • 能用合成,聚合的方式,就不推薦用繼承。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章