2. 設計模式七大原則

設計模式七大原則

編寫軟件過程中,程序員面臨着來自 耦合性,內聚性以及可維護性,可擴展性,重用性,靈活性 等多方面的挑戰,設計模式是爲了讓程序(軟件),具有更好的:

  1. 代碼重用性 (即:相同功能的代碼,不用多次編寫)
  2. 可讀性 (即:編程規範性, 便於其他程序員的閱讀和理解)
  3. 可擴展性 (即:當需要增加新的功能時,非常的方便,稱爲可維護)
  4. 可靠性 (即:當我們增加新的功能後,對原來的功能沒有影響)
  5. 使程序呈現高內聚,低耦合的特性

設計模式包含了面向對象的精髓,“懂了設計模式,你就懂了面向對象分析和設計(OOA/D)的精要”

設計模式原則,其實就是 程序員在編程時,應當遵守的原則,也是各種設計模式的基礎。即設計模式爲什麼這樣設計的依據。
設計模式常用的七大原則有:

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

1. 單一職責原則

對類來說的, 即一個類應該只負責一項職責。

如類 A 負責兩個不同職責:職責 1,職責 2。當職責 1 需求變更而改變 A 時,可能造成職責 2 執行錯誤,所以需要將類 A 的粒度分解爲 A1,A2從而滿足單一職責原則。

違反單一職責原則
// 交通工具類
// 1. 在這裏的 run 方法中, 違反了單一職責原則
// 2. 解決的方案非常的簡單,根據交通工具運行方法不同,分解成不同類即可
class Vehicle {
    public void run(String vehicle) {
        System.out.println(vehicle + " 在公路上運行....");
    }
}
遵守單一職責原則
//1. 遵守單一職責原則
//2. 但是這樣做的改動很大,即將 類分解,同時修改客戶端
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 + "水中運行");
    }
}

改進後:

//1. 這種修改方法沒有對原來的類做大的修改,只是增加方法
//2. 這裏雖然沒有在類這個級別上遵守單一職責原則,但是在方法級別上,仍然是遵守單一職責
class Vehicle {
    
    public void run(String vehicle) {
        //處理
        System.out.println(vehicle + " 在公路上運行....");
    }
    public void runAir(String vehicle) {
        System.out.println(vehicle + " 在天空上運行....");
    }
    public void runWater(String vehicle) {
        System.out.println(vehicle + " 在水中行....");
    }

    //...
}
單一職責原則細節
  1. 降低類的複雜度,一個類只負責一項職責。
  2. 提高類的可讀性,可維護性
  3. 降低變更引起的風險
  4. 通常情況下,我們應當遵守單一職責原則,只有邏輯足夠簡單,纔可以在代碼級違反單一職責原則;只有類中方法數量足夠少,可以在方法級別保持單一職責原則。

2. 接口隔離原則

  1. 客戶端不應該依賴它不需要的接口,即一個類對另一個類的依賴應該建立在最小的接口上。
  2. 使用接口隔離原則拆分接口時,首先必須滿足單一職責原則,將一組相關的操作定義在一個接口中,且在滿足高內聚的前提下,接口中的方法越少越好。
  3. 可以在進行系統設計時採用定製服務的方式,即爲不同的客戶端提供寬窄不同的接口,只提供用戶需要的行爲,而隱藏用戶不需要的行爲。

3. 依賴倒轉原則☆

  1. 高層模塊不應該依賴低層模塊,二者都應該依賴其抽象
  2. 抽象不應該依賴細節,細節應該依賴抽象
  3. 依賴倒轉(倒置)的中心思想是面向接口編程
  4. 依賴倒轉原則是基於這樣的設計理念:相對於細節的多變性,抽象的東西要穩定的多。以抽象爲基礎搭建的架構比以細節爲基礎的架構要穩定的多。在 java 中,抽象指的是接口或抽象類,細節就是具體的實現類
  5. 使用 接口或抽象類 的目的是制定好 規範,而不涉及任何具體的操作,把展現細節的任務交給他們的實現類去完成。
違反依賴倒轉原則
/*
*測試
*/
public class DependecyInversion {
    public static void main(String[] args) {
        Person person = new Person();
        person.receive(new Email());
    }
}
class Email {
    public String getInfo() {
    	return "電子郵件信息: hello,world";
    }
}
//完成 Person 接收消息的功能
class Person {
    public void receive(Email email ) {
  		  System.out.println(email.getInfo());
    }
}
  1. 簡單,比較容易想到
  2. 如果我們獲取的對象是 微信,短信等等,則新增類,同時 Perons 也要增加相應的接收方法
  3. 解決思路:引入一個抽象的接口 IReceiver, 表示接收者, 這樣 Person 類與接口 IReceiver 發生依賴。因爲 Email, WeiXin 等等屬於接收的範圍,他們各自實現 IReceiver 接口就 可以了, 這樣我們就符合依賴倒轉原則。
遵守依賴倒轉原則
public class DependecyInversion {
    public static void main(String[] args) {
        //客戶端無需改變
        Person person = new Person();
        person.receive(new Email());
        person.receive(new WeiXin());
    }
}

//定義接口
interface IReceiver {
	public String getInfo();
}
class Email implements IReceiver {
    public String getInfo() {
    	return "電子郵件信息: hello,world";
    }
}

//增加微信
class WeiXin implements IReceiver {
    public String getInfo() {
    	return "微信信息: hello,ok";
    }
}

class Person {
    //這裏我們是對接口的依賴
    public void receive(IReceiver receiver ) {
   		 System.out.println(receiver.getInfo());
    }
}
依賴關係傳遞的三種方式
  1. 接口傳遞
  2. 構造方法傳遞
  3. setter 方式傳遞
依賴倒轉原則的細節
  1. 低層模塊儘量都要有抽象類或接口,或者兩者都有,程序穩定性更好。
  2. 變量的聲明類型儘量是抽象類或接口, 這樣我們的變量引用和實際對象間,就存在 一個緩衝層,利於程序擴展
    和優化
  3. 繼承時遵循 里氏替換原則。

4. 里氏替換原則

學習里氏替換原則之前我們需要先了解面向對象中的繼承性:

  1. 繼承包含這樣一層含義:父類中凡是已經實現好的方法,實際上是在設定規範和契約,雖然它不強制要求所有的子類必須遵循這些契約,但是如果子類對這些已經實現的方法任意修改,就會對整個繼承體系造成破壞。
  2. 繼承在給程序設計帶來便利的同時, 也帶來了弊端。比如使用繼承會給程序帶來 侵入性,程序的可移植性降低,增加對象間的耦合性,如果一個類被其他的類所繼承,則當這個類需要修改時,必須考慮到所有的子類,並且父類修改後,所有涉及到子類的功能都有可能產生故障。
  3. 問題提出: 在編程中,如何正確的使用繼承?
  4. 那就是里氏替換原則
基本介紹
  1. 里氏替換原則(Liskov Substitution Principle)在 1988 年,由麻省理工學院的以爲姓裏的女士提出–所有引用基類(父類)的地方必須能透明地使用其子類的對象。
  2. 里氏代換原則可以通俗表述爲:在軟件中如果能夠使用基類對象,那麼一定能夠使用其子類對象。把基類都替換成它的子類,程序將不會產生任何錯誤和異常,反過來則不成立,如果一個軟件實體使用的是一個子類的話,那麼它不一定能夠使用基類。
  3. 里氏代換原則是實現開閉原則的重要方式之一,由於使用基類對象的地方都可以使用子類對象,因此在程序中儘量使用基類類型來對對象進行定義,而在運行時再確定其子類類型,用子類對象來替換父類對象。
  4. 使用里氏代換原則需要注意:
    1. 子類的多有方法必須在父類中聲明,或者子類必須實現父類中聲名的所有方法。
    2. 儘量把父類設計成抽象類或接口,讓子類繼承父類或實現父接口。增加一個新功能時,通過增加一個新的子類來實現。

5. 開閉原則☆

開閉原則(Open Closed Principle)是編程中最基礎、最重要的設計原則。

  1. 一個軟件實體如類,模塊和函數應該 對擴展開放( 對提供方),對 修改關閉( 對使用方)。用抽象構建框架,用實現擴展細節。
  2. 當軟件需要變化時,儘量 通過擴展軟件實體的行爲來實現變化,而不是 通過修改已有的代碼來實現變化。
  3. 編程中遵循其它原則,以及使用設計模式的目的就是遵循開閉原則。
違反了開閉原則
public class Ocp {

	public static void main(String[] args) {
		//使用看看存在的問題
		GraphicEditor graphicEditor = new GraphicEditor();
		graphicEditor.drawShape(new Rectangle());
		graphicEditor.drawShape(new Circle());
		graphicEditor.drawShape(new Triangle());
	}

}

//這是一個用於繪圖的類 [使用方]
class GraphicEditor {
	//接收Shape對象,然後根據type,來繪製不同的圖形
	public void drawShape(Shape s) {
		if (s.m_type == 1)
			drawRectangle(s);
		else if (s.m_type == 2)
			drawCircle(s);
		else if (s.m_type == 3)
			drawTriangle(s);
	}

	//繪製矩形
	public void drawRectangle(Shape r) {
		System.out.println(" 繪製矩形 ");
	}

	//繪製圓形
	public void drawCircle(Shape r) {
		System.out.println(" 繪製圓形 ");
	}
	
	//繪製三角形
	public void drawTriangle(Shape r) {
		System.out.println(" 繪製三角形 ");
	}
}

//Shape類,基類
class Shape {
	int m_type;
}

class Rectangle extends Shape {
	Rectangle() {
		super.m_type = 1;
	}
}

class Circle extends Shape {
	Circle() {
		super.m_type = 2;
	}
}

//新增畫三角形
class Triangle extends Shape {
	Triangle() {
		super.m_type = 3;
	}
}

  1. 優點是比較好理解,簡單易操作。
  2. 缺點是違反了設計模式的開閉原則,即對擴展開放(提供方),對修改關閉(使用方)。即當我們給類增加新功能的時候,儘量不修改代碼,或者儘可能少修改代碼.
  3. 比如我們這時要新增加一個圖形種類 三角形,我們需要做如下修改,修改的地方較多。
遵循開閉原則
  1. 思路:把創建 Shape 類做成抽象類,並提供一個 抽象的 draw 方法,讓 子類去實現即可,這樣我們有新的圖形種類時,只需要讓新的圖形類繼承 Shape,並實現 draw 方法即可,使用方的代碼就不需要修改 。
  2. 這樣就滿足了開閉原則
public class Ocp {

	public static void main(String[] args) {
		//使用看看存在的問題
		GraphicEditor graphicEditor = new GraphicEditor();
		graphicEditor.drawShape(new Rectangle());
		graphicEditor.drawShape(new Circle());
		graphicEditor.drawShape(new Triangle());
		graphicEditor.drawShape(new OtherGraphic());
	}

}

//這是一個用於繪圖的類 [使用方]
class GraphicEditor {
	//接收Shape對象,調用draw方法
	public void drawShape(Shape s) {
		s.draw();
	}

	
}

//Shape類,基類
abstract class Shape {
	int m_type;
	
	public abstract void draw();//抽象方法
}

class Rectangle extends Shape {
	Rectangle() {
		super.m_type = 1;
	}

	@Override
	public void draw() {
		System.out.println(" 繪製矩形 ");
	}
}

class Circle extends Shape {
	Circle() {
		super.m_type = 2;
	}
	@Override
	public void draw() {
		System.out.println(" 繪製圓形 ");
	}
}

//新增畫三角形
class Triangle extends Shape {
	Triangle() {
		super.m_type = 3;
	}
	@Override
	public void draw() {
		System.out.println(" 繪製三角形 ");
	}
}

//新增一個圖形
class OtherGraphic extends Shape {
	OtherGraphic() {
		super.m_type = 4;
	}

	@Override
	public void draw() {
		System.out.println(" 繪製其它圖形 ");
	}
}

6. 迪米特法則

基本介紹
  1. 一個對象應該對其他對象保持最少的瞭解
  2. 類與類關係越密切,耦合度越大
  3. 迪米特法則(Demeter Principle)又叫最少知道原則,即一個類 對自己依賴的類知道的越少越好。也就是說,對於被依賴的類不管多麼複雜,都儘量將邏輯封裝在類的內部。對外除了提供的 public 方法,不對外泄露任何信息。
  4. 迪米特法則還有個更簡單的定義:只與直接的朋友通信。
    • 直接的朋友:每個對象都會與其他對象有耦合關係,只要兩個對象之間有耦合關係,我們就說這兩個對象之間是朋友關係。
    • 耦合的方式很多,依賴,關聯,組合,聚合等。其中,我們稱出現成員變量, 方法參數, 方法返回值中的類爲直接的朋友,而出現在 局部變量中的類不是直接的朋友。也就是說,陌生的類最好不要以局部變量的形式出現在類的內部。
迪米特法則細節
  1. 迪米特法則的核心是降低類之間的耦合
  2. 但是注意:由於每個類都減少了不必要的依賴,因此迪米特法則只是要求降低類間(對象間)耦合關係, 並不是要求完全沒有依賴關係。

7. 合成複用原則

原則是儘量使用合成/聚合的方式,而不是使用繼承。

小結

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

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