設計模式的七大原則

MXC肖某某發表於2021-08-21

一、設計模式的目的

  設計模式主要是為了解決在編寫程式碼過程中,面臨的耦合性、內聚性、可維護性、可擴充套件行、重用性、靈活性等多方面的挑戰。

  1. 程式碼重用性:相同功能的程式碼不用多次編寫
  2. 可讀性:程式設計規範,便於他人的閱讀和理解
  3. 可擴充套件性:當需要新增新功能時,非常的方便
  4. 可靠性:當增加新的功能後,對原來的功能沒有影響
  5. 最終達到高內聚低耦合的特性

二、設計模式七大原則

  設計模式原則,其實就是程式設計師在程式設計時,應當遵守的原則,也是各種設計模式的基礎。

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

三、單一職責原則

1,基本介紹

  對類來說的,即一個類應該只負責一項職責。如類A負責兩個不同職責:職責1,職責2。當職責1需求變更而改變A時,可能造成職責2執行錯誤,所以需要將類A的粒度分解為A1,A2

2,應用例項

a)方案1

/**
 * 方案1:違反單一職責,所有的交通工具採用同一種方式執行
 */
public class SingleResponsibility1 {
    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 + " 在公路上執行....");
    }
}

b)方案2

/**
 * 方案2的分析
 * 1. 遵守單一職責原則
 * 2. 但是這樣做的改動很大,即將類分解,同時修改客戶端
 * 3. 改進:直接修改Vehicle 類,改動的程式碼會比較少=>方案3
 */
public class SingleResponsibility2 {
    public static void main(String[] args) {
        RoadVehicle roadVehicle = new RoadVehicle();
        roadVehicle.run("摩托車");
        roadVehicle.run("汽車");

        AirVehicle airVehicle = new AirVehicle();
        airVehicle.run("飛機");
    }
}

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

class AirVehicle {
    public void run(String vehicle) {
        System.out.println(vehicle + " 天空執行");
    }
}

c)方案3

/**
 * 方式3的分析
 * 1. 這種修改方法沒有對原來的類做大的修改,只是增加方法
 * 2. 這裡雖然沒有在類這個級別上遵守單一職責原則,但是在方法級別上,仍然是遵守單一職責
 */
public class SingleResponsibility3 {
    public static void main(String[] args) {
        Vehicle2 vehicle2  = new Vehicle2();
        vehicle2.run("汽車");
        vehicle2.runWater("輪船");
        vehicle2.runAir("飛機");
    }
}

class Vehicle2 {
    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 + " 在水中行....");
    }

}

3,注意事項

  • 降低類的複雜度,一個類只負責一項職責
  • 提交類的可讀性,可維護性
  • 降低變更引起的風險
  • 通常情況下,我們應當遵守單一職責原則。只有邏輯足夠簡單,才可以在程式碼級違反單一職責原則;只有類中方法數量足夠少,才可以在方法級保持單一職責原則

四、介面隔離原則

1,基本介紹

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

2,應用案例

  類A通過介面Interface1依賴類B,類C通過介面Interface1依賴類D。但是類A只需要操作類B中的operation1、operation2和operation3,同時類C也只需要操作類D中的operation1、operation4和operation5,但是由於類B和類D都是介面Interface1的實現類,故而都重寫了不需要的方法。

  設計模式的七大原則

 原始碼:基本案例

設計模式的七大原則
public interface Interface1 {
    void operation1();

    void operation2();

    void operation3();

    void operation4();

    void operation5();
}

public abstract class A {
    public void dependecy1(Interface1 interface1) {
        interface1.operation1();
    }
    public void dependecy2(Interface1 interface1) {
        interface1.operation2();
    }
    public void dependecy3(Interface1 interface1) {
        interface1.operation3();
    }
}

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

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

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

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

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

public  class C {
    public void dependecy1(Interface1 interface1) {
        interface1.operation1();
    }
    public void dependecy4(Interface1 interface1) {
        interface1.operation4();
    }
    public void dependecy5(Interface1 interface1) {
        interface1.operation5();
    }
}

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

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

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

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

    @Override
    public void operation5() {
        System.out.println("D實現了operation5");
    }
}
View Code

3,基於介面隔離原則的改進

  將介面Interface1拆分為獨立的幾個介面,類A和類C分別與他們需要的介面建立依賴關係,即採用介面隔離原則。

  設計模式的七大原則

原始碼:介面隔離實現

設計模式的七大原則
public interface Interface1 {
    void operation1();
}

public interface Interface2 {
    void operation2();

    void operation3();
}

public interface Interface3 {
    void operation4();

    void operation5();
}

public class A {
    public void dependecy1(Interface1 interface1) {
        interface1.operation1();
    }

    public void dependecy2(Interface2 interface2) {
        interface2.operation2();
    }

    public void dependecy3(Interface2 interface2) {
        interface2.operation3();
    }
}

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

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

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

public class C {
    public void dependecy1(Interface1 interface1) {
        interface1.operation1();
    }

    public void dependecy4(Interface3 interface3) {
        interface3.operation4();
    }

    public void dependecy5(Interface3 interface3) {
        interface3.operation5();
    }
}

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

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

    @Override
    public void operation5() {
        System.out.println("D實現了operation5");
    }
}
View Code

五、依賴倒轉原則

1,基本介紹

  • 高層模組不應該依賴底層模組,二者都應該依賴其抽象
  • 抽象不應該依賴細節,細節應該依賴抽象
  • 依賴倒轉(倒置)的中心思想是面向介面程式設計
  • 依賴倒轉原則是基於這樣的設計理念:相對於細節的多變性,抽象的東西要穩定的多。以抽象為基礎搭建的架構比以細節為基礎的架構要穩定的多。在java中,抽象指的是介面或抽象類,細節就是具體的實現類
  • 使用介面或抽象類的目的是制定好規範,而不涉及任何具體的操作,把展示細節的任務交給他們的實現類去完成

2,應用案例

a)方案1

/**
 * 方案1:完成Person接受訊息的功能
 *  簡單易懂
 *  如果我們獲取的物件是 微信、簡訊等,則新增類,同時Person也要增加對應的接收方法
 */
public class Person {
    public void receive(Email email){
        System.out.println(email.getInfo());
    }
}

class Email{
    public String getInfo() {
        return "電子郵件資訊:hello word";
    }
}

b)方案2改進

/**
 * 方案2:依賴倒置原則
 *  引入一個抽象的介面IReceiver介面,讓不同的接收訊息方法都實現這個介面重寫getInfo方法
 *  由呼叫方決定傳入的具體物件
 */
public class Person {
    public void receive(IReceiver receiver) {
        System.out.println(receiver.getInfo());
    }
}

interface IReceiver{
    public String getInfo();
}

class Email implements IReceiver{
    @Override
    public String getInfo() {
        return "電子郵件資訊:hello word";
    }
}

class WeiXin implements IReceiver {
    @Override
    public String getInfo() {
        return "微信資訊:hello wechat";
    }
}

3,依賴關係傳遞的三種方式(同Spring DI)

a)介面傳遞

設計模式的七大原則
/**
 * 介面傳遞的方式,將介面ITV傳遞open方法
 */
class OPenAndClose implements IOPenAndClose {
    @Override
    public void open(ITV itv) {
        itv.play();
    }
}

interface IOPenAndClose {
    public void open(ITV itv);
}

interface ITV {
    public void play();
}
View Code

b)構造方法傳遞

設計模式的七大原則
//方式二:構造方法傳遞
interface IOpenAndClose {
    public void open();//抽象方法
}

interface ITV {//ITV介面

    public void play();
}

class OpenAndClose implements IOpenAndClose {
    public ITV tv;//成員

    public OpenAndClose(ITV tv) {//構造方法
        this.tv = tv;
    }

    @Override
    public void open() {
        this.tv.play();
    }
}
View Code

c)setter傳遞

設計模式的七大原則
//方式三:setter方法傳遞
interface IOpenAndClose {
    public void open();//抽象方法
}

interface ITV {//ITV介面

    public void play();
}

class OpenAndClose implements IOpenAndClose {
    private ITV tv;

    public void setTv(ITV tv) {
        this.tv = tv;
    }

    @Override
    public void open() {
        this.tv.play();
    }
}
View Code

4,注意事項和細節

  • 低層模組儘量都要有抽象類和介面,或者兩者都有,提高程式的穩定性
  • 變數的宣告型別儘量時抽象類或介面,這樣我們的變數引用和實際物件間就存在一個緩衝層,利於程式擴充套件和優化
  • 繼承時遵循里氏替換原則

六、里氏替換原則

1,OO中繼承性的思考和說明

  • 繼承:父類中凡是已經實現好的方法,實際上是在設定規範和契約,雖然它不強制要求所有的子類必須遵循這些契約,但是如果子類對這些已經實現的方法任意修改,就會對整個繼承體系造成破壞。
  • 繼承在程式設計上帶來了便利的同時,也帶來了弊端。比如使用繼承會給程式帶來侵入性,程式的可移植性降低,增加物件間的耦合性。如果一個類被其他的類所繼承,則當這個類需要修改時,必須考慮所有的子類,並且父類修改後,所有涉及到子類的功能都有可能產生故障。

2,基本介紹

  • 如果對每一個型別為T1的物件o1,都有型別為T2的物件o2,使得以T1定義的所有程式P在所有的物件o1都代換成o2時,程式P的行為沒有發生變化,那麼型別T2是型別T1的子型別。
  • 所有引用基類的地方必須能透明地使用其子類的物件。
  • 在使用繼承時,遵循里氏替換原則,在子類中儘量不要重寫父類的方法
  • 繼承實際上讓兩個類的耦合性增強了,在適當的情況下,可以通過聚合、組合、依賴來處理

3,應用案例

a)方案1

public class A {
    public int fuc1(int a, int b) {
        return a - b;
    }
}

/**
 * 類B繼承A
 *  由於B無意識重寫了A的fuc1方法,導致最終呼叫時發現預期類A的fuc1不生效
 */
class B extends A {
    public int fuc1(int a, int b) {
        return a + b;
    }

    public int fuc2(int a, int b) {
        return a * b;
    }
}

b)方案2改進

//定義基類
public class Base {
}

class A extends Base {
    public int fuc1(int a, int b) {
        return a - b;
    }
}

/**
 * 類A和類B繼承Base類
 *  B使用組合的方式使用A,這樣fuc3仍然是類A的方法
 */
class B extends Base {
    A a = new A();
    //這裡,重寫了 A 類的方法,  可能是無意識
    public int fuc1(int a, int b) {
        return a + b;
    }

    public int fuc2(int a, int b) {
        return fuc1(a, b) + 9;
    }

    //我們仍然想使用 A 的方法
    public int fuc3(int a, int b) {
        return this.a.fuc1(a, b);
    }
}

七、開閉原則

1,基本介紹

  • 開閉原則是程式設計中最基礎、最重要的設計原則
  • 一個軟體實體如類、模組和函式應該對擴充套件開發(指對提供方開放),對修改關閉(指對使用方關閉)。用抽象構建框架,用實現擴充套件細節
  • 當軟體需要變化時,儘量通過擴充套件軟體實體的行為來實現變化,而不是通過修改已有的程式碼來實現變化。(儘量增加一種功能/擴充套件,而不是修改這部分可能正在使用的功能)
  • 程式設計中遵循其他原則,以及使用設計模式的目的就是遵循開閉原則

2,應用案例

a)方案1

public class OCP {

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

}
//繪圖的類,使用方
class GraphicEditor{
    //根據不同的type繪製不同的圖
    public void drawShape(Shape s) {
        if (s.type == 1) {
            drawRectangle(s);
        } else if (s.type == 2) {
            drawRectangle(s);
        }
    }

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

//基類
abstract class Shape{
    int type;
}

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

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

問題:該方案違反了設計模式的開閉原則,當需要增加一個圖形種類(例:三角形),不僅需要新建類,而且使用方程式碼也需要新增適配

b)方案2改進

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

class GraphicEditor{
    public void draw(Shape s) {
        s.draw();
    }
}

abstract class Shape{
    public abstract void draw();
}

class Rectangle extends Shape {

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

class Circle extends Shape {

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

八、迪米特法則

1,基本介紹

  • 一個物件應該對其他物件保持最少的瞭解
  • 類與類關係越密切,耦合度越大
  • 迪米特法則又叫最少知道原則,即一個類對自己依賴的類知道的越少越好。也就是說,對於被依賴的類不管多麼複雜,都儘量將邏輯封裝在類的內部。對外除了提供public方法,不對外洩露任何資訊
  • 迪米特法則還有個更簡單的定義:只與直接的朋友通訊
  • 直接的朋友:每個物件都會與其他物件有耦合關係,只要兩個物件之間有耦合關係,我們就說這兩個物件之間是朋友關係。耦合的方式很多,依賴、關聯、組合、聚合等。其中,我們成出現成員變數、方法引數、方法返回值中的類為直接朋友,而出現在區域性變數中的類不是直接的朋友。也就是說,陌生的類最好不要以區域性變數的形式出現在類的內部。

2,應用例項

  有一個學校,下屬有各個學院和總部,現要求列印出學校總部員工 ID 和學院員工的 id

a)方案1

public class Demeter {
    public static void main(String[] args) {
        SchoolManage manage = new SchoolManage();
        manage.printAllEmp(new CollegeManage());
    }
}

//學校總部員工
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 CollegeManage {

    public List<CollegeEmployee> getAllEmployee() {
        List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
        for (int i = 0; i < 10; i++) {
            CollegeEmployee emp = new CollegeEmployee();
            emp.setId("學院員工 id= " + i);
            list.add(emp);
        }
        return list;
    }
}

//學校總部管理
class SchoolManage {
    public List<Employee> getAllEmployee() {
        List<Employee> list = new ArrayList<Employee>();
        for (int i = 0; i < 10; i++) {
            Employee emp = new Employee();
            emp.setId("學校總部員工 id= " + i);
            list.add(emp);
        }
        return list;
    }

    //這裡的  CollegeEmployee 不是    SchoolManager 的直接朋友
    //違反了迪米特法則
    public void printAllEmp(CollegeManage collegeManage) {
        List<CollegeEmployee> allEmployee = collegeManage.getAllEmployee();
        System.out.println("------------學院員工------------");
        for (CollegeEmployee e : allEmployee) {
            System.out.println(e.getId());
        }
        //獲取到學校總部員工
        List<Employee> list2 = this.getAllEmployee();
        System.out.println("------------學校總部員工------------");
        for (Employee e : list2) {
            System.out.println(e.getId());
        }

    }
}

問題:由於在SchoolManage中,CollegeEmployee類並不是SchoolManage類的直接朋友。按照迪米特法則,應該避免類中出現這樣非直接朋友關係的耦合

b)方案2

優化:完整程式碼

//學院管理
class CollegeManage {

    public List<CollegeEmployee> getAllEmployee() {
        List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
        for (int i = 0; i < 10; i++) {
            CollegeEmployee emp = new CollegeEmployee();
            emp.setId("學院員工 id= " + i);
            list.add(emp);
        }
        return list;
    }

    //在學院管理中增加遍歷類,方便直接被呼叫
    public void printCollegeEmp() {
        List<CollegeEmployee> allEmployee = this.getAllEmployee();
        System.out.println("------------學院員工------------");
        for (CollegeEmployee e : allEmployee) {
            System.out.println(e.getId());
        }
    }
}

//學校總部管理
class SchoolManage {
    public List<Employee> getAllEmployee() {
        List<Employee> list = new ArrayList<Employee>();
        for (int i = 0; i < 10; i++) {
            Employee emp = new Employee();
            emp.setId("學校總部員工 id= " + i);
            list.add(emp);
        }
        return list;
    }

    public void printAllEmp(CollegeManage collegeManage) {
        collegeManage.printCollegeEmp();
        //獲取到學校總部員工
        List<Employee> list2 = this.getAllEmployee();
        System.out.println("------------學校總部員工------------");
        for (Employee e : list2) {
            System.out.println(e.getId());
        }

    }
}

3,注意事項

  • 迪米特法則的核心是降低類之間的耦合
  • 由於每個類都減少了不必要的依賴,因此迪米特法則只是要求降低類間(物件間)耦合關係,並不是要求完全沒有依賴關係

九、合成複用原則

1,定義

  • 合成複用原則是指儘量使用物件組合/聚合,而不是繼承關係達到軟體複用的目的。可以說使系統更加靈活,降低類與類之間的耦合度,一個類的變化對其他類造成的影響相對較少。
  • 繼承我們叫做白箱複用,相當於把所有的實現細節暴露給子類。組合/聚合也稱之為黑箱複用,對類以外的物件是無法獲取到實現細節的。要根據具體的業務場景來做程式碼設計,其實也都要遵循OOP模型

十、 設計原則核心思想

  • 1)找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的程式碼混在一起。
  • 2)針對介面程式設計,而不是針對實現程式設計。
  • 3)為了互動物件之間的鬆耦合設計而努力

相關文章