主頁 > 軟體設計 > 圖解Java設計模式之設計模式七大原則

圖解Java設計模式之設計模式七大原則

2020-09-13 17:25:56 軟體設計

圖解Java設計模式之設計模式七大原則

    • 2.1 設計模式的目的
    • 2.2 設計模式七大原則
    • 2.3 單一職責原則
      • 2.3.1 基本介紹
      • 2.3.2 應用實體
    • 2.4 介面隔離原則(Interface Segregation Principle)
      • 2.4.1 基本介紹
      • 2.4.2 應用實體
    • 2.5 依賴倒轉原則
      • 2.5.1 基本介紹
      • 2.5.2 應用實體
    • 2.6 里氏替換原則
      • 2.6.1 OO中的繼承性的思考和說明
      • 2.6.2 基本介紹
      • 2.6.3 一個程式引出的問題和思考
      • 2.6.4 解決方法
    • 2.7 開閉原則
      • 2.7.1 基本介紹
      • 2.7.2 看下面一段代碼
      • 2.7.3 方式1的優缺點
      • 2.7.4 改進的思路分析
    • 2.8 迪米特法則
      • 2.8.1 基本介紹
      • 2.8.2 應用實體
      • 2.8.3 應用實體改進
      • 2.8.4 迪米特法則注意事項和細節
    • 2.9 合成復用原則(Composite Reuse Principle)
    • 設計原則核心思想

 

2.1 設計模式的目的

撰寫軟體程序中,程式員面臨著來自耦合性,內聚性以及可維護性,可擴展性,重用性,靈活性等多方面的挑戰,設計模式是為了讓程式(軟體),具有更好
1)代碼重用性(即:相同功能的代碼,不用多次撰寫)
2)可讀性(即:編程規范性,便于其他程式員的閱讀和理解)
3)可擴展性(即:當需要增加新的功能時,非常的方便,稱為可維護)
4)可靠性(即:當我們增加新的功能后,對原來的功能沒有影響)
5)使程式呈現高內聚,低耦合的特性
6)設計模式包含了面向物件的精髓,“懂了設計模式,你就懂了面向物件分析和設計(OOA/D)的精要“
7)Scott Mayers 在其巨著《Effective C++》就曾經說過 :C++老手和C++新手的區別就是前者手背上有很多傷疤

2.2 設計模式七大原則

設計模式原則,其實就是程式員在編程時,應當遵守的原則,也是各種設計模式的基礎(即:設計模式為什么這樣設計的依據)
設計模式常用的七大原則有 :
1)單一職責原則
2)介面隔離原則
3)依賴倒轉(倒置)原則
4)里氏替換原則
5)開閉原則
6)迪米特法則
7)合成復用原則

2.3 單一職責原則

2.3.1 基本介紹

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

2.3.2 應用實體

以交通工具案例講解

package com.example.testdemo.mode.principle;

public class SingleResponsibility1 {

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

}

// 交通工具類

/**
 * 方式1 :
 * 1 , 在方式1的run方法中,違反了單一職責原則
 * 2 , 解決的方案非常第二季簡單,根據交通工具運行方法不同,分解成不同類即可
 */
class Vehicle {

    public void run(String vehicle) {
        System.out.println(vehicle + " 在公路上運行,,,,,");
    }
}
package com.example.testdemo.mode.principle;

public class SingleResponsibility2 {

    public static void main(String[] args) {
        ReadVehicle readVehicle = new ReadVehicle();
        readVehicle.run("摩托車");
        readVehicle.run("汽車");
        AirVehicle airVehicle = new AirVehicle();
        airVehicle.run("飛機");
        WaterVehicle waterVehicle = new WaterVehicle();
        waterVehicle.run("輪船");
    }

}

/**
 * 方案2分析 :
 * 1 :遵守單一職責原則
 * 2 :但是這樣改動大,即將類分解,同時修改客戶端
 * 3 :改進 :直接修改Vehicle類,改動的代碼會比較少 =》方案3
 *
 */
class ReadVehicle {
    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 + "水中運行");
    }
}
package com.example.testdemo.mode.principle;

public class SingleResponsibility3 {

    public static void main(String[] args) {
        Vehicle2 vehicle2 = new Vehicle2();
        vehicle2.run("汽車");
        vehicle2.runAir("飛機");
        vehicle2.runWater("輪船");
    }

}

/**
 * 方案3的分析 :
 * 1 :這種修改方法沒有對原來的類做大的修改,只是增加方法
 * 2 :這里雖然沒有在類這個級別上遵守單一職責原則,但是在方法級別上,仍然是遵守單一職責
 */
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 + " 在水中運行,,,,,");
    }
}

單一職責原則注意事項和細節
(1) 降低類的復雜度,一個類只負責一項職責,
(2)提高類的可讀性,可維護性,
(3)降低變更引起的風險,
(4)通常情況下,我們應當遵守單一職責原則,只有邏輯足夠簡單,才可以在代碼級違反單一職責原則 :只有類中方法數量足夠少,可以在方法級別保存單一職責原則,

2.4 介面隔離原則(Interface Segregation Principle)

2.4.1 基本介紹

(1)客戶端不應該依賴它不需要的介面,即一個類對另一個類的依賴應該建立在最小的介面上
(2)先看一張圖
在這里插入圖片描述
(3)類A通過介面Interface1依賴類B,類C通過介面Interface1依賴類D,如果介面Interface1對于類A和類C來說不是最小介面,那么類B和類C必須去實作他們不需要的方法,
(4)按隔離原則應當這樣處理 :
將介面Interface1拆分為獨立的幾個介面(這里我們拆分3個介面),類A和類C分別與他們需要的介面建立依賴關系,也就是采用介面隔離原則

2.4.2 應用實體

1)類A通過介面Interface1依賴類B,類C通過介面Interface1依賴類D,
2)沒有使用介面隔離原則的代碼

package com.example.testdemo.mode.principle.segregation;

import io.swagger.models.auth.In;

public class Segregation1 {

    public static void main(String[] args) {

    }

}

/**
 * 介面
 */
interface Interface1 {
    void operation1();
    void operation2();
    void operation3();
    void operation4();
    void operation5();
}

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");
    }
}

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");
    }
}

/**
 * A 類通過介面Interface1 依賴(使用)B類,但是只會用到1,2,3方法
 */
class A {
    public void depend1(Interface1 interface1) {
        interface1.operation1();
    }

    public void depend2(Interface1 interface1) {
        interface1.operation2();
    }

    public void depend3(Interface1 interface1) {
        interface1.operation3();
    }
}

/**
 * C 類通過介面Interface1 依賴(使用)D類,但是只會用到1,4,5方法
 */
class C {
    public void depend1(Interface1 interface1) {
        interface1.operation1();
    }

    public void depend4(Interface1 interface1) {
        interface1.operation4();
    }

    public void depend5(Interface1 interface1) {
        interface1.operation5();
    }
}
  • 應傳統方法的問題和介面隔離原則改進
    (1)類A通過Interface1依賴類B,類C通過介面Interface1依賴類D,如果介面Interface1對于類A和類C來說不是最小介面,那么類B和類C必須去實作他們不需要的方法,
    (2)將介面Interface1拆分為獨立的幾個介面,類A和類C分別與他們需要的介面建立依賴關系,也就是采用介面隔離原則,
    (3)介面Interface1中出現的方法,根據實際情況拆分為三個介面
    (4)代碼實作
    在這里插入圖片描述
package com.example.testdemo.mode.principle.segregation1;

public class Segregation2 {

    public static void main(String[] args) {
        // 使用一把
        A a = new A();
        // A 類通過介面去依賴B類
        a.depend1(new B());
        a.depend2(new B());
        a.depend3(new B());

        // C 類通過介面去依賴(使用)D類
        C c = new C();
        c.depend1(new D());
        c.depend4(new D());
        c.depend5(new D());
    }
}

/**
 * 介面
 */
interface Interface1 {
    void operation1();

}

interface Interface2 {
    void operation2();

    void operation3();
}

interface Interface3 {

    void operation4();

    void operation5();
}

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");
    }

}

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");
    }
}

/**
 * A 類通過介面Interface1 ,Interface2 依賴(使用)B類,但是只會用到1,2,3方法
 */
class A {
    public void depend1(Interface1 interface1) {
        interface1.operation1();
    }

    public void depend2(Interface2 interface1) {
        interface1.operation2();
    }

    public void depend3(Interface2 interface1) {
        interface1.operation3();
    }
}

/**
 * C 類通過介面Interface1 ,Interface3 依賴(使用)D類,但是只會用到1,4,5方法
 */
class C {
    public void depend1(Interface1 interface1) {
        interface1.operation1();
    }

    public void depend4(Interface3 interface1) {
        interface1.operation4();
    }

    public void depend5(Interface3 interface1) {
        interface1.operation5();
    }
}

2.5 依賴倒轉原則

2.5.1 基本介紹

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

2.5.2 應用實體

1)方案1 + 分析說明

package com.example.testdemo.mode.principle.inversion;

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接收訊息的功能
 * 方式1分析
 * 1,簡單,比較容易想到
 * 2,如果我們獲取的物件是微信,短信等等,則新增類,同時Persons也要增加相應的接收方法
 * 3,解決思路 :引入一個抽象的介面IReceiver,表示接收者,這樣Person類與介面IReceiver發生依賴
 * 因為Email,微信等等屬于接收的范圍,他們各自實作IReceiver介面就ok,這樣我們就符合依賴倒轉原則
 */
class Person {
    public void receive(Email email) {
        System.out.println(email.getInfo());
    }
}

2)方案2(依賴倒轉)+ 分析說明

package com.example.testdemo.mode.principle.inversion.inprove;

public class DependecyInversion {

    public static void main(String[] args) {
        // 客戶端無需改變
        Person person = new Person();
        person.receive(new Email());

        person.receive(new WeiXin());
    }

}

/**
 * 定義介面
 */
interface IReceiver {
    String getInfo();
}

class Email implements IReceiver{

    @Override
    public String getInfo() {
        return "電子郵件資訊 :hello,world";
    }
}

/**
 * 增加微信
 */
class WeiXin implements IReceiver {

    @Override
    public String getInfo() {
        return "微信訊息 :hello ok";
    }
}

/**
 * 方式2
 */
class Person {
    /**
     * 這里是我們對介面的依賴
     * @param iReceiver
     */
    public void receive(IReceiver iReceiver) {
        System.out.println(iReceiver.getInfo());
    }
}

依賴關系傳遞的三種方式 :
1)介面傳遞
2)構造方法傳遞
3)setter方法傳遞

package com.example.testdemo.mode.principle.inversion.inprove;

public class Dependecy {
    public static void main(String[] args) {
        IOpenAndClose iOpenAndClose = new OpenAndClose();
        iOpenAndClose.open(new ChangHong());

        IOpenAndClose2 iOpenAndClose2 = new OpenAndClose2(new XiaoMi());
        iOpenAndClose2.open();

        IOpenAndClose3 iOpenAndClose3 = new OpenAndClose3();
        iOpenAndClose3.setTv(new SanXing());
        iOpenAndClose3.open();
    }
}

/**
 * 方式1 :通過介面傳遞實作依賴
 */
interface IOpenAndClose {
    /**
     * 抽象方法,接收介面
     * @param tv
     */
    void open(ITV tv);
}

/**
 * ITV介面
 */
interface ITV {
    void play();
}

class ChangHong implements ITV {

    @Override
    public void play() {
        System.out.println("長虹電視機打開");
    }
}

/**
 * 實作介面
 */
class OpenAndClose implements IOpenAndClose {

    @Override
    public void open(ITV tv) {
        tv.play();
    }
}

/**
 * 方式2 :通過構造方法依賴傳遞
 */
interface IOpenAndClose2 {
    /**
     * 抽象方法
     */
    void open();
}

/**
 * ITV介面
 */
interface ITV2 {
    void play();
}

class XiaoMi implements ITV2 {

    @Override
    public void play() {
        System.out.println("小米電視機打開");
    }
}

class OpenAndClose2 implements IOpenAndClose2 {
    /**
     * 成員屬性
     */
    public ITV2 tv;

    /**
     * 構造方法
     * @param itv2
     */
    public OpenAndClose2(ITV2 itv2) {
        this.tv = itv2;
    }

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

/**
 * 方式3,通過setter方法傳遞
 */
interface IOpenAndClose3 {
    /**
     * 抽象方法
     */
    void open();

    void setTv(ITV3 tv);
}

/**
 * ITV介面
 */
interface ITV3 {
    void play();
}

class SanXing implements ITV3 {

    @Override
    public void play() {
        System.out.println("三星電視打開");
    }
}

class OpenAndClose3 implements IOpenAndClose3 {

    private ITV3 itv3;

    @Override
    public void open() {
        this.itv3.play();
    }

    @Override
    public void setTv(ITV3 tv) {
        this.itv3 = tv;
    }

}

依賴倒轉原則的注意事項和細節
1)底層模塊盡力都要有抽象類或介面,或者兩者都有,程式穩定性更好,
2)變數的宣告型別盡量是抽象類或介面,這樣我們的變數參考和實際物件間,就存在一個快取層,利于程式擴展和優化,
3)繼承時遵循里氏替換原則,

2.6 里氏替換原則

2.6.1 OO中的繼承性的思考和說明

1)繼承包含這樣一層含義 :父類中凡是已經實作好的方法,實際上是在設定規范和契約,雖然它不強制要求所有的子類必須遵循這些契約,但是如果子類物件這些已經實作的方法任意修改,就會對整個繼承體系造成破壞,
2)繼承在給程式設計帶來便利的同時,也帶來類弊端,比如使用繼承會給程式帶來侵入性,程式的可移植性降低,增加物件間的耦合性,如果一個類被其他的類所繼承,則當這個類需要修改時,必須考慮到所有的子類,并且父類修改后,所有涉及到子類的功能都有可能產生故障,
3)問題提出 :在編程中,如何正確的使用繼承?=》里氏替換原則

2.6.2 基本介紹

1)里氏替換原則(Liskov Substitution Principle)在1988年,由麻省理工學院的一位姓里的女士提出的,
2)如果對每個型別為T1的物件O1,都有型別為T2的物件O2,使得以T1定義的所有程式P在所有的物件O1都代換成O2時,程式P的行為沒有變化,那么型別T2是型別T1的子型別,換句話說,所有參考基類的地方必須能透明地使用其子類的物件,
3)在使用繼承時,遵循里氏替換原則,在子類中盡量不要重寫父類的方法,
4)里氏替換原則告訴我們,繼承實際上讓兩個類耦合性增強了,在適當的情況下,可以通過聚合、組合、依賴來解決問題,

2.6.3 一個程式引出的問題和思考

該看個程式,思考下問題和解決思路

package com.example.testdemo.mode.principle.liskov;

public class Liskov {
    public static void main(String[] args) {
        A a = new A();
        System.out.println("11 - 3 = " + a.funcl(11, 3));
        System.out.println("1 - 8  = " + a.funcl(1, 8));

        System.out.println("-----------------");
        B b = new B();
        // 這里本意是求出11 - 3
        System.out.println("11 - 3 = " + b.funcl(11, 3));
        // 1 - 8
        System.out.println("1 - 8  = " + b.funcl(1, 8));
        System.out.println("11 + 3 + 9 = " + b.func2(11, 3));
    }
}

class A {
    /**
     * 回傳兩個數的差
     *
     * @param num1
     * @param num2
     * @return
     */
    public int funcl(int num1, int num2) {
        return num1 - num2;
    }
}

/**
 * B類繼承類A
 *
 * 增加類一個新功能 :完成兩個數相加,然后和9 求和
 */
class B extends A {

    /**
     * 這里,重寫類A類的方法,可能是無意識
     * @param a
     * @param b
     * @return
     */
    @Override
    public int funcl(int a, int b) {
        return a + b;
    }

    public int func2(int a, int b) {
        return funcl(a, b) + 9;
    }

}

2.6.4 解決方法

1)我們發現原來運行正常的相減功能發生類錯誤,原因就是類B無意中重寫父類的方法,造成原有功能出現錯誤,在實際編程中,我們常常會通過重寫父類的方法完成新的功能,這樣寫起來雖然簡單,但整個繼承體系的復用性會比較差,特別是運行多型比較頻繁的時候,
2)通用的做法是 :原來的父類和子類都繼承一個更通俗的基類,原有的繼承關系去掉,采用依賴、聚合、組合等關系代替,
3)改進方案,
在這里插入圖片描述

package com.example.testdemo.mode.principle.improve;

public class Liskov {
    public static void main(String[] args) {
        A a = new A();
        System.out.println("11 - 3 = " + a.func1(11, 3));
        System.out.println("1 - 8 = " + a.func1(1, 8));

        System.out.println("--------------------------");
        B b = new B();
        // 因為B類不再繼承A類,因此呼叫者,不會再funcl是求減法
        // 呼叫完成的功能就會很明確
        // 這里本意是求出 11 + 3
        System.out.println("11 + 3 = " + b.func1(11, 3));

        // 1 + 8
        System.out.println("1 + 8 = " + b.func1(1, 8));
        System.out.println("11 + 3 + 9 = " + b.func2(11, 3));

        // 使用組合仍然可以使用到A類相關方法
        // 這里本意是求出 11 - 3
        System.out.println("11 - 3 = " + b.func3(11, 3));
    }
}

/**
 * 創建一個更加基礎的基類
 */
class Base {
    // 把更加基礎的方法和成員寫Base類
}

/**
 * A 類
 */
class A extends Base {

    /**
     * 回傳兩個數的差
     * @param num1
     * @param num2
     * @return
     */
    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}

/**
 * B類 繼承了 A
 *
 * 增加類一個新功能 :完成兩個數相加,然后和9 求和
 */
class B extends Base {

    /**
     * 如果B需要使用A類的方法,使用組合關系
     */
    private A a = new A();

    /**
     * 這里,重寫了A類方法,可能是無意識
     *
     * @param a
     * @param b
     * @return
     */
    public int func1(int a, int b) {
        return a + b;
    }

    public int func2(int a, int b) {
        return func1(a, b) + 9;
    }

    /**
     * 我們仍然想使用A的方法
     * @param a
     * @param b
     * @return
     */
    public int func3(int a, int b) {
        return this.a.func1(a,b);
    }

}

2.7 開閉原則

2.7.1 基本介紹

1)開閉原則(Open Closed Principle)是編程中最基礎、最重要的設計原則
2)一個軟體物體如類,模塊和函式應該對擴展開放(對提供方),對修改關閉(對使用方),用抽象構建框架,用實作擴展細節,
3)當軟體需要變化時,盡量通過擴展軟體物體的行為來實作變化,而不是通過修改已有的代碼來實作變化,
4)編程中遵循其它原則,以及使用設計模式的目的就是遵循開閉原則,

2.7.2 看下面一段代碼

看一個畫圖形的功能,
類圖設計,如下 :
在這里插入圖片描述

package com.example.demo.ocp;

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,來繪制不同的圖形
     * @param shape
     */
    public void drawShape(Shape shape) {
        if (shape.m_type == 1) {
            drawRectangle(shape);
        } else if (shape.m_type == 2) {
            drawCircle(shape);
        } else if (shape.m_type == 3) {
            drawTriangle(shape);
        }
    }

    /**
     * 繪制三角形
     * @param shape
     */
    private void drawTriangle(Shape shape) {
        System.out.println("繪制三角形");
    }

    /**
     * 繪制圓形
     * @param shape
     */
    private void drawCircle(Shape shape) {
        System.out.println("繪制圓形");
    }


    /**
     * 繪制矩形
     * @param shape
     */
    private void drawRectangle(Shape shape) {
        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;
    }
}

2.7.3 方式1的優缺點

1)優點是比較好理解,簡單易操作,
2)缺點是違反了設計模式的ocp原則,即對擴展開放(提供方),對修改關閉(使用方),即當我們給類增加新功能的時候,盡量不修改代碼,或者盡可能少修改代碼,
3)比如我們這時要新增加一個圖形種類 三角形,我們需要做很多修改,修改的地方比較多,

2.7.4 改進的思路分析

思路 : 把創建Shape類做成抽象類,并提供一個抽象的draw方法,讓子類去實作即可,這樣我們有新的圖形種類時,只需要讓新的圖形類繼承Shape,并實作draw方法即可,使用方的代碼就不需要修改 -》
滿足了開閉原則
改進后的代碼 :

package com.example.demo.ocp.improve;

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方法
     * @param shape
     */
    public void drawShape(Shape shape) {
        shape.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("繪制其他圖形");
    }
}

2.8 迪米特法則

2.8.1 基本介紹

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

2.8.2 應用實體

1)有一個學校,下屬有各個學院和總部,現要求列印出學校總部員工ID和學院員工的id
2)編程實作上面的功能,看代碼演示
3)代碼演示

package com.example.demo.demeter;

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

/**
 * 客戶端
 */
public class Demeter1 {

    public static void main(String[] args) {
        // 創建一個 SchoolManager 物件
        SchoolManager schoolManager = new SchoolManager();
        // 輸出學院的員工id 和 學院總部的員工資訊
        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 {

    /**
     * 回傳學院的所有員工
     * @return
     */
    public List<CollegeEmployee> getAllEmployee() {
        List<CollegeEmployee> employees = new ArrayList<>();
        // 這里我們增加了10個員工到list
        for (int i = 0; i < 10; i++) {
            CollegeEmployee collegeEmployee = new CollegeEmployee();
            collegeEmployee.setId("學院員工 id = " + i);
            employees.add(collegeEmployee);
        }
        return employees;
    }
}

/**
 * 學校管理類
 *
 * 分析 SchoolManager 類的直接朋友類有哪些 Employee、CollegeManager
 * CollegeEmployee 不是 直接朋友,而是一個陌生類,這樣違背了迪米特法則
 *
 */
class SchoolManager {

    /**
     * 回傳學校總部的員工
     * @return
     */
    public List<Employee> getAllEmployee() {
        List<Employee> list = new ArrayList<>();
        // 這里我們增加了5個員工到list
        for (int i = 0; i < 5; i++) {
            Employee employee = new Employee();
            employee.setId("學校總部員工 id = " + i);
            list.add(employee);
        }
        return list;
    }

    /**
     * 該方法完成輸出學校總部和學院員工資訊 (id)
     * @param collegeManager
     */
    void printAllEmployee(CollegeManager collegeManager) {
        // 分析問題
        // 1. 這里的 CollegeEmployee 不是 SchoolManageer的直接朋友
        // 2. CollegeEmployee 是以區域變數方式出現在 SchoolManager
        // 3. 違反了 迪米特法則
        // 獲取到學院員工
        List<CollegeEmployee> allEmployee = collegeManager.getAllEmployee();
        System.out.println("-------------學院員工-------------");
        for (CollegeEmployee collegeEmployee : allEmployee) {
            System.out.println(collegeEmployee.getId());
        }
        // 獲取到學院總部員工
        List<Employee> employee = this.getAllEmployee();
        System.out.println("-----------學校總部員工-------------");
        for (Employee employee1 : employee) {
            System.out.println(employee1.getId());
        }
    }
}

2.8.3 應用實體改進

1)前面設計的問題在于SchoolManager中,CollegeEmployee類并不是SchoolManager類的直接朋友(分析)
2)按照迪米特法則,應該避免類中出現這樣非直接朋友關系的耦合
3)對代碼按照迪米特法則進行改進,
4)代碼演示

package com.example.demo.demeter.improve;

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

/**
 * 客戶端
 */
public class Demeter1 {

    public static void main(String[] args) {
        // 創建一個 SchoolManager 物件
        SchoolManager schoolManager = new SchoolManager();
        // 輸出學院的員工id 和 學院總部的員工資訊
        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 {

    /**
     * 回傳學院的所有員工
     * @return
     */
    public List<CollegeEmployee> getAllEmployee() {
        List<CollegeEmployee> employees = new ArrayList<>();
        // 這里我們增加了10個員工到list
        for (int i = 0; i < 10; i++) {
            CollegeEmployee collegeEmployee = new CollegeEmployee();
            collegeEmployee.setId("學院員工 id = " + i);
            employees.add(collegeEmployee);
        }
        return employees;
    }

    /**
     * 輸出學院員工的資訊
     */
    public void printEmployee() {
        // 獲取到學院員工
        List<CollegeEmployee> allEmployee = getAllEmployee();
        System.out.println("----------學院員工-----------");
        for (CollegeEmployee collegeEmployee : allEmployee) {
            System.out.println(collegeEmployee.getId());
        }
    }
}

/**
 * 學校管理類
 *
 * 分析 SchoolManager 類的直接朋友類有哪些 Employee、CollegeManager
 * CollegeEmployee 不是 直接朋友,而是一個陌生類,這樣違背了迪米特法則
 *
 */
class SchoolManager {

    /**
     * 回傳學校總部的員工
     * @return
     */
    public List<Employee> getAllEmployee() {
        List<Employee> list = new ArrayList<>();
        // 這里我們增加了5個員工到list
        for (int i = 0; i < 5; i++) {
            Employee employee = new Employee();
            employee.setId("學校總部員工 id = " + i);
            list.add(employee);
        }
        return list;
    }

    /**
     * 該方法完成輸出學校總部和學院員工資訊 (id)
     * @param collegeManager
     */
    void printAllEmployee(CollegeManager collegeManager) {
        // 分析問題
        // 1. 將輸出學院的員工方法,封裝到CollegeManager
        collegeManager.printEmployee();
        // 獲取到學院總部員工
        List<Employee> employee = this.getAllEmployee();
        System.out.println("-----------學校總部員工-------------");
        for (Employee employee1 : employee) {
            System.out.println(employee1.getId());
        }
    }
}

2.8.4 迪米特法則注意事項和細節

1)迪米特法則的核心是降低類之間的耦合
2)但是注意 :由于每個類都減少了不必要的依賴,因此迪米特法則只是要求降低類間(物件間)耦合關系,并不是要求完全沒有依賴關系,

2.9 合成復用原則(Composite Reuse Principle)

基本介紹 :原則是盡量使用合成/聚合的方式,而不是使用繼承,
在這里插入圖片描述

設計原則核心思想

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

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/26058.html

標籤:設計模式

上一篇:JavaScript-觀察者模式

下一篇:JavaScript-迭代器模式

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more