設計模式下篇-行爲型

行爲型主要解決類或對象之間交互的經典結構。行爲型的設計模式有觀察者模式,模板模式,策略模式,職責鏈模式,狀態模式,迭代器模式,訪問者模式,備忘錄模式,命令模式,解釋器模式和中介模式。

接下來以3個W和1個H來學習下這十一種設計模式

十一種模式介紹

觀察者模式-Observer

什麼是觀察者模式

在對象之間定義一個一對多的依賴,當一個對象(被觀察者)狀態改變的時候,所有依賴的對象(觀察者)都會自動收到通知。

爲什麼使用觀察者模式

降低被觀察者和觀察者的耦合性,提高易用性。

如何使用觀察者模式

被觀察者,該被觀察者有一組註冊的觀察者,當被觀察者改變時,通知每一個觀察者

// 觀察者
public interface Observer {
    
    void update(Observable o, Object arg);
}

// 被觀察者的封裝類
public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;
    
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }
    
    // something
    
    public void notifyObservers(Object arg) {
        
        Object[] arrLocal;

        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

    // something
    
    protected synchronized void setChanged() {
        changed = true;
    }

}

// 被觀察者
public class MyObservable extends Observable {

    public synchronized void setChanged() {
        super.setChanged();
    }
}

// 調用類
public class Main {

    public static void main(String[] args) {
        // java.util.Observable
        MyObservable observable = new MyObservable();
        observable.addObserver(new java.util.Observer() {
            @Override
            public void update(Observable o, Object arg) {
                System.out.println("A"+arg.toString());
                o.deleteObserver(this);
            }
        });

        observable.addObserver(new java.util.Observer() {
            @Override
            public void update(Observable o, Object arg) {
                System.out.println("B"+arg.toString());
            }
        });

        observable.setChanged();
        observable.notifyObservers("message1");
        observable.setChanged();
        observable.notifyObservers("message2");

    }
}

// 打印如下:
Bmessage1
Amessage1
Bmessage2

額外學習

在jdk9,Observable被廢棄,使用java.beans包下的PropertyChangeEvent和PropertyChangeListener來代替Observer和Observable的功能。

使用觀察者模式實現簡單的EventBus,git倉庫:https://github.com/CNXMBuyu/design-pattern-study.git

  • 改進方案-消息隊列

設計模式核心是對程序進行解耦,在觀察者模式中被觀察者需要添加觀察者,且當發起通知時要遍歷所有的觀察者。爲了使解耦的更加徹底,就出現了消息隊列。

模板模式-Template

什麼是模板模式

將算法整體架構定義好,讓子類在不改變算法整體結構的情況下,重新定義算法中的某些步驟。

爲什麼使用模板模式

代碼複用

如何使用模板模式

定義算法整體架構(抽象類),將需要子類來實現的方法定義爲抽象方法。

public abstract class Player {

    public void initPlayer() {
        // 保存玩家信息
        savePlayer();
        // 初始化玩家數據,因爲每款遊戲都不同,所以需要有子類具體實現
        initData();
    }

    protected void savePlayer() {
        System.out.println("已保存玩家信息");
    }

    /**
     * 初始化玩家數據
     */
    protected abstract void initData();

}

public class GamePlayer extends Player {

    @Override
    protected void initData() {
        System.out.println("初始化遊戲玩家");
    }
}

相似功能:回調

A類事先註冊某個函數F到B類,A類在調用B類的P函數的時候,B類反過來調用A類註冊給它的F函數。

public class Player {

    public void initPlayer(PlayerCallback playerCallback){
        // 保存玩家信息
        savePlayer();
        // 初始化玩家數據,因爲每款遊戲都不同,所以需要有子類具體實現
        playerCallback.initData();
    }

    protected void savePlayer(){
        System.out.println("已保存玩家信息");
    }
}

public interface PlayerCallback {

    /**
     * 初始化玩家數據
     */
    void initData();
}

// 調用類
public class Main {

    public static void main(String[] args) {
        Player player = new Player();
        player.initPlayer(new PlayerCallback() {
            @Override
            public void initData() {
                System.out.println("初始化遊戲玩家數據");
            }
        });
    }
}

回調函數的經典案例:spring的JdbcTemplate

模板模板是使用繼承來實現,而回調函數是使用組合來實現。

策略模式-Strategy

什麼是策略模式

策略模式就是定義一族算法類,將每個算法分別封裝起來,讓它們可以互相替換。

爲什麼使用策略模式

解耦策略的定義、創建、使用這三部分

如何使用策略模式

分爲定義、創建、使用三個步驟。


// 以下爲策略的定義

// 策略接口
public interface Cache {

    void addCache(String key, String content);

    String getCache(String key);
}


// 策略的實現者
public class MemoryCache implements Cache {

    private ConcurrentMap<String, String> cache = new ConcurrentHashMap<>();

    @Override
    public void addCache(String key, String content) {
        cache.put(key, content);
        System.out.println("memory add cache; key:" + key + ", content:" + content);
    }

    @Override
    public String getCache(String key) {
        System.out.println("memory get key:" + key + ", content:" + cache.get(key));
        return cache.get(key);
    }
}

// 策略的實現者
public class RedisCache implements Cache {

    @Override
    public void addCache(String key, String content) {
        System.out.println("redis add cache; key:" + key + ", content:" + content);
    }

    @Override
    public String getCache(String key) {
        String content = "redis get key:" + key;
        System.out.println(content);
        return content;
    }
}

// 策略的創建
public class CacheFactory {

    public Cache getCache(String type){
        String redisType = "redis";
        if(redisType.equals(type)){
            return RedisCacheSingleton.INSTANCE;
        } else {
            return MemoryCacheSingleton.INSTANCE;
        }
    }

    /**
     * 使用內部類的單例
     */
    private static class RedisCacheSingleton{
        private static RedisCache INSTANCE = new RedisCache();
    }

    /**
     * 使用內部類的單例
     */
    private static class MemoryCacheSingleton{
        private static MemoryCache INSTANCE = new MemoryCache();
    }
}

// 策略的使用
public class Main {

    public static void main(String[] args) {
        Cache redisCache = new CacheFactory().getCache("redis");
        redisCache.addCache("a", "a content");
        redisCache.getCache("a");

        Cache memoryCache = new CacheFactory().getCache("memory");
        memoryCache.addCache("a", "a content");
        memoryCache.getCache("a");
    }
}

職責鏈模式-Chain

什麼是職責鏈模式

職責鏈模式是將請求的發送和接收解耦,讓多個接收對象都有機會處理這個請求。將這些接收對象串成一條鏈,並沿着這條鏈傳遞這個請求,直到鏈上的某個接收對象能夠處理它爲止。

爲什麼使用職責鏈模式

將請求的發送和接收解耦。

如何使用職責鏈模式

public abstract class AbstractScript{

    /**
     * 執行初始化
     */
    public abstract void doInit();
}

public class ScriptA extends  AbstractScript {

    @Override
    public void doInit() {
        System.out.println(this.getClass().getName());
    }
}

public class ScriptB extends  AbstractScript {

    @Override
    public void doInit() {
        System.out.println(this.getClass().getName());
    }
}


// 這裏對職責器模式進行了一個小的修改,職責器模式定義“直到鏈上的某個接收對象能夠處理它爲止”,而我們處理它之後依然沒有停止,知道執行結束
public class ScriptChain {

    public void init(List<AbstractScript> initScriptList) {
        // 排序
        Collections.sort(initScriptList);
        // 遍歷執行所有腳本
        for (AbstractScript script : initScriptList) {
            if (script.filter(predicate -> {
                return true;
            })) {
                script.doInit();
            }
        }
    }
}

狀態模式-State

什麼是狀態模式

狀態機又叫有限狀態機,由3個部分組成:狀態、事件、動作。其中,事件也稱爲轉移條件。事件觸發狀態的轉移及動作的執行。

爲什麼使用狀態模式

當狀態條狀觸發的動作比較複雜時,使用狀態模式,可以提高代碼的可維護性。

如何使用狀態模式

訂單有5個狀態,分別爲待付款,已關閉,已付款,已退款,已完成。事件有同意和拒絕。示例代碼簡單沒有動作。

public class Order {

    private OrderStateEnum status =  OrderStateEnum.unpaid;

    public OrderStateEnum getStatus() {
        return status;
    }

    public void setStatus(OrderStateEnum status) {
        this.status = status;
    }
}

public enum OrderStateEnum {

    unpaid, closed, paid, refund, finish
}


// 狀態接口
public interface OrderState {

    default void agree(OrderStateMachine orderStateMachine){
        // nothing
    }

    default void reject(OrderStateMachine orderStateMachine){
        // nothing
    }
}

// 待付款的狀態改變
public class UnpaidOrderState implements OrderState {

    @Override
    public void agree(OrderStateMachine orderStateMachine) {
        // 處理已付款的相關事項
        // .....
        // 狀態調整
        orderStateMachine.setState(new PaidOrderState());
    }

    @Override
    public void reject(OrderStateMachine orderStateMachine) {
        orderStateMachine.setState(new ClosedOrderState());
    }
}

// 已付款的狀態改變
public class PaidOrderState implements OrderState {

    @Override
    public void agree(OrderStateMachine orderStateMachine) {
        orderStateMachine.setState(new FinishOrderState());
    }

    @Override
    public void reject(OrderStateMachine orderStateMachine) {
            orderStateMachine.setState(new RefundedOrderState());
    }
}

// 已退款-最終狀態,沒有任何操作
public class RefundedOrderState implements OrderState {}

// 已關閉-最終狀態,沒有任何操作
public class ClosedOrderState implements OrderState {}

// 已完成-最終狀態,沒有任何操作
public class FinishOrderState implements OrderState {}

// 狀態機
public class OrderStateMachine {

    private OrderState state;

    private static Map<OrderStateEnum, OrderState> orderStateMap = new HashMap<>(10);

    static{
        orderStateMap.put(OrderStateEnum.unpaid, new UnpaidOrderState());
        orderStateMap.put(OrderStateEnum.closed, new ClosedOrderState());
        orderStateMap.put(OrderStateEnum.paid, new UnpaidOrderState());
        orderStateMap.put(OrderStateEnum.refund, new RefundedOrderState());
        orderStateMap.put(OrderStateEnum.finish, new FinishOrderState());
    }

    public OrderStateMachine(Order order){
        state = orderStateMap.get(order.getStatus());
    }

    public void agree(){
        state.agree(this);
    }

    public void reject(){
        state.reject(this);
    }

    public OrderState getState() {
        return state;
    }

    public void setState(OrderState state) {
        this.state = state;
    }
}

// 調用代碼
public class Main {

    public static void main(String[] args) {
        Order order = new Order();
        OrderStateMachine orderStateMachine = new OrderStateMachine(order);
        orderStateMachine.agree();
        System.out.println(orderStateMachine.getState().toString());
        orderStateMachine.agree();
        System.out.println(orderStateMachine.getState().toString());
    }
}

// 打印日誌如下:
cn.hgy.state.PaidOrderState@136432db
cn.hgy.state.FinishOrderState@7382f612

迭代器模式-Iterator

什麼是迭代器模式

迭代器模式將集合對象的遍歷操作從集合類中拆分出來,放到迭代器類中,讓兩者的職責更加單一。

爲什麼使用迭代器模式

  • 迭代器模式封裝集合內部的複雜數據結構,開發者不需要了解如何遍歷,直接使用容器提供的迭代器即可;
  • 迭代器模式將集合對象的遍歷操作從集合類中拆分出來,放到迭代器類中,讓兩者的職責更加單一;
  • 迭代器模式讓添加新的遍歷算法更加容易,更符合開閉原則。除此之外,因爲迭代器都實現自相同的接口,在開發中,基於接口而非實現編程,替換迭代器也變得更加容易。

如何使用迭代器模式

定義迭代器接口和具體的實現。

public interface Iterator<E> {
    
    boolean hasNext();
    
    E next();
}

訪問者模式-Visitor

什麼是訪問者模式

訪問者模式允許一個或者多個操作應用到一組對象上。

爲什麼使用訪問者模式

解耦操作和對象本身,保持類職責單一、滿足開閉原則以及應對代碼的複雜性。

如何使用訪問者模式

public abstract class AbstractFile {

    public abstract void read(IRead read);
}

public class Doc extends AbstractFile {
    @Override
    public void read(IRead read) {
        read.read(this);
    }
}

public class Pdf extends  AbstractFile {
    @Override
    public void read(IRead read) {
        read.read(this);
    }
}

public interface IRead {

    void read(Doc doc);

    void read(Pdf pdf);
}

public class ReadImpl implements IRead {
    @Override
    public void read(Doc doc) {
        System.out.println("doc");
    }

    @Override
    public void read(Pdf pdf) {
        System.out.println("pdf");
    }
}

public class Main {

    public static void main(String[] args) {
        List<AbstractFile> fileList = new ArrayList<>();
        fileList.add(new Doc());
        fileList.add(new Pdf());
        IRead read = new ReadImpl();
        fileList.forEach(abstractFile -> {
            // 編譯會失敗,所以改用abstractFile.read(read);
            // read.read(abstractFile);
            abstractFile.read(read);
        });
    }
}

備忘錄模式-Memento

什麼是備忘錄模式

備忘錄模式也叫快照模式,具體來說,就是在不違背封裝原則的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態,以便之後恢復對象爲先前的狀態。

命令模式-Command

什麼是命令模式

命令模式將請求封裝爲一個對象,這樣可以使用不同的請求參數化其他對象,並且能夠支持請求的排隊執行、記錄日誌、撤銷等附加功能。

爲什麼使用命令模式

解耦行爲請求者與行爲實現者。

在大部分編程語言中,函數是沒法作爲參數傳遞給其他函數的,也沒法賦值給變量。藉助命令模式,我們將函數封裝成對象,這樣就可以實現把函數像對象一樣使用。

如何使用命令模式


public interface Command {
    
    void execute();
}

// 燈
public class Light {

    public void on() {
        System.out.println("燈亮了");
    }
    
    public void off() {
        System.out.println("燈暗了");
    }
}

// 開燈的命令
public class LightOnCommand implements Command {

    private Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }
}

// 關燈的命令
public class LightOffCommand implements Command {

    private Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.off();
    }
}

// 使用類
public class Main {

    public static void main(String[] args) {
        Queue<Command> request = new LinkedList<>();
        Light light = new Light();
        request.add(new LightOnCommand(light));
        request.add(new LightOffCommand(light));

        request.forEach(command -> {
            command.execute();
        });
    }
}

解釋器模式-Interpreter

什麼是解釋器模式

解釋器模式爲某個語言定義它的語法(或者叫文法)表示,並定義一個解釋器用來處理這個語法。

中介模式-Mediator

什麼是中介模式

中介模式定義了一個單獨的對象,來封裝一組對象之間的交互。將這組對象之間的交互委派給與中介對象交互,來避免對象之間的直接交互。

總結

觀察者模式

觀察者將自己註冊到被觀察者中,當被觀察者發生改變時,將改變推送給每一個被觀察中。

可以通過框架或者反射解決註冊到被觀察者中,或者使用消息隊列解耦觀察者和被觀察者;可以通過異步執行觀察者函數。

模板模式

定義一套流程,將其中部分功能放在子類實現。

模板模式需要繼承實現,可以通過回調來實現類似功能,解決繼承問題。

策略模式

定義一組功能相似的算法實現,可以互相替換。

職責鏈模式

職責鏈模式是將請求的發送和接收解耦,讓多個接收對象都有機會處理這個請求。

狀態模式

適用於狀態並不多、狀態轉移也比較簡單,但事件觸發執行的動作包含的業務邏輯可能比較複雜的狀態機。

迭代器模式

迭代器模式用來遍歷集合對象。解耦容器代碼和遍歷代碼。

訪問者模式

訪問者模式允許一個或者多個操作應用到一組對象上。

備忘錄模式

備忘錄模式也叫快照模式,具體來說,就是在不違背封裝原則的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態,以便之後恢復對象爲先前的狀態。

命令模式

命令模式將請求封裝爲一個對象,這樣可以使用不同的請求參數化其他對象,並且能夠支持請求的排隊執行、記錄日誌、撤銷等附加功能。

解釋器模式

解釋器模式爲某個語言定義它的語法(或者叫文法)表示,並定義一個解釋器用來處理這個語法

中介模式

中介模式定義了一個單獨的對象,來封裝一組對象之間的交互。將這組對象之間的交互委派給與中介對象交互,來避免對象之間的直接交互。

關注我

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