大型Java進階專題(八) 設計模式之介面卡模式、裝飾者模式、觀察者模式

有夢想的老王發表於2020-07-21

前言

​ 今天開始我們專題的第八課了。本章節將介紹:三個設計模式,介面卡模式、裝飾者模式和觀察者模式。通過學習介面卡模式,可以優雅的解決程式碼功能的相容問題。另外有重構需求的人群一定需要掌握裝飾者模式。本章節參考資料書籍《Spring 5核心原理》中的第一篇 Spring 內功心法(Spring中常用的設計模式)(沒有電子檔,都是我取其精華並結合自己的理解,一個字一個字手敲出來的,如果覺得本文對你有用,請點個推薦)。

介面卡模式

介面卡模式的應用場景

​ 介面卡模式(Adapter Pattern)是指將一個類的介面轉換成客戶期望的另一個介面,使原本的介面不相容的類可以一起工作,屬於結構型設計模式。
介面卡適用於以下幾種業務場景:
​ 1、已經存在的類,它的方法和需求不匹配(方法結果相同或相似)的情況。
​ 2、介面卡模式不是軟體設計階段考慮的設計模式,是隨著軟體維護,由於不同產品、不同廠家造成功能類似而介面不相同情況下的解決方案。有點亡羊補牢的感覺。生活中也非常的應用場景,例如電源插轉換頭、手機充電轉換頭、顯示器轉接頭。

在中國民用電都是 220V 交流電,但我們手機使用的鋰電池使用的 5V 直流電。因此,我 們給手機充電時就需要使用電源介面卡來進行轉換。下面我們有程式碼來還原這個生活場 景,建立 AC220 類,表示 220V 交流電:

package com.study;

/**
 * @author wangzhongyuan
 */
public class AC220 {
    public int outputAC220V(){
        int output = 220;
        System.out.println("提供220V交流電");
        return output;
    }
}

建立5V電源的介面:

package com.study;

/**
 * @author wangzhongyuan
 */
public interface DC5 {
    int output5V();
}

建立提供5V電源的介面卡:

package com.study;

/**
 * @author wangzhongyuan
 */
public class PowerAdapter implements DC5{
    AC220 ac220;

    public PowerAdapter(AC220 ac220) {
        this.ac220 = ac220;
    }

    @Override
    public int output5V() {
        int outputAC220V = ac220.outputAC220V();
        System.out.println("將220v轉換成5v");
        return outputAC220V/44;
    }
}

測試程式碼:

package com.study;

/**
 * @author wang.zhongyuan
 * @version 1.0
 * @date 2020/7/20 11:17 下午
 */
public class DemoTest {

    public static void main(String[] args) {
        DC5 powerAdapter = new PowerAdapter(new AC220());
        powerAdapter.output5V();
    }
}

輸出結果:

從上面的程式碼樣式可以看出,介面卡就是通過增加一個介面卡類持有原有提供者的物件,實現了二者的相容。

適配模式的優缺點

優點:

1、能提高類的透明性和複用,現有的類複用但不需要改變。

2、目標類和介面卡類解耦,提高程式的擴充套件性。

3、在很多業務場景中符合開閉原則。

缺點:

1、介面卡編寫過程需要全面考慮,可能會增加系統的複雜性。

2、增加程式碼閱讀難度,降低程式碼可讀性,過多使用介面卡會使系統程式碼變得凌亂。

裝飾者模式

裝飾者模式的應用場景

​ 裝飾者模式(Decorator Pattern)是指在不改變原有物件的基礎之上,將功能附加到對 象上,提供了比繼承更有彈性的替代方案(擴充套件原有物件的功能),屬於結構型模式。 裝飾者模式在我們生活中應用也比較多如給煎餅加雞蛋;給蛋糕加上一些水果;給房子 裝修等,為物件擴充套件一些額外的職責。裝飾者在程式碼程式中適用於以下場景:

1、用於擴充套件一個類的功能或給一個類新增附加職責。

2、動態的給一個物件新增功能,這些功能可以再動態的撤銷。

程式碼實現

來看一個這樣的場景,上班族白領其實大多有睡懶覺的習慣,每天早上上班都是踩點,於是很多小夥伴為了多賴一會兒床都不吃早餐。那麼,也有些小夥伴可能在上班路上碰 到賣煎餅的路邊攤,都會順帶一個到公司茶水間吃早餐。賣煎餅的大姐可以給你的煎餅 加雞蛋,也可以加香腸。

建立煎餅類:

package com.study;

/**
 * @author wang.zhongyuan
 * @version 1.0
 * @date 2020/7/20 11:57 下午
 */
public class Battercake {

    public String getMsg(){
        return "煎餅";
    }

    public int getPrice(){
        return 5;
    }
}

給煎餅加個蛋:

package com.study;

/**
 * @author wang.zhongyuan
 * @version 1.0
 * @date 2020/7/20 11:59 下午
 */
public class BattercakeWithEgg extends Battercake{
    @Override
    public String getMsg() {
        return super.getMsg() + "加雞蛋";
    }

    @Override
    public int getPrice() {
        return super.getPrice() + 1;
    }
}

再加個烤腸:

package com.study;

/**
 * @author wang.zhongyuan
 * @version 1.0
 * @date 2020/7/21 12:00 上午
 */
public class BatterCakeWithEggAndSausage extends BattercakeWithEgg{
    @Override
    public String getMsg() {
        return super.getMsg() +"加烤腸";
    }

    @Override
    public int getPrice() {
        return super.getPrice() + 3;
    }
}

測試程式碼:

package com.study;

/**
 * @author wang.zhongyuan
 * @version 1.0
 * @date 2020/7/21 12:01 上午
 */
public class DemoTest {

    public static void main(String[] args) {
        BatterCakeWithEggAndSausage batterCakeWithEggAndSausage = new BatterCakeWithEggAndSausage();
        String msg = batterCakeWithEggAndSausage.getMsg();
        int price = batterCakeWithEggAndSausage.getPrice();
        System.out.println("買了:"+msg+",價格為:"+price);
    }
}

輸出結果:

執行結果沒有問題,但是如果使用者需要一個加 2 個雞蛋加 1 根香腸的煎餅,那麼用我們現在的類結構是建立不出來的,也無法自動計算出價格,除非再建立一個類做定製。 如果需求再變,一直加定製顯然是不科學的。那麼下面我們就用裝飾者模式來解決上面 的問題。

建立煎餅抽象類:

package com.study;

/**
 * @author wang.zhongyuan
 * @version 1.0
 * @date 2020/7/21 12:18 上午
 */
public abstract class AbstractBatterCake {
    protected abstract String getMsg();
    protected abstract int getPrice();
}

建立基礎套餐煎餅:

package com.study;

/**
 * @author wang.zhongyuan
 * @version 1.0
 * @date 2020/7/21 12:19 上午
 */
public class BatterCakeWithBase extends AbstractBatterCake{
    @Override
    protected String getMsg() {
        return "煎餅";
    }

    @Override
    protected int getPrice() {
        return 5;
    }
}

建立額外套餐的抽象裝飾類:

package com.study;

/**
 * @author wang.zhongyuan
 * @version 1.0
 * @date 2020/7/21 12:21 上午
 */
public abstract class BatterCakeDecorator extends AbstractBatterCake{
    AbstractBatterCake batterCake;

    public BatterCakeDecorator(AbstractBatterCake batterCake) {
        this.batterCake = batterCake;
    }

    protected abstract void doSomething();

    @Override
    protected String getMsg() {
        return this.batterCake.getMsg();
    }

    @Override
    protected int getPrice() {
        return this.batterCake.getPrice();
    }
}

建立加雞蛋套餐:

package com.study;

/**
 * @author wang.zhongyuan
 * @version 1.0
 * @date 2020/7/20 11:59 下午
 */
public class BattercakeWithEgg extends BatterCakeDecorator{


    public BattercakeWithEgg(AbstractBatterCake batterCake) {
        super(batterCake);
    }

    @Override
    protected void doSomething() {

    }

    @Override
    public String getMsg() {
        return super.getMsg() + "加雞蛋";
    }

    @Override
    public int getPrice() {
        return super.getPrice() + 1;
    }
}

建立加烤腸套餐:

package com.study;

/**
 * @author wang.zhongyuan
 * @version 1.0
 * @date 2020/7/21 12:00 上午
 */
public class BatterCakeWithSausage extends BatterCakeDecorator{

    public BatterCakeWithSausage(AbstractBatterCake batterCake) {
        super(batterCake);
    }

    @Override
    protected void doSomething() {

    }

    @Override
    public String getMsg() {
        return super.getMsg() +"加烤腸";
    }

    @Override
    public int getPrice() {
        return super.getPrice() + 3;
    }
}

測試程式碼:

package com.study;

/**
 * @author wang.zhongyuan
 * @version 1.0
 * @date 2020/7/21 12:01 上午
 */
public class DemoTest {

    public static void main(String[] args) {
        AbstractBatterCake batterCake;
        batterCake = new BatterCakeWithBase();
        batterCake = new BattercakeWithEgg(batterCake);
        batterCake = new BatterCakeWithSausage(batterCake);
        //再加個雞蛋
        batterCake = new BattercakeWithEgg(batterCake);
        System.out.println(batterCake.getMsg()+",價格:"+batterCake.getPrice());
    }
}

輸出結果:

裝飾者模式最本質的特徵是講原有類的附加功能抽離出來,簡化原有類的邏輯。通過這樣兩個案例,我們可以總結出來,其實抽象的裝飾者是可有可無的,具體可以根據業務模型來選擇。

裝飾者模式與介面卡模式對比

裝飾者和介面卡模式都是包裝模式(Wrapper Pattern),裝飾者也是一種特殊的代理模式。

		裝飾者模式 															介面卡模式 
形式 是一種非常特別的介面卡模式 沒有層級關係,		裝飾器模式有層級關係 
定義 裝飾者和被裝飾者都實現同一個介面,					介面卡和被適配者沒有必然的聯絡,通 常是採用繼承或代理的形式進行包裝 
		主要目的是為了擴充套件之後依舊保 留 OOP 關係 
關係 滿足 is-a 的關係 												 滿足 has-a 的關係 
功能 注重覆蓋、擴充套件 							 							注重相容、轉換 
設計 前置考慮 									 							後置考慮

裝飾者模式的優缺點

優點:

1、裝飾者是繼承的有力補充,比繼承靈活,不改變原有物件的情況下動態地給一個物件擴充套件功能,即插即用。

2、通過使用不同裝飾類以及這些裝飾類的排列組合,可以實現不同效果。

3、裝飾者完全遵守開閉原則。

缺點:

1、會出現更多的程式碼,更多的類,增加程式複雜性。

2、動態裝飾時,多層裝飾時會更復雜。

觀察者模式

觀察者模式的應用場景

​ 觀察者模式(Observer Pattern)定義了物件之間的一對多依賴,讓多個觀察者物件同 時監聽一個主體物件,當主體物件發生變化時,它的所有依賴者(觀察者)都會收到通 知並更新,屬於行為型模式。觀察者模式有時也叫做釋出訂閱模式。觀察者模式主要用 於在關聯行為之間建立一套觸發機制的場景。

程式碼實現

​ 小夥伴們肯定都刷過抖音,遇到喜歡的作品都會點個❥喜歡,我們通過模擬喜歡這個事件來實踐下觀察者模式。當你點選喜歡時,會觸發兩個事件,一個喜歡數量會增加+1,二是作者會受到訊息。你可能會想到MQ,非同步佇列等,其實JDK本身就提供這樣的API。

建立事件模型類,用於區分什麼樣的事件,觀察者可以根據不同事件型別做不同處理:

package com.study.demo4;

/**
 * 事件模型
 * @Author wangzhongyuan
 * @Date 2020/7/21 11:28
 * @Version 1.0
 **/
public enum EventModel {
    LIKE_EVENT("喜歡事件",1,null),
    MCOMENT_ENVET("評論事件",2,null)
    ;

    private String message;
    private int type;
    private Object date;

    EventModel(String message, int type, Object date) {
        this.message = message;
        this.type = type;
        this.date = date;
    }}

建立事件類,被觀察者物件,呼叫方可以向該物件中傳送事件:

package com.study.demo4;

import java.util.Observable;

/**
 * 事件匯流排(被觀察者)
 * @Author wangzhongyuan
 * @Date 2020/7/21 11:24
 * @Version 1.0
 **/
public class EventBus extends Observable{
    /**
     * 被觀察者方法
     * @param eventModel
     */
    public void postEvent(EventModel eventModel){
        System.out.println("推送事件");
        setChanged();
        //通知觀察者
        notifyObservers(eventModel);
    }
}

建立不同業務的觀察者:

package com.study.demo4;

import java.util.Observable;
import java.util.Observer;

/**
 * 觀察者1
 *
 * @Author 19054253
 * @Date 2020/7/21 11:32
 * @Version 1.0
 **/
public class OneObserver implements Observer {

    public void update(Observable o, Object arg) {
        System.out.println("觀察1,監聽到了事件,觸發:給作者推送訊息!");
    }
}
package com.study.demo4;

import java.util.Observable;
import java.util.Observer;

/**
 * 觀察者2
 *
 * @Author 19054253
 * @Date 2020/7/21 11:48
 * @Version 1.0
 **/
public class TwoObserver implements Observer {

    public void update(Observable o, Object arg) {
        System.out.println("觀察2,監聽到了事件,觸發:喜歡總數+1");
    }
}

呼叫測試:

package com.study.demo4;

/**
 * @Author wangzhongyuan
 * @Date 2020/7/21 11:35
 * @Version 1.0
 **/
public class DemoTest {
    public static void main(String[] args) {
        //被觀察者
        EventBus eventBus = new EventBus();
        //觀察者
        OneObserver oneObserver = new OneObserver();
        TwoObserver twoObserver = new TwoObserver();
        //監聽觀察者
        eventBus.addObserver(oneObserver);
        eventBus.addObserver(twoObserver);
        //被觀察者觸發事件
        eventBus.postEvent(EventModel.LIKE_EVENT);
    }
}

輸出結果:

從上面程式碼可以看出來,觀察者模式的本質就是,被觀察者物件持有觀察者物件的引用,由被觀察者去通知了觀察者去做了某件事。JDK原始碼中,觀察者模式也應用非常多。例如java.awt.Event就是觀察者模式的一種,只不過Java很少被用來寫桌面程式。以上就是我模擬的事件機制。

觀察模式的優缺點

優點:

1、觀察者和被觀察者之間建立了一個抽象的耦合。
2、觀察者模式支援廣播通訊。

缺點:

1、觀察者之間有過多的細節依賴、提高時間消耗及程式的複雜度。
2、使用要得當,要避免迴圈呼叫。

相關文章