設計模式學習筆記01 - Java版之七大原則介紹

1. 設計模式的七大原則

1.1 設計模式的目的

讓程序軟件具有更好的

  1. 代碼重用性: 相同功能代碼不要多次編寫
  2. 可讀性: 編程規範性, 便於他人閱讀
  3. 可擴展性: 當需要增加新的功能時非常方便,成本低
  4. 可靠性: 當我們增加新的功能後,對原來的功能不能有影響
  5. 高內聚, 低耦合

1.2 設計模式七大原則

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

1.2.1 單一職責原則

對類來說, 一個類應該只負責一項職責. 如果A類負責兩項職責, 職責1和職責2, 那麼職責1改變的時候可能會改變A類, 進而導致職責2執行錯誤

注意事項:

  1. 降低類的複雜度, 達到一個類只負責一項職責
  2. 提高類的可讀性, 可維護性
  3. 降低變更引起的風險
  4. 通常情況下, 我們應當遵守單一職責原則, 只有邏輯足夠簡單纔可以在代碼級違反單一職責原則, 也就是說只有當類中的方法足夠少的時候, 可以再方法級別保證單一職責原則
package com.test;

public class SingleResponsibility {
    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        vehicle.run("汽車");
        vehicle.run("飛機");
    }
}

class Vehicle {
	
	private String common = "在";
	
    public void run(String vehicle) {
        System.out.println(vehicle + common + "路上跑...");
    }
	
    public void fly(String vehicle) {
        System.out.println(vehicle + common + "路上飛...");
    }
}

看上面的案例,輸出了汽車在路上跑 和 飛機在路上飛, 他們都用到了 “在” 現在我們要改成 汽車zai路上跑, 而飛機不變, 怎麼辦?
我們使用單一職責原則優化下代碼…

package com.test;

public class SingleResponsibility {
    public static void main(String[] args) {
        RoadVehicle roadVehicle = new RoadVehicle();
        SkyVehicle skyVehicle = new SkyVehicle();
        roadVehicle.run("汽車");
        skyVehicle.run("飛機");
    }
}

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

class SkyVehicle {
    public void run(String vehicle) {
        System.out.println(vehicle + "在天上飛...");
    }
}

這樣子拆成了兩個類就沒有問題了, 可是上面也說了當邏輯足夠簡單是可以再方法級別遵守單一職責原則的, 我們嘗試下…

package com.test;

public class SingleResponsibility {
    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        vehicle.runRoad("汽車");
        vehicle.runSky("飛機");
    }
}

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

    public void runSky(String vehicle) {
        System.out.println(vehicle + "在天上飛...");
    }
}

1.2.2 接口隔離原則 (Interface Segregation Principle)

介紹:
客戶端不應該依賴它不需要的接口, 即一個類對另一個類的依賴應該建立在最小接口上

現在有一段代碼如下:

/**
 * 接口中有三個方法
 */
interface interface1 {
    void method1();

    void method2();

    void method3();
}

/**
 * A類實現接口並重寫全部的方法
 */
class A implements interface1 {

    @Override
    public void method1() {
        System.out.println("A重寫了方法1...");
    }

    @Override
    public void method2() {
        System.out.println("A重寫了方法2...");
    }

    @Override
    public void method3() {
        System.out.println("A重寫了方法3...");
    }
}

/**
 * B類實現了接口 重寫了全部的方法
 */
class B implements interface1 {

    @Override
    public void method1() {
        System.out.println("B重寫了方法1...");
    }

    @Override
    public void method2() {
        System.out.println("B重寫了方法2...");
    }

    @Override
    public void method3() {
        System.out.println("B重寫了方法3...");
    }
}

/**
 * C類通過接口依賴A的方法1
 */
class C {
    public void m1(interface1 i) {
        i.method1();
    }
}

/**
 * D類通過接口依賴B類的方法2
 */
class D {
    public void m2(interface1 i) {
        i.method2();
        i.method3();
    }
}

上面的代碼是不符合接口隔離原則的, 那我們怎麼優化呢? 將接口拆成多個…

/**
 * 接口中有三個方法
 */
interface interface1 {
    void method1();
}

interface interface2 {
    void method2();

    void method3();
}

/**
 * A類實現接口並重寫全部的方法
 */
class A implements interface1 {

    @Override
    public void method1() {
        System.out.println("A重寫了方法1...");
    }
}

/**
 * B類實現了接口 重寫了全部的方法
 */
class B implements interface2 {
    @Override
    public void method2() {
        System.out.println("B重寫了方法2...");
    }

    @Override
    public void method3() {
        System.out.println("B重寫了方法3...");
    }
}

/**
 * C類通過接口依賴A的方法1
 */
class C {
    public void m1(interface1 i) {
        i.method1();
    }
}

/**
 * D類通過接口依賴B類的方法2
 */
class D {
    public void m2(interface2 i) {
        i.method2();
        i.method3();
    }
}

1.2.3 依賴倒轉原則 (Denpendence Inversion Principle)

  1. 高層模塊不應該依賴低層模塊, 二者都應該依賴其抽象
  2. 抽象不應該依賴細節, 細節應該依賴抽象
  3. 核心思想: 依賴接口編程
  4. 設計理念: 相對於細節的多邊性, 抽象的東西要穩定的多, 以抽象爲基礎搭建的架構比以細節爲基礎搭建的架構要穩定的多. 在java中抽象指的是接口或抽象類, 細節就是具體的實現類
  5. 使用接口或者抽象類的目的是制定好規範, 而不涉及具體的操作, 細節的東西交給實現類去做

注意事項:

  1. 低層模塊儘量有接口或者抽象類, 或者兩者都有, 程序的穩定性更好
  2. 變量的聲明類型儘量使抽象類或者接口, 這樣變量的引用和實際的對象之間就有一個緩存層利於程序的擴展和優化
  3. 繼承時遵循里氏替換原則

看下面的案例:

package com.test;

public class Inversion {
    public static void main(String[] args) {
        People people = new People();
        people.receive(new Email());
    }
}

/**
 * 電子郵件類
 */
class Email {
    public String getInfo() {
        return "hello email ...";
    }
}

/**
 * 人類
 * 直接依賴了Email類, 這樣不方便擴展, 如果以後要接受微信的消息怎麼辦...
 */
class People {
    public void receive(Email email) {
        System.out.println(email.getInfo());
    }
}

很明顯, 上面的代碼是不方便擴展的, 我們使用依賴倒轉原則優化下…

package com.test;

public class Inversion {
    public static void main(String[] args) {
        People people = new People();
        people.receive(new Email());
    }
}

/**
 * 接收消息的抽象, 所有的接收消息類全部實現這個抽象
 */
interface Ireceiver {
    String getinfo();
}

/**
 * 電子郵件類 實現 Ireceiver
 */
class Email implements Ireceiver {
    @Override
    public String getinfo() {
        return "hello email ...";
    }
}

/**
 * 不直接依賴於Email, 現在直接依賴於它的抽象Ireceiver
 */
class People {
    public void receive(Ireceiver receiver) {
        System.out.println(receiver.getinfo());
    }
}

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

  1. 接口傳遞
    在這裏插入圖片描述
  2. 構造方法傳遞
    在這裏插入圖片描述
  3. 通過setter方法傳遞
    在這裏插入圖片描述

1.2.4 里氏替換原則 (Liskov Substitution Principle)

oo中(面向對象)繼承的說明:

  1. 繼承中包含這樣一層含義: 父類中凡是已經實現好的方法實際上是在設定規範和契約, 雖然不強制要求子類去遵守, 但是如果子類隨意對這些已經實現好的方法進行修改就會破壞整個集成體系
  2. 繼承在給程序設計帶來便利的同時也帶來了弊端. 比如會給程序帶來侵入性, 程序的可移植性降低, 增加對象之間的耦合性. 如一個類被其他類所繼承, 那麼我們在修改這個類的時候要考慮到所有子類, 並且這個類修改之後子類都有可能會出現故障
  3. 正確的使用繼承: 里氏替換原則

里氏替換原則介紹:
4. 如果每個類型爲T1的對象o1 都有每個類型爲T2的對象o2, 使在以T1定義的所有程序P中將對象o1替換成對象o2時,程序P的行爲沒有發生變化, 那麼類型T2是類型T1的子類型. 也就是說所有引用基類的地方必須能透明的使用其子類對象.
5. 使用繼承時, 儘量遵循里氏替換原則, 子類儘量不要重寫父類的方法
6. 如果你迫不得已非要重寫父類的方法, 可以通過聚合 組合 依賴來解決問題

看下面的例子:

/**
 * AA類裏面有方法計算兩個數的差
 */
class AA {
    public int method1(int x, int y) {
        return x - y;
    }
}

/**
 * BB 重寫了AA, 增加了方法2
 */
class BB extends AA {
    @Override
    public int method1(int x, int y) {
        return x - y;
    }

    public int method2(int x, int y) {
        return method1(x, y) + 9;
    }
}

上面AA中就一個方法, BB給人家重寫了, 那麼還要AA幹啥, 按照里氏替換原則優化下…

/**
 * 寫一個抽象, 讓AA和BB去實現
 */
interface Base {
    int method1(int x, int y);
}

/**
 * AA實現Base
 */
class AA implements Base{
    public int method1(int x, int y) {
        return x - y;
    }
}

/**
 * BB實現Base 組合AA
 */
class BB implements Base {
    AA aa = new AA();   // 組合AA使用的
    @Override
    public int method1(int x, int y) {
        return x - y;
    }

    public int method2(int x, int y) {
        return aa.method1(x, y) + 9;
    }
}

1.2.5 開閉原則 (Open Closed Principle)

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

看下面的案例:

package com.test;

public class Ocp {
    public static void main(String[] args) {
        GraphEditor graphEditor = new GraphEditor();
        graphEditor.drawSharp(new Circle());
        graphEditor.drawSharp(new Rectangle());
    }
}

/**
 * 用於繪圖的類
 */
class GraphEditor {
    public void drawSharp(Sharp sharp) {
        if (sharp.m_type == 1) {
            drawCircle();
        } else {
            drawRectangle();
        }
    }

    public void drawCircle() {
        System.out.println("畫圓形...");
    }

    public void drawRectangle() {
        System.out.println("畫正方形...");
    }
}

/**
 * 基類
 */
class Sharp {
    int m_type;
}

/**
 * 圓形
 */
class Circle extends Sharp {
    Circle() {
        super.m_type = 1;
    }
}

/**
 * 方形
 */
class Rectangle extends Sharp {
    public Rectangle() {
        super.m_type = 2;
    }
}

上面的代碼實際上是有問題的, 現在的代碼支持構建圓形和正方形, 那麼我們現在想擴展功能, 我們要繪製三角形我們要怎麼辦呢?

  1. 需要新建一個三角形實體
  2. 更改GraphEditor的代碼 就相當於使用方
  3. 更改main方法

所以這就違反了OCP原則…

優化如下:

package com.test;

public class Ocp {
    public static void main(String[] args) {
        GraphEditor graphEditor = new GraphEditor();
        graphEditor.drawSharp(new Circle());
        graphEditor.drawSharp(new Rectangle());
    }
}

/**
 * 用於繪圖的類
 */
class GraphEditor {
    public void drawSharp(Sharp sharp) {
        sharp.draw();
    }
}

/**
 * 基類
 */
abstract class Sharp {
    abstract void draw();
}

/**
 * 圓形
 */
class Circle extends Sharp {
    @Override
    void draw() {
        System.out.println("畫圓形...");
    }
}

/**
 * 方形
 */
class Rectangle extends Sharp {
    @Override
    void draw() {
        System.out.println("畫正方形...");
    }
}

這樣子我們需要擴展的時候需要:

  1. 增加三角形的實體
  2. 更改main方法

真正的使用方 也就是GraphEditor類不需要改變…

1.2.6 迪米特法則 (Demeter Principle)

  1. 一個對象應該和其他對象保持最少的瞭解
  2. 類與類的關係越密切, 耦合度越大
  3. 迪米特法則 又叫最少知道原則, 即一個類對自己依賴的類回到的越少越好. 也就是說對於被依賴的類不管多麼的複雜, 都儘量封裝在類的內部, 對外出來提供public方法外, 不泄露任何信息
  4. 迪米特法則還有更直接的定義: 只與直接的朋友通信
  5. 直接朋友: 每個對象都會與其他對象有耦合關係, 只要兩個對象之間有耦合關係就說是朋友關係. 耦合的方式很多依賴 關聯 組合 聚合等. 我們稱出現在成員變量 方法參數 方法返回值中的類爲直接朋友. 局部變量中的類不是直接朋友. 也就是說陌生的類儘量不要以局部變量的方式出現在類的內部…

注意事項:

  1. 核心是降低類之間的耦合
  2. 但是注意: 由於每個類減少了沒必要的依賴, 因此迪米特法則只是要求降低類間(對象間)耦合關係, 並不是完全沒有依賴

查看案例: 打印學校總部 和 分佈所有的員工ID

package com.liyang;

import java.util.ArrayList;
import java.util.List;

// 客戶端
public class Demeter {

	public static void main(String[] args) {
		SchoolManager schoolManager = new SchoolManager();
		schoolManager.printAllEmployee(new CollegeManager());
	}

}

// 學校總部員工類
class Employee {
	private String id;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}
	
}

// 學院員工類
class CollegeEmployee {
	private String id;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}
	
}

// 管理學院員工類
class CollegeManager {
	public List<CollegeEmployee> getAllEmployee() {
		ArrayList<CollegeEmployee> arrayList = new ArrayList<CollegeEmployee>();
		for (int i = 0; i < 10; i++) {
			CollegeEmployee collegeEmployee = new CollegeEmployee();
			collegeEmployee.setId("ID:" + i);
			arrayList.add(collegeEmployee);
		}
		return arrayList;
	}
}

// 管理學校總部員工類
class SchoolManager {
	public List<Employee> getAllEmployee() {
		ArrayList<Employee> arrayList = new ArrayList<Employee>();
		for (int i = 0; i < 10; i++) {
			Employee employee = new Employee();
			employee.setId("ID:" + i);
			arrayList.add(employee);
		}
		return arrayList;
	}
	
	void printAllEmployee(CollegeManager collegeManager) {
		List<CollegeEmployee> allEmployee = collegeManager.getAllEmployee();
		System.out.println("學院員工...");
		for (CollegeEmployee collegeEmployee : allEmployee) {
			System.out.println(collegeEmployee.getId());
		}
		
		List<Employee> allEmployee2 = this.getAllEmployee();
		System.out.println("總部員工...");
		for (Employee employee : allEmployee2) {
			System.out.println(employee.getId());
		}
	}
}

分析上面的SchoolManager類, Employee, CollegeManager是直接朋友關係, CollegeEmployee就不是直接朋友關係 這樣子就違反了迪米特法則…

package com.liyang;

import java.util.ArrayList;
import java.util.List;

// 客戶端
public class Demeter {

	public static void main(String[] args) {
		SchoolManager schoolManager = new SchoolManager();
		schoolManager.printAllEmployee(new CollegeManager());
	}

}

// 學校總部員工類
class Employee {
	private String id;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}
	
}

// 學院員工類
class CollegeEmployee {
	private String id;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}
	
}

// 管理學院員工類
class CollegeManager {
	public List<CollegeEmployee> getAllEmployee() {
		ArrayList<CollegeEmployee> arrayList = new ArrayList<CollegeEmployee>();
		for (int i = 0; i < 10; i++) {
			CollegeEmployee collegeEmployee = new CollegeEmployee();
			collegeEmployee.setId("ID:" + i);
			arrayList.add(collegeEmployee);
		}
		return arrayList;
	}
	
	void printAllEmployee() {
		List<CollegeEmployee> allEmployee = this.getAllEmployee();
		System.out.println("學院員工...");
		for (CollegeEmployee collegeEmployee : allEmployee) {
			System.out.println(collegeEmployee.getId());
		}
	}
}

// 管理學校總部員工類
class SchoolManager {
	public List<Employee> getAllEmployee() {
		ArrayList<Employee> arrayList = new ArrayList<Employee>();
		for (int i = 0; i < 10; i++) {
			Employee employee = new Employee();
			employee.setId("ID:" + i);
			arrayList.add(employee);
		}
		return arrayList;
	}
	
	void printAllEmployee(CollegeManager collegeManager) {
		collegeManager.printAllEmployee();
		
		List<Employee> allEmployee2 = this.getAllEmployee();
		System.out.println("總部員工...");
		for (Employee employee : allEmployee2) {
			System.out.println(employee.getId());
		}
	}
}

1.2.7 合成複用原則(Composite Reuse Principle)

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

1.3 設計原則核心思想

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

2. 設計模式類型

  1. 創建型模式: 單例 抽象工廠 原型 建造者 工廠
  2. 結構型模式: 適配器 橋接 裝飾者 組合 外觀 享元 代理
  3. 型衛星模式: 模板方法 命令 訪問者 迭代器 觀察者 中介者 備忘錄 解釋器 狀態 策略 職責鏈
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章