Java後端架構師的成長之路(二)——Java設計模式(3)

Java設計模式

23種設計模式

行爲型模式

模板方法模式

豆漿製作問題

  • 製作豆漿的流程:選材—>添加配料—>浸泡—>放到豆漿機打碎。
  • 通過添加不同的配料,可以製作出不同口味的豆漿。
  • 選材、浸泡和放到豆漿機打碎這幾個步驟對於製作每種口味的豆漿都是一樣的。
  • 請使用 模板方法模式 完成:因爲模板方法模式,比較簡單,很容易就想到這個方案,因此就直接使用,不再使用傳統的方案來引出模板方法模式。

模板方法模式基本介紹

  • 模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一個抽象類公開定義了執行它的方法的模板。它的子類可以按需要重寫方法實現,但調用將以抽象類中定義的方式進行。
  • 簡單說,模板方法模式 定義一個操作中的算法的骨架,而將一些步驟延遲到子類中,使得子類可以不改變一個算法的結構,就可以重定義該算法的某些特定步驟。
  • 這種類型的設計模式屬於行爲型模式。

模板方法模式原理類圖

在這裏插入圖片描述

  • AbstractClass 抽象類, 類中實現了模板方法(template),定義了算法的骨架,具體子類需要去實現其它的抽象方法 operationr1,2,3。
  • ConcreteClass 實現抽象方法 operationr1,2,3, 以完成算法中特點子類的步驟。

模板方法模式解決豆漿製作問題

  • UML思路分析:
    在這裏插入圖片描述
  • 代碼實現:
public class TemplateMothodCase {
    public static void main(String[] args) {
        System.out.println("----製作紅豆豆漿----");
        RedBeanSoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
        redBeanSoyaMilk.make();

        System.out.println("----製作花生豆漿----");
        PeanutSoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
        peanutSoyaMilk.make();
    }
}
/**
 * 抽象類:豆漿
 */
abstract class SoyaMilk {
    /**
     * 模板方法make:模板方法可以做成 final, 不讓子類去覆蓋
     */
    final void make() {
        select();
        addCondiments();
        soak();
        beat();
    }
    /**
     * 選材料
     */
    void select() {
        System.out.println("第一步:選擇好的新鮮黃豆");
    }
    /**
     * 添加不同的配料,抽象方法交給子類具體實現
     */
    abstract void addCondiments();
    /**
     * 浸泡
     */
    void soak() {
        System.out.println("第三步:黃豆和配料開始浸泡,需要3小時");
    }
    void beat() {
        System.out.println("第四步:黃豆和配料放到豆漿機打碎");
    }
}
/**
 * 具體的子類
 */
class RedBeanSoyaMilk extends SoyaMilk {
    @Override
    void addCondiments() {
        System.out.println("加入上好的紅豆");
    }
}
class PeanutSoyaMilk extends SoyaMilk {
    @Override
    void addCondiments() {
        System.out.println("加入上好的花生");
    }
}

模板方法模式的鉤子方法

  • 在模板方法模式的父類中,我們可以定義一個方法,它默認不做任何事,子類可以視情況要不要覆蓋它,該方法稱爲“鉤子”。
  • 還是用上面做豆漿的例子來講解,比如,我們還希望製作純豆漿,不添加任何的配料,請使用鉤子方法對前面的模板方法進行改造。
abstract class SoyaMilk {
    /**
     * 模板方法make:模板方法可以做成 final, 不讓子類去覆蓋
     */
    final void make() {
        select();
        if (customerWantCondiments()) {
            addCondiments();
        }
        soak();
        beat();
    }
    // 省略其它...
    /**
     * 鉤子方法,決定是否需要添加配料
     */
    boolean customerWantCondiments() {
        return true;
    }
}
/**
 * 純豆漿
 */
class PureSoyaMilk extends SoyaMilk {
    @Override
    void addCondiments() {
        // 空實現
    }
    @Override
    boolean customerWantCondiments() {
        return false;
    }
}

模板方法模式在Spring框架應用的源碼分析

  • Spring IOC容器初始化時運用到的模板方法模式:obtainFreshBeanFactory、onRefresh
/**
 * 完成IoC容器的創建及初始化工作
 */
@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		// Prepare this context for refreshing.
           // STEP 1: 刷新預處理
		prepareRefresh();

		// Tell the subclass to refresh the internal bean factory.
           // STEP 2:
           // 		a) 創建IoC容器(DefaultListableBeanFactory)
           //		b) 加載解析XML文件(最終存儲到Document對象中)
           //		c) 讀取Document對象,並完成BeanDefinition的加載和註冊工作
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// Prepare the bean factory for use in this context.
           // STEP 3: 對IoC容器進行一些預處理(設置一些公共屬性)
		prepareBeanFactory(beanFactory);

		try {
			// Allows post-processing of the bean factory in context subclasses.
               // STEP 4: 
			postProcessBeanFactory(beanFactory);

			// Invoke factory processors registered as beans in the context.
			// STEP 5: 調用BeanFactoryPostProcessor後置處理器對BeanDefinition處理
               invokeBeanFactoryPostProcessors(beanFactory);

			// Register bean processors that intercept bean creation.
			// STEP 6: 註冊BeanPostProcessor後置處理器
               registerBeanPostProcessors(beanFactory);

			// Initialize message source for this context.
			// STEP 7: 初始化一些消息源(比如處理國際化的i18n等消息源)
               initMessageSource();

			// Initialize event multicaster for this context.
			// STEP 8: 初始化應用事件廣播器
               initApplicationEventMulticaster();

			// Initialize other special beans in specific context subclasses.
			// STEP 9: 初始化一些特殊的bean,是一個鉤子方法
               onRefresh();

			// Check for listener beans and register them.
			// STEP 10: 註冊一些監聽器
               registerListeners();

			// Instantiate all remaining (non-lazy-init) singletons.
			// STEP 11: 實例化剩餘的單例bean(非懶加載方式)
               // 注意事項:Bean的IoC、DI和AOP都是發生在此步驟
               finishBeanFactoryInitialization(beanFactory);

			// Last step: publish corresponding event.
			// STEP 12: 完成刷新時,需要發佈對應的事件
               finishRefresh();
		}
		// 省略...
	}
}
	↓↓↓↓↓
// 也用到了模板方法模式
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
	// 主要是通過該方法完成IoC容器的刷新
	refreshBeanFactory();	
	ConfigurableListableBeanFactory beanFactory = getBeanFactory();
	if (logger.isDebugEnabled()) {
		logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
	}
	return beanFactory;
}
	↓↓↓↓↓
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
@Override
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

在這裏插入圖片描述

模板方法模式的注意事項和細節

  • 基本思想是:算法只存在於一個地方,也就是在父類中,容易修改。需要修改算法時,只要修改父類的模板方法或者已經實現的某些步驟,子類就會繼承這些修改。
  • 實現了最大化代碼複用。父類的模板方法和已實現的某些步驟會被子類繼承而直接使用。
  • 統一了算法,也提供了很大的靈活性。父類的模板方法確保了算法的結構保持不變,同時由子類提供部分步驟的實現。
  • 該模式的不足之處:每一個不同的實現都需要一個子類實現,導致類的個數增加,使得系統更加龐大。
  • 一般模板方法都加上final關鍵字, 防止子類重寫模板方法。
  • 模板方法模式使用場景:當要完成在某個過程,該過程要執行一系列步驟 ,這一系列的步驟基本相同,但其個別步驟在實現時 可能不同,通常考慮用模板方法模式來處理

命令模式

智能生活項目需求

  • 我們買了一套智能家電,有照明燈、風扇、冰箱、洗衣機,我們只要在手機上安裝app就可以控制對這些家電工作。
  • 這些智能家電來自不同的廠家,我們不想針對每一種家電都安裝一個App,分別控制,我們希望只要一個app就可以控制全部智能家電。
  • 要實現一個app控制所有智能家電的需要,則每個智能家電廠家都要提供一個統一的接口給app調用,這時 就可以考慮使用命令模式。
  • 命令模式可將“動作的請求者”從“動作的執行者”對象中解耦出來。
  • 在我們的例子中,動作的請求者是手機app,動作的執行者是每個廠商的一個家電產品。

命令模式基本介紹

  • 命令模式(Command Pattern):在軟件設計中,我們經常需要向某些對象發送請求,但是並不知道請求的接收者是誰,也不知道被請求的操作是哪個,我們只需在程序運行時指定具體的請求接收者即可,此時,可以使用命令模式來進行設計。
  • 命名模式使得請求發送者與請求接收者消除彼此之間的耦合,讓對象之間的調用關係更加靈活,實現解耦。
  • 在命名模式中,會將一個請求封裝爲一個對象,以便使用不同參數來表示不同的請求(即命名),同時命令模式也支持可撤銷的操作。
  • 通俗易懂的理解:將軍發佈命令,士兵去執行。其中有幾個角色將軍(命令發佈者)、士兵(命令的具體執行者)、命令(連接將軍和士兵)。
  • Invoker是調用者(將軍),Receiver是被調用者(士兵),MyCommand是命令,實現了Command接口,持有接收對象。

命令模式的原理類圖

在這裏插入圖片描述

  • Invoker:是調用者角色
  • Command:是命令角色,需要執行的所有命令都在這裏,可以是接口或抽象類
  • Receiver:接受者角色,知道如何實施和執行一個請求相關的操作
  • ConcreteCommand:將一個接受者對象與一個動作綁定,調用接受者相應的操作,實現execute

命令模式解決智能生活項目

  • 思路分析:
    在這裏插入圖片描述
  • 代碼實現:
public class CommandCase {
    public static void main(String[] args) {
        // 創建點燈對象(接收者)
        LightReceiver receiver = new LightReceiver();
        // 創建點燈相關的開關命令
        LightOnCommand lightOnCommand = new LightOnCommand(receiver);
        LightOffCommand lightOffCommand = new LightOffCommand(receiver);
        // 創建一個遙控器
        RemoteController remoteController = new RemoteController();
        // 給遙控器設置命令
        remoteController.setCommand(0, lightOnCommand, lightOffCommand);

        System.out.println("測試遙控器");
        System.out.println("--------按下燈的開按鈕--------");
        remoteController.onButtonWasPushed(0);
        System.out.println("--------按下燈的關按鈕--------");
        remoteController.offButtonWasPushed(0);
        System.out.println("--------按下撤銷按鈕--------");
        remoteController.undoButtonWasPushed();
    }
}
/**
 * 命令接口
 */
interface Command {
    void execute();
    void undo();
}
/**
 * 命令接收者
 */
class LightReceiver {
    public void on() {
        System.out.println("點燈打開了...");
    }
    public void off() {
        System.out.println("點燈關閉了...");
    }
}
/**
 * 具體命令
 */
class LightOnCommand implements Command {
    /**
     * 聚合點燈命令接收者
     */
    private LightReceiver receiver;
    LightOnCommand(LightReceiver receiver) {
        this.receiver = receiver;
    }
    @Override
    public void execute() {
        receiver.on();
    }
    @Override
    public void undo() {
        receiver.off();
    }
}
class LightOffCommand implements Command {
    /**
     * 聚合點燈命令接收者
     */
    private LightReceiver receiver;
    LightOffCommand(LightReceiver receiver) {
        this.receiver = receiver;
    }
    @Override
    public void execute() {
        receiver.off();
    }
    @Override
    public void undo() {
        receiver.on();
    }
}
/**
 * 空命令:用於初始化每個按鈕,當調用空命令時,對象什麼都不做
 * 其實這也是一種設計模式,可以省掉對空的判斷
 */
class NoCommand implements Command {
    @Override
    public void execute() { }
    @Override
    public void undo() { }
}
/**
 * 命令的調用者/發佈者:遙控器
 */
class RemoteController {
    private Command[] onCommands;
    private Command[] offCommands;
    /**
     * 撤銷命令
     */
    private Command undoCommand;
    RemoteController() {
        onCommands = initCommand(5);
        offCommands = initCommand(5);
    }
    private Command[] initCommand(int num) {
        Command[] commands = new Command[num];
        for (int i = 0; i < num; i++) {
            commands[i] = new NoCommand();
        }
        return commands;
    }
    /**
     * 給遙控器按鈕設置需要的命令
     */
    public void setCommand(int no, Command onCommand, Command offCommand) {
        onCommands[no] = onCommand;
        offCommands[no] = offCommand;
    }
    /**
     * 按下開按鈕
     */
    public void onButtonWasPushed(int no) {
        // 找到你按下的 開 的按鈕,並調用對應的方法
        onCommands[no].execute();
        // 記錄這次操作,用於撤銷
        undoCommand = onCommands[no];
    }
    /**
     * 按下關按鈕
     */
    public void offButtonWasPushed(int no) {
        // 找到你按下的 關 的按鈕,並調用對應的方法
        offCommands[no].execute();
        // 記錄這次操作,用於撤銷
        undoCommand = offCommands[no];
    }
    /**
     * 按下撤銷按鈕
     */
    public void undoButtonWasPushed() {
        undoCommand.undo();
    }
}
//測試遙控器
//--------按下燈的開按鈕--------
//點燈打開了
//--------按下燈的關按鈕--------
//點燈關閉了
//--------按下撤銷按鈕--------
//點燈打開了

令模式在Spring框架JdbcTemplate應用的源碼分析

  • Spring框架的JdbcTemplate就使用到了命令模式
  • StatementCallback 接口,類似命令接口(Command)。
@FunctionalInterface
public interface StatementCallback<T> {
	@Nullable
	T doInStatement(Statement stmt) throws SQLException, DataAccessException;
}
  • class QueryStatementCallback implements StatementCallback, SqlProvider:匿名內部類, 實現了命令接口, 同時也充當命令接收者。
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
	@Override
	public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
		return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
	}
	↓↓↓↓↓
	@Override
	@Nullable
	public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
		Assert.notNull(sql, "SQL must not be null");
		Assert.notNull(rse, "ResultSetExtractor must not be null");
		if (logger.isDebugEnabled()) {
			logger.debug("Executing SQL query [" + sql + "]");
		}

		class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
			@Override
			@Nullable
			// !!!!!!!核心
			public T doInStatement(Statement stmt) throws SQLException {
				ResultSet rs = null;
				try {
					rs = stmt.executeQuery(sql);
					return rse.extractData(rs);
				}
				finally {
					JdbcUtils.closeResultSet(rs);
				}
			}
			@Override
			public String getSql() {
				return sql;
			}
		}
		// !!!!!!!核心
		return execute(new QueryStatementCallback());
	}
	↓↓↓↓↓
	@Override
	@Nullable
	public <T> T execute(StatementCallback<T> action) throws DataAccessException {
		Assert.notNull(action, "Callback object must not be null");

		Connection con = DataSourceUtils.getConnection(obtainDataSource());
		Statement stmt = null;
		try {
			stmt = con.createStatement();
			applyStatementSettings(stmt);
			// !!!!核心
			T result = action.doInStatement(stmt);
			handleWarnings(stmt);
			return result;
		}
		catch (SQLException ex) {
			// Release Connection early, to avoid potential connection pool deadlock
			// in the case when the exception translator hasn't been initialized yet.
			String sql = getSql(action);
			JdbcUtils.closeStatement(stmt);
			stmt = null;
			DataSourceUtils.releaseConnection(con, getDataSource());
			con = null;
			throw translateException("StatementCallback", sql, ex);
		}
		finally {
			JdbcUtils.closeStatement(stmt);
			DataSourceUtils.releaseConnection(con, getDataSource());
		}
	}
}
  • 命令調用者是 JdbcTemplate,其中execute(StatementCallback action) 方法中,調用action.doInStatement 方法. 不同的 實現 StatementCallback 接口的對象,對應不同的doInStatemnt 實現邏輯。
  • 另外實現 StatementCallback 命令接口的子類還有 QueryStatementCallback等。
    在這裏插入圖片描述

命令模式的注意事項和細節

  • 將發起請求的對象與執行請求的對象解耦。發起請求的對象是調用者,調用者只要調用命令對象的execute()方法就可以讓接收者工作,而不必知道具體的接收者對象是誰、是如何實現的,命令對象會負責讓接收者執行請求的動作,也就是說【請求發起者】和【請求執行者】之間的解耦是通過命令對象實現的,命令對象起到 了紐帶橋樑的作用。
  • 容易設計一個命令隊列。只要把命令對象放到列隊,就可以多線程的執行命令。
  • 容易實現對請求的撤銷和重做。
  • 命令模式不足:可能導致某些系統有過多的具體命令類,增加了系統的複雜度,這點在在使用的時候要注意。
  • 空命令也是一種設計模式,它爲我們省去了判空的操作。在上面的實例中,如果沒有用空命令,我們每按下一個按鍵都要判空,這給我們編碼帶來一定的麻煩。
  • 命令模式經典的應用場景:界面的一個按鈕都是一條命令、模擬CMD(DOS命令)訂單的撤銷/恢復、觸發-反饋機制

訪問者模式

測評系統的需求

  • 將觀衆分爲男人和女人,對歌手進行測評,當看完某個歌手錶演後,得到他們對該歌手不同的評價(評價有不同的種類,比如 成功、失敗 等)
  • 傳統方案:繼承
  • 如果系統比較小,還是ok的,但是考慮系統增加越來越多新的功能時,對代碼改動較大,違反了ocp原則, 不利於維護。
  • 擴展性不好,比如 增加了 新的人員類型,或者管理方法,都不好做。
  • 引出我們會使用新的設計模式 – 訪問者模式

訪問者模式基本介紹

  • 訪問者模式(Visitor Pattern),封裝一些作用於某種數據結構的各元素的操作,它可以在不改變數據結構的前提下定義作用於這些元素的新的操作。
  • 主要將數據結構與數據操作分離,解決 數據結構和操作耦合性 問題。
  • 訪問者模式的基本工作原理是:在被訪問的類裏面加一個對外提供接待訪問者的接口
  • 訪問者模式主要應用場景是:需要對一個對象結構中的對象進行很多不同操作(這些操作彼此沒有關聯),同時需要避免讓這些操作"污染"這些對象的類,可以選用訪問者模式解決。

訪問者模式的原理類圖

在這裏插入圖片描述

  • Visitor:是抽象訪問者,爲該對象結構中的 ConcreteElement 的每一個類聲明一個visit操作。
  • ConcreteVisitor:是一個具體的訪問值,實現每個由 Visitor 聲明的操作,是每個操作實現的部分。
  • ObjectStructure:能枚舉它的元素, 可以提供一個高層的接口,用來允許訪問者訪問元素。
  • Element:定義一個accept 方法,接收一個訪問者對象。
  • ConcreteElement:爲具體元素,實現了accept 方法。

訪問者模式應用實例

  • 思路分析:
    在這裏插入圖片描述
  • 代碼實現:
public class VisitorCase {
    public static void main(String[] args) {
        // 創建數據結構
        ObjectStructure structure = new ObjectStructure();
        structure.attach(new Male("Tom"));
        structure.attach(new Male("Jack"));
        structure.attach(new Male("Smith"));
        structure.attach(new Female("Lily"));
        structure.attach(new Female("Sari"));

        // 成功
        Succcess succcess = new Succcess();
        structure.display(succcess);
        // 失敗
        Fail fail = new Fail();
        structure.display(fail);
    }
}
/**
 * 抽象訪問者:測評的動作
 */
abstract class Action {
    abstract void getMaleResult(Male male);
    abstract void getFemaleResult(Female female);
}
/**
 * 具體訪問者
 */
class Succcess extends Action {
    @Override
    void getMaleResult(Male male) {
        System.out.println(male.name + ": Yes");
    }
    @Override
    void getFemaleResult(Female female) {
        System.out.println(female.name + ": Yes");
    }
}
class Fail extends Action {
    @Override
    void getMaleResult(Male male) {
        System.out.println(male.name + ": No");
    }
    @Override
    void getFemaleResult(Female female) {
        System.out.println(female.name + ": No");
    }
}
/**
 * 抽象元素:人
 */
abstract class Person {
    public String name;
    Person(String name) {
        this.name = name;
    }
    abstract void accept(Action action);
}
/**
 * 具體的元素:男人和女人
 * 合理我們使用到了雙分派,即首先在客戶端程序中,將具體狀態作爲參數傳遞到了【Male、Female】中
 * 然後【Male、Female】類調用作爲參數的"具體方法"中getXxxResult,同時將自己(this)作爲參數傳入,即第二次分派
 */
class Male extends Person {
    Male(String name) {
        super("[M]" + name);
    }
    @Override
    void accept(Action action) {
        action.getMaleResult(this);
    }
}
class Female extends Person {
    Female(String name) {
        super("[F]" + name);
    }
    @Override
    void accept(Action action) {
        action.getFemaleResult(this);
    }
}
/**
 * 數據結構:管理了很多人(Male、Female)
 */
class ObjectStructure {
    /**
     * 維護了一個集合
     */
    private List<Person> persons = new LinkedList<>();
    /**
     * 增加
     */
    public void attach(Person p) {
        persons.add(p);
    }
    /**
     * 移除
     */
    public void detach(Person p) {
        persons.remove(p);
    }
    /**
     * 顯示
     */
    public void display(Action action) {
        for (Person p : persons) {
            p.accept(action);
        }
    }
}
  • 上面提到了雙分派,所謂雙分派是指不管類怎麼變化,我們都能找到期望的方法運行。雙分派意味着得到執行的操作取決於請求的種類和兩個接收者的類型。
  • 以上述實例爲例,假設我們要添加一個Wait的狀態類,考察Man類和Woman類的反應,由於使用了雙分派,只需增加一個Action子類即可在客戶端調用即可,不需要改動任何其他類的代碼

訪問者模式的注意事項和細節

  • 訪問者模式符合單一職責原則、讓程序具有優秀的擴展性、靈活性非常高。
  • 訪問者模式可以對功能進行統一,可以做報表、UI、攔截器與過濾器,適用於數據結構相對穩定的系統。
  • 具體元素對訪問者公佈細節,也就是說訪問者關注了其他類的內部細節,這是迪米特法則所不建議的,這樣造成了具體元素變更比較困難。
  • 違背了依賴倒轉原則。訪問者依賴的是具體元素,而不是抽象元素。
  • 因此,如果一個系統有比較穩定的數據結構,又有經常變化的功能需求,那麼訪問者模式就是比較合適的。

迭代器模式

看一個具體的需求

  • 編寫程序展示一個學校院系結構:需求是這樣,要在一個頁面中展示出學校的院系組成,一個學校有多個學院,一個學院有多個系。
  • 傳統設計方案:將學院看做是學校的子類,系是學院的子類,這樣實際上是站在組織大小來進行分層次的。
  • 實際上我們的要求是:在一個頁面中展示出學校的院系組成,一個學校有多個學院,一個學院有多個系, 因此這種方案,不能很好實現的遍歷的操作
  • 解決方案:迭代器模式
    在這裏插入圖片描述

迭代器模式基本介紹

  • 迭代器模式(Iterator Pattern)是常用的設計模式,屬於行爲型模式。
  • 如果我們的集合元素是用不同的方式實現的,有數組,還有java的集合類,或者還有其他方式,當客戶端要遍歷這些集合元素的時候就要使用多種遍歷方式,而且還會暴露元素的內部結構,可以考慮使用迭代器模式解決。
  • 迭代器模式,提供一種遍歷集合元素的統一接口,用一致的方法遍歷集合元素,不需要知道集合對象的底層表示,即:不暴露其內部的結構。

迭代器模式的原理類圖

在這裏插入圖片描述

  • Iterator:迭代器接口,是系統提供,含義 hasNext、next、remove。
  • ConcreteIterator:具體的迭代器類,管理迭代。
  • Aggregate:一個統一的聚合接口,將客戶端和具體聚合解耦。
  • ConcreteAggregate:具體的聚合,持有對象集合,並提供一個方法,返回一個迭代器,該迭代器可以正確遍歷集合。
  • Client:客戶端,通過 Iterator 和 Aggregate 依賴子類。

迭代器模式應用實例

  • 思路分析:
    在這裏插入圖片描述
  • 代碼實現:
public class IteratorCase {
    public static void main(String[] args) {
        ComputerCollege computerCollege = new ComputerCollege();
        computerCollege.addDepartment("Java專業", "學習Java");
        computerCollege.addDepartment("Php專業", "學習Php");
        computerCollege.addDepartment("大數據專業", "學習大數據");

        InfoCollege infoCollege = new InfoCollege();
        infoCollege.addDepartment("信息安全專業", "學習信息安全");
        infoCollege.addDepartment("網絡安全專業", "學習網絡安全");

        List<College> colleges = new ArrayList<>();
        colleges.add(computerCollege);
        colleges.add(infoCollege);

        OutputImpl output = new OutputImpl(colleges);
        output.printCollege();
    }
}
/**
 * 系
 */
@Data
@AllArgsConstructor
class Department {
    private String name;
    private String desc;
}
/**
 * 計算機學院迭代器
 */
class ComputerCollegeIterator implements Iterator<Department> {
    /**
     * 這裏我們定義 Department 是以數組形式存放
     */
    private Department[] departments;
    /**
     * 遍歷的位置
     */
    private int position;
    ComputerCollegeIterator(Department[] departments) {
        this.departments = departments;
    }
    @Override
    public boolean hasNext() {
        return position < departments.length && departments[position] != null;
    }
    @Override
    public Department next() {
        Department department = departments[position];
        position += 1;
        return department;
    }
    @Override
    public void remove() {
    }
}
/**
 * 信息工程學院迭代器
 */
class InfoCollegeIterator implements Iterator<Department> {
    /**
     * 這裏我們定義 Department 是以List集合形式存放
     */
    private List<Department> departments;
    /**
     * 遍歷的索引位置
     */
    private int index = -1;
    InfoCollegeIterator(List<Department> departments) {
        this.departments = departments;
    }
    @Override
    public boolean hasNext() {
        if (index >= departments.size() - 1) {
            return false;
        } else {
            index += 1;
            return true;
        }
    }
    @Override
    public Department next() {
        return departments.get(index);
    }
    @Override
    public void remove() {
    }
}
/**
 * 聚合接口:學院
 */
interface College {
    String getName();
    void addDepartment(String name, String desc);
    Iterator<Department> createIterator();
}
/**
 * 計算機學院
 */
class ComputerCollege implements College {
    private Department[] departments;
    /**
     * 保存當前數組對象的個數
     */
    private int numOfDep;
    ComputerCollege() {
        departments = new Department[5];
    }
    @Override
    public String getName() {
        return "計算機學院";
    }
    @Override
    public void addDepartment(String name, String desc) {
        departments[numOfDep++] = new Department(name, desc);
    }
    @Override
    public Iterator<Department> createIterator() {
        return new ComputerCollegeIterator(departments);
    }
}
/**
 * 信息工程學院
 */
class InfoCollege implements College {
    private List<Department> departments;
    InfoCollege() {
        departments = new ArrayList<>();
    }
    @Override
    public String getName() {
        return "信息工程學院";
    }
    @Override
    public void addDepartment(String name, String desc) {
        departments.add(new Department(name, desc));
    }
    @Override
    public Iterator<Department> createIterator() {
        return new InfoCollegeIterator(departments);
    }
}
/**
 * 輸出
 */
class OutputImpl {
    /**
     * 學院集合
     */
    private List<College> colleges;
    OutputImpl(List<College> colleges) {
        this.colleges = colleges;
    }
    /**
     * 遍歷所有學院,調用 printDepartment
     */
    public void printCollege() {
        Iterator<College> iterator = colleges.iterator();
        while (iterator.hasNext()) {
            College college = iterator.next();
            System.out.println("----" + college.getName() + "----");
            printDepartment(college.createIterator());
        }
    }
    private void printDepartment(Iterator<Department> iterator) {
        while (iterator.hasNext()) {
            Department department = iterator.next();
            System.out.println(department.getName());
        }
    }
}

迭代器模式在JDK-ArrayList集合應用的源碼分析

  • JDK的ArrayList 集合中就使用了迭代器模式
public class JDKIterator {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("jack");
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

在這裏插入圖片描述
在這裏插入圖片描述

  • 內部類 Itr 充當具體實現迭代器 Iterator 的類, 作爲 ArrayList 內部類。
  • List 就是充當了聚合接口,含有一個 iterator() 方法,返回一個迭代器對象。
  • ArrayList 是實現聚合接口 List 的子類,實現了 iterator()。
  • Iterator 接口系統提供。
  • 迭代器模式解決了不同集合(ArrayList, LinkedList) 統一遍歷問題

迭代器模式的注意事項和細節

  • 提供一個統一的方法遍歷對象,客戶不用再考慮聚合的類型,使用一種方法就可以遍歷對象了。
  • 隱藏了聚合的內部結構,客戶端要遍歷聚合的時候只能取到迭代器,而不會知道聚合的具體組成。提供了一種設計思想,就是一個類應該只有一個引起變化的原因(叫做單一責任原則)。在聚合類中,我們把迭代器分開,就是要把管理對象集合遍歷對象集合的責任分開,這樣一來集合改變的話,隻影響到聚合對象。而如果遍歷方式改變的話,隻影響到了迭代器。
  • 當要展示一組相似對象,或者遍歷一組相同對象時使用,適合使用迭代器模式。
  • 缺點:每個聚合對象都要一個迭代器,會生成多個迭代器不好管理類。

觀察者模式

天氣預報項目需求

  • 氣象站可以將每天測量到的溫度、溼度、氣壓等等以公告的形式發佈出去(比如發佈到自己的網站或第三方)。
  • 需要設計開放型API,便於其他第三方也能接入氣象站獲取數據。
  • 提供溫度、氣壓和溼度的接口。
  • 測量數據更新時,要能實時的通知給第三方。

普通方案

  • 通過對氣象站項目的分析,我們可以初步設計出一個WeatherData類。
    在這裏插入圖片描述
  • 通過getXxx方法,可以讓第三方接入,並得到相關信息。
  • 當數據有更新時,氣象站通過調用dataChange() 去更新數據,當第三方再次獲取時,就能得到最新數據,當然也可以推送。
    在這裏插入圖片描述
  • 代碼實現:
public class ObserverCase {
    public static void main(String[] args) {
        // 創建接入方 currentConditions
        CurrentConditions conditions = new CurrentConditions();
        // 創建 WeatherData 並將 接入方 currentConditions 傳遞到 WeatherData 中
        WeatherData weatherData = new WeatherData(conditions);
        // 更新天氣情況
        weatherData.setData(30, 150, 40);

        //天氣情況變化 
        System.out.println("============天氣情況變化=============");
        weatherData.setData(40, 160, 20);
    }
}
/**
 * 顯示當前天氣情況(可以理解成是氣象站自己的網站)
 */
class CurrentConditions {
    /**
     * 溫度、氣壓、溼度
     */
    private float temperature;
    private float pressure;
    private float humidity;
    /**
     * 更新 天氣情況,是由 WeatherData 來調用 ==>> 使用推送模式
     */
    public void update(float temperature, float pressure, float humidity) {
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;

        display();
    }
    /**
     * 顯示
     */
    private void display() {
        System.out.println("***Today temperature: " + temperature + "***");
        System.out.println("***Today pressure: " + pressure + "***");
        System.out.println("***Today humidity: " + humidity + "***");
    }
}
/**
 * 類是核心
 * 1. 包含最新的天氣情況信息
 * 2. 含有 CurrentConditions 對象
 * 3. 當數據有更新時,就主動的調用 CurrentConditions 對象 update 方法(含 display), 這樣他們(接入方)就看到最新的信息
 */
class WeatherData {
    private float temperatrue;
    private float pressure;
    private float humidity;
    /**
     * 加入新的第三方
     */
    private CurrentConditions currentConditions;
    public WeatherData(CurrentConditions currentConditions) {
        this.currentConditions = currentConditions;
    }
    /**
     * 當數據有更新時,就調用 setData
     */
    public void setData(float temperature, float pressure, float humidity) {
        this.temperatrue = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        //調用 dataChange, 將最新的信息 推送給 接入方 currentConditions
        dataChange();
    }
    private void dataChange() {
        //調用接入方的 update
        currentConditions.update(getTemperature(), getPressure(), getHumidity());
    }
    private float getTemperature() {
        return temperatrue;
    }
    private float getPressure() {
        return pressure;
    }
    private float getHumidity() {
        return humidity;
    }
}
  • 問題分析:其他第三方接入氣象站獲取數據的問題時,無法在運行時動態的添加第三方 (新浪網站),違反ocp原則=>*觀察者模式
  • //在WeatherData中,當增加一個第三方,都需要創建一個對應的第三方的公告板對象,並加入到dataChange, 不利於維護,也不是動態加入
public void dataChange() {
	currentConditions.update(getTemperature(), getPressure(), getHumidity());
}

觀察者模式(Observer)原理

  • 觀察者模式類似訂牛奶業務:奶站/氣象局Subject,用戶/第三方網站Observer。
  • Subject:登記註冊、移除和通知
    在這裏插入圖片描述
* registerObserver 註冊
* removeObserver 移除
* notifyObservers() 通知所有的註冊的用戶,根據不同需求,可以是更新數據,讓用戶來取,也可能是實施推送,看具體需求定
  • Observer:接收輸入
    在這裏插入圖片描述
  • 觀察者模式:對象之間多對一依賴的一種設計方案,被依賴的對象爲Subject,依賴的對象爲Observer,Subject通知Observer變化,比如這裏的牛奶站是Subject,是一的一方。用戶時Observer,是多的一方。
  • 原理UML類圖:
    在這裏插入圖片描述

觀察者模式解決天氣預報需求

  • 思路分析:
    在這裏插入圖片描述
  • 代碼實現:
public class ObserverCase {
    public static void main(String[] args) {
        // 創建一個 WeatherData
        WeatherData weatherData = new WeatherData();
        // 創建觀察者
        CurrentCondition currentConditions = new CurrentCondition();
        BaiduSite baiduSite = new BaiduSite();

        // 註冊到 weatherData
        weatherData.registerObserver(currentConditions);
        weatherData.registerObserver(baiduSite);

        // 測試
        System.out.println("通知各個註冊的觀察者, 看看信息");
        weatherData.setData(10f, 100f, 30.3f);

        // 測試移除
        weatherData.removeObserver(currentConditions);
        System.out.println(); System.out.println("通知各個註冊的觀察者, 看看信息");
        weatherData.setData(10f, 100f, 30.3f);
    }
}
/**
 * 目標接口
 */
interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}
/**
 * 觀察者接口
 */
interface Observer {
    void update(float temperature, float pressure, float humidity);
}
/**
 * 具體目標-核心:天氣信息
 * 1. 包含最新的天氣情況信息
 * 2. 含有 觀察者集合,使用 ArrayList 管理
 * 3. 當數據有更新時,就主動的調用 ArrayList, 通知所有的(接入方)就看到最新的信息
 */
class WeatherData implements Subject {
    private float temperatrue;
    private float pressure;
    private float humidity;
    /**
     * 觀察者集合
     */
    private List<Observer> observers = new ArrayList<>();

    public void setData(float temperature, float pressure, float humidity) {
        this.temperatrue = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        //調用 dataChange, 將最新的信息 推送給 接入方 currentConditions
        dataChange();
    }
    private void dataChange() {
        notifyObservers();
    }
    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }
    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }
    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperatrue, pressure, humidity);
        }
    }
}
/**
 * 具體觀察者
 */
class CurrentCondition implements Observer {
    /**
     * 溫度、氣壓、溼度
     */
    private float temperature;
    private float pressure;
    private float humidity;
    /**
     * 更新 天氣情況,是由 WeatherData 來調用 ==>> 使用推送模式
     */
    @Override
    public void update(float temperature, float pressure, float humidity) {
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;

        display();
    }
    /**
     * 顯示
     */
    private void display() {
        System.out.println("====氣象局====");
        System.out.println("***Today temperature: " + temperature + "***");
        System.out.println("***Today pressure: " + pressure + "***");
        System.out.println("***Today humidity: " + humidity + "***");
    }
}
class BaiduSite implements Observer {
    private float temperature;
    private float pressure;
    private float humidity;
    @Override
    public void update(float temperature, float pressure, float humidity) {
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        display();
    }
    private void display() {
        System.out.println("====百度網站====");
        System.out.println("***百度網站 氣溫 : " + temperature + "***");
        System.out.println("***百度網站 氣壓: " + pressure + "***");
        System.out.println("***百度網站 溼度: " + humidity + "***");
    }
}

觀察者模式在Jdk應用的源碼分析

  • Jdk的 Observable 類就使用了觀察者模式
public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;
    public Observable() {
        obs = new Vector<>();
    }
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
    public void notifyObservers() {
        notifyObservers(null);
    }
    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);
    }
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }
    protected synchronized void setChanged() {
        changed = true;
    }
    protected synchronized void clearChanged() {
        changed = false;
    }
    public synchronized boolean hasChanged() {
        return changed;
    }
    public synchronized int countObservers() {
        return obs.size();
    }
}
	↓↓↓↓↓
public interface Observer {
    void update(Observable o, Object arg);
}
  • Observable 的作用和地位等價於 我們前面講過Subject。
  • Observable 是類,不是接口,類中已經實現了核心的方法 ,即管理Observer的方法 add… delete … notify…。
  • Observer 的作用和地位等價於我們前面講過的 Observer,有update方法。
  • Observable 和 Observer 的使用方法和前面講過的一樣,只是Observable 是類,通過繼承來實現觀察者模式

中介者模式

智能家庭管理問題

  • 智能家庭包括各種設備,鬧鐘、咖啡機、電視機、窗簾等。
  • 主人要看電視時,各個設備可以協同工作,自動完成看電視的準備工作,比如流程爲:鈴響起 => 咖啡機開始做咖啡 => 窗簾自動落下 => 電視機開始播放。

中介者模式基本介紹

  • 中介者模式(MediatorPattern),用一箇中介對象來封裝一系列的對象交互。中介者使各個對象不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的交互。
  • 中介者模式屬於行爲型模式,使代碼易於維護。
  • 比如MVC模式,C(Controller控制器)是M(Model模型)和V(View視圖)的中介者,在前後端交互時起到了中間人的作用。

中介者模式的原理類圖

在這裏插入圖片描述

  • Mediator:就是抽象中介者,定義了同事對象到中介者對象的接口。
  • Colleague:是抽象同事類。
  • ConcreteMediator:具體的中介者對象, 實現抽象方法, 他需要知道所有的具體的同事類,即以一個集合來管理HashMap,並接受某個同事對象消息,完成相應的任務。
  • ConcreteColleague:具體的同事類,會有很多, 每個同事只知道自己的行爲, 而不瞭解其他同事類的行爲(方法),但是他們都依賴中介者對象。

中介者模式應用實例

  • 思路分析:
    在這裏插入圖片描述
  • 代碼實現:
public class MediatorCase {
    public static void main(String[] args) {
        // 創建一箇中介者對象
        Mediator mediator = new ConcreteMediator();
        // 創建一個 Alarm 並加入到 ConcreteMediator 的Map中
        Alarm alarm = new Alarm(mediator, "alarm");
        // 創建一個 CoffeeMachine 並加入到 ConcreteMediator 的Map中
        CoffeeMachine coffeeMachine = new CoffeeMachine(mediator, "coffeeMachine");
        // 創建一個 Curtains 並加入到 ConcreteMediator 的Map中
        Curtains curtains = new Curtains(mediator, "curtains");
        TV tv = new TV(mediator, "tv");
        // 讓鬧鐘發出消息
        alarm.sendMessage(0);
        coffeeMachine.finishCoffee();
        alarm.sendMessage(1);
    }
}
/**
 * 中介者:抽象類
 */
abstract class Mediator {
    /**
     * 將一個同事類加入到集合
     */
    abstract void register(String colleagueName, Colleague colleague);
    /**
     * 接收消息,具體的同事對象發出
     */
    abstract void getMessage(int stateChange, String colleagueName);
    abstract void sendMessage();
}
/**
 * 具體的中介者類
 */
class ConcreteMediator extends Mediator {
    /**
     * 集合:放入了所有的同事類
     */
    private Map<String, Colleague> colleagueMap;
    private Map<String, String> interMap;
    ConcreteMediator() {
        colleagueMap = new HashMap<>();
        interMap = new HashMap<>();
    }
    @Override
    void register(String colleagueName, Colleague colleague) {
        colleagueMap.put(colleagueName, colleague);
        if (colleague instanceof Alarm) {
            interMap.put("Alarm", colleagueName);
        } else if (colleague instanceof CoffeeMachine) {
            interMap.put("CoffeeMachine", colleagueName);
        } else if (colleague instanceof TV) {
            interMap.put("TV", colleagueName);
        } else if (colleague instanceof Curtains) {
            interMap.put("Curtains", colleagueName);
        }
    }
    @Override
    void getMessage(int stateChange, String colleagueName) {
        Colleague colleague = colleagueMap.get(colleagueName);
        if (colleague instanceof Alarm) {
            if (stateChange == 0) {
                ((CoffeeMachine) colleagueMap.get(interMap.get("CoffeeMachine"))).startCoffee();
                ((TV) colleagueMap.get(interMap.get("TV"))).startTV();
            } else if (stateChange == 1) {
                ((TV) colleagueMap.get(interMap.get("TV"))).stopTV();
            }
        } else if (colleague instanceof CoffeeMachine) {
            ((Curtains) colleagueMap.get(interMap.get("Curtains"))).upCurtains();
        } else if (colleague instanceof TV) {
            // ...
        } else if (colleague instanceof Curtains) {
            // ...
        }
    }
    @Override
    void sendMessage() {

    }
}
/**
 * 同事:抽象類
 */
abstract class Colleague {
    private Mediator mediator;
    protected String name;
    Colleague(Mediator mediator, String name) {
        this.mediator = mediator;
        this.name = name;
    }
    public Mediator getMediator() {
        return mediator;
    }
    abstract void sendMessage(int stateChange);
}
/**
 * 具體的同事類
 */
class Alarm extends Colleague {
    Alarm(Mediator mediator, String name) {
        super(mediator, name);
        // 在創建Alarm對象時,將自己放入到ConcreteMediator對象中
        mediator.register(name, this);
    }
    @Override
    void sendMessage(int stateChange) {
        // 調用中介者對象的 getMessage
        this.getMediator().getMessage(stateChange, this.name);
    }
    public void sendAlarm(int stateChange) {
        this.sendMessage(stateChange);
    }
}
class CoffeeMachine extends Colleague {
    CoffeeMachine(Mediator mediator, String name) {
        super(mediator, name);
        mediator.register(name, this);
    }
    @Override
    void sendMessage(int stateChange) {
        this.getMediator().getMessage(stateChange, this.name);
    }
    public void startCoffee() {
        System.out.println("It's time to start coffee!");
    }
    public void finishCoffee() {
        System.out.println("After 5 minutes!");
        System.out.println("Coffee is OK!");
        sendMessage(0);
    }
}
class TV extends Colleague {
    TV(Mediator mediator, String name) {
        super(mediator, name);
        mediator.register(name, this);
    }
    @Override
    void sendMessage(int stateChange) {
        this.getMediator().getMessage(stateChange, this.name);
    }
    public void startTV() {
        System.out.println("It's time to Start TV!");
    }
    public void stopTV() {
        System.out.println("Stop TV!");
    }
}
class Curtains extends Colleague {
    Curtains(Mediator mediator, String name) {
        super(mediator, name);
        mediator.register(name, this);
    }
    @Override
    void sendMessage(int stateChange) {
        this.getMediator().getMessage(stateChange, this.name);
    }
    public void upCurtains() {
        System.out.println("I am holding Up Curtains!");
    }
}
//It's time to start coffee!
//It's time to Start TV!
//After 5 minutes!
//Coffee is OK!
//I am holding Up Curtains!
//Stop TV!

中介者模式的注意事項和細節

  • 多個類相互耦合,會形成網狀結構, 使用中介者模式將網狀結構分離爲星型結構,進行解耦。
  • 減少類間依賴,降低了耦合,符合迪米特原則。
  • 中介者承擔了較多的責任,一旦中介者出現了問題,整個系統就會受到影響
  • 如果設計不當,中介者對象本身變得過於複雜,這點在實際使用時,要特別注意。

備忘錄模式

遊戲角色狀態恢復問題

  • 遊戲角色有攻擊力和防禦力,在大戰Boss前保存自身的狀態(攻擊力和防禦力),當大戰Boss後攻擊力和防禦力下降,從備忘錄對象恢復到大戰前的狀態。

傳統方案解決遊戲角色恢復

在這裏插入圖片描述

  • 一個對象,就對應一個保存對象狀態的對象, 這樣當我們遊戲的對象很多時,不利於管理,開銷也很大。
  • 傳統的方式是簡單地做備份,new出另外一個對象出來,再把需要備份的數據放到這個新對象,但這就暴露了對象內部的細節。
  • 解決方案:=>備忘錄模式。

備忘錄模式基本介紹

  • 備忘錄模式(Memento Pattern)在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態。這樣以後就可將該對象恢復到原先保存的狀態。
  • 可以這裏理解備忘錄模式:現實生活中的備忘錄是用來記錄某些要去做的事情,或者是記錄已經達成的共同意見的事情,以防忘記了。而在軟件層面,備忘錄模式有着相同的含義,備忘錄對象主要用來記錄一個對象的某種狀態,或者某些數據,當要做回退時,可以從備忘錄對象裏獲取原來的數據進行恢復操作。

備忘錄模式原理類圖

在這裏插入圖片描述

  • Originator:對象(需要保存狀態的對象)
  • Memento:備忘錄對象,負責保存好記錄,即Originator內部狀態
  • Caretaker:守護者對象,負責保存多個備忘錄對象,使用集合管理,提高效率
  • 如果希望保存多個originator對象的不同時間的狀態,也可以,只需要要 HashMap <String, 集合>

備忘錄模式原理代碼實現

public class MementoCase {
    public static void main(String[] args) {
        Originator originator = new Originator();
        Caretaker caretaker = new Caretaker();
        originator.setState("狀態1 攻擊力100");
        // 保存當前狀態
        caretaker.add(originator.saveStateMemento());
        System.out.println("當前的狀態是:" + originator.getState());

        originator.setState("狀態2 攻擊力80");
        caretaker.add(originator.saveStateMemento());
        System.out.println("當前的狀態是:" + originator.getState());
        
        originator.setState("狀態3 攻擊力30");
        caretaker.add(originator.saveStateMemento());

        System.out.println("當前的狀態是:" + originator.getState());
        // 狀態恢復
        originator.recoveryState(caretaker.get(1));
        System.out.println("恢復到狀態2:" + originator.getState());
    }
}
@Data
class Originator {
    private String state;
    /**
     * 編寫一個方法,可以保存一個狀態對象Memento
     */
    public Memento saveStateMemento() {
        return new Memento(state);
    }
    /**
     * 通過備忘錄對象,恢復狀態
     */
    public void recoveryState(Memento memento) {
        state = memento.getState();
    }
}
class Memento {
    private String state;
    Memento(String state) {
        this.state = state;
    }
    public String getState() {
        return state;
    }
}
class Caretaker {
    /**
     * 在List集合中有很多備忘錄對象
     */
    private List<Memento> mementos = new ArrayList<>();
    public void add(Memento memento) {
        mementos.add(memento);
    }
    public Memento get(int index) {
        return mementos.get(index);
    }
}

備忘錄模式的注意事項和細節

  • 給用戶提供了一種可以恢復狀態的機制,可以使用戶能夠比較方便地回到某個歷史的狀態。
  • 實現了信息的封裝,使得用戶不需要關心狀態的保存細節。
  • 如果類的成員變量過多,勢必會佔用比較大的資源,而且每一次保存都會消耗一定的內存, 這個需要注意。
  • 適用的應用場景:① 後悔藥、② 打遊戲時的存檔、③ Windows 裏的 ctri + z、④ IE 中的後退、⑤ 數據庫的事務管理。
  • 爲了節約內存,備忘錄模式可以和原型模式配合使用。

解釋器模式

四則運算問題

通過解釋器模式來實現四則運算,如計算a+b-c的值,具體要求:

  • 先輸入表達式的形式,比如 a+b+c-d+e, 要求表達式的字母不能重複
  • 再分別輸入a ,b, c, d, e 的值
  • 最後求出結果,如圖:
    在這裏插入圖片描述

傳統方案解決四則運算問題分析

  • 編寫一個方法,接收表達式的形式,然後根據用戶輸入的數值進行解析,得到結果
  • 問題分析:如果加入新的運算符,比如 * / ( 等等,不利於擴展,另外讓一個方法來解析會造成程序結構混亂,不夠清晰。
  • 解決方案:可以考慮使用解釋器模式, 即: 表達式 -> 解釋器(可以有多種) -> 結果

解釋器模式基本介紹

  • 在編譯原理中,一個算術表達式通過詞法分析器形成詞法單元,而後這些詞法單元再通過語法分析器構建語法分析樹,最終形成一顆抽象的語法分析樹。這的詞法分析器和語法分析器都可以看做是解釋器
  • 解釋器模式(Interpreter Pattern):是指給定一個語言(表達式),定義它的文法的一種表示,並定義一個解釋器,使用該解釋器來解釋語言中的句子(表達式)。
  • 應用場景:應用可以將一個需要解釋執行的語言中的句子表示爲一個抽象語法樹、一些重複出現的問題可以用一種簡單的語言來表達、一個簡單語法需要解釋的場景。
  • 這樣的例子還有,比如編譯器、運算表達式計算、正則表達式、機器人等。

解釋器模式的原理類圖

在這裏插入圖片描述

  • Context:是環境角色,含有解釋器之外的全局信息
  • AbstractExpression:抽象表達式, 聲明一個抽象的解釋操作,這個方法爲抽象語法樹中所有的節點所共享
  • TerminalExpression:爲終結符表達式, 實現與文法中的終結符相關的解釋操作
  • NonTermialExpression:爲非終結符表達式,爲文法中的非終結符實現解釋操作
  • 說明:輸入Context 和 TerminalExpression 信息通過 Client 輸入即可

解釋器模式來實現四則運算

  • 思路分析:
    在這裏插入圖片描述
  • 代碼實現:
public class InterpreterCase {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String expStr = getExpStr(scanner);
        Map<String, Integer> var = getValue(expStr, scanner);
        Calculator calculator = new Calculator(expStr);
        System.out.println(expStr + "的運算結果:" + calculator.run(var));
    }

    private static String getExpStr(Scanner scanner) {
        System.out.println("請輸入表達式:");
        return scanner.nextLine();
    }
    private static Map<String, Integer> getValue(String expStr, Scanner scanner) {
        Map<String, Integer> map = new HashMap<>(8);

        for (char ch : expStr.toCharArray()) {
            if (ch != '+' && ch != '-') {
                String key = String.valueOf(ch);
                if (!map.containsKey(key)) {
                    System.out.println("請輸入" + key +"的值");
                    int value = scanner.nextInt();
                    map.put(key, value);
                }
            }
        }

        return map;
    }
}
/**
 * 抽象類表達式,通過Map鍵值對,可以獲取到變量的值
 */
abstract class AbsExpression {
    /**
     * a + b + c
     * 解釋公式和值,key就是公式(表達式)參數[a, b, c],value就是具體值
     * 比如: {a: 10, b: 20}
     */
    abstract int interpret(Map<String, Integer> var);
}
/**
 * 變量的解釋器
 */
class VarExpression extends AbsExpression {
    /**
     * 變量的key,key=a,b,c,...
     */
    private String key;
    VarExpression(String key) {
        this.key = key;
    }
    @Override
    int interpret(Map<String, Integer> var) {
        return var.get(this.key);
    }
}
/**
 * 抽象運算符號解釋器,這裏每個運算符號都只和自己左右兩個數字有關係
 * 但左右兩個數字有可能也是一個解析的結果,無論何種類型,都是Expression的實現類
 */
class SymbolExpression extends AbsExpression {
    protected AbsExpression left;
    protected AbsExpression right;
    SymbolExpression(AbsExpression left, AbsExpression right) {
        this.left = left;
        this.right = right;
    }
    /**
     * 這裏是一個空實現,有具體的子類實現
     */
    @Override
    int interpret(Map<String, Integer> var) {
        return 0;
    }
}
/**
 * 加法表達式
 */
class AddExpression extends SymbolExpression {
    AddExpression(AbsExpression left, AbsExpression right) {
        super(left, right);
    }
    @Override
    public int interpret(Map<String, Integer> var) {
        return left.interpret(var) + right.interpret(var);
    }
}
/**
 * 減法表達式
 */
class SubExpression extends SymbolExpression {
    SubExpression(AbsExpression left, AbsExpression right) {
        super(left, right);
    }
    @Override
    public int interpret(Map<String, Integer> var) {
        return left.interpret(var) - right.interpret(var);
    }
}
/**
 * 計算器
 */
class Calculator {
    private AbsExpression expression;
    Calculator(String expStr) {
        Stack<AbsExpression> stack = new Stack<>();
        AbsExpression left, right;
        for (int i = 0; i < expStr.length(); i++) {
            switch (expStr.charAt(i)) {
                case '+':
                    left = stack.pop();
                    right = new VarExpression(String.valueOf(expStr.charAt(++i)));
                    stack.push(new AddExpression(left, right));
                    break;
                case '-':
                    left =  stack.pop();
                    right = new VarExpression(String.valueOf(expStr.charAt(++i)));
                    stack.push(new SubExpression(left, right));
                    break;
                default:
                    stack.push(new VarExpression(String.valueOf(expStr.charAt(i))));
                    break;
            }
        }
        this.expression = stack.pop();
    }
    public int run(Map<String, Integer> var) {
        return this.expression.interpret(var);
    }
}

解釋器模式在Spring框架應用的源碼剖析

  • Spring 框架中 SpelExpressionParser 就使用到解釋器模式
public class InterpreterSpring {
    public static void main(String[] args) {
        //創建一個 Parser 對象
        SpelExpressionParser parser = new SpelExpressionParser();
        //通過 Parser 對象 獲取到一個Expression對象
        //會根據不同的  Parser 對象 ,返回不同的 Expression對象
        Expression expression = parser.parseExpression("10 * (2 + 1) * 1 + 66");
        int result = (Integer) expression.getValue();
        System.out.println(result);
    }
}
	 ↓↓↓↓↓
public class SpelExpressionParser extends TemplateAwareExpressionParser {
	private final SpelParserConfiguration configuration;
	public SpelExpressionParser() {
		this.configuration = new SpelParserConfiguration();
	}
	public SpelExpressionParser(SpelParserConfiguration configuration) {
		Assert.notNull(configuration, "SpelParserConfiguration must not be null");
		this.configuration = configuration;
	}
	public SpelExpression parseRaw(String expressionString) throws ParseException {
		return doParseExpression(expressionString, null);
	}
	@Override
	protected SpelExpression doParseExpression(String expressionString, @Nullable ParserContext context) throws ParseException {
		return new InternalSpelExpressionParser(this.configuration).doParseExpression(expressionString, context);
	}
}
  • Expression是表達式接口,下面有不同的實現類,比如SpelExpression, 或者CompositeStringExpression。
  • 使用時候,根據你創建的不同的Parser 對象,返回不同的 Expression 對象。
    在這裏插入圖片描述
  • 使用得到的 Expression對象,調用getValue 解釋執行 表達式,最後得到結果

解釋器模式的注意事項和細節

  • 當有一個語言需要解釋執行,可將該語言中的句子表示爲一個抽象語法樹,就可以考慮使用解釋器模式,讓程序具有良好的擴展性。
  • 應用場景:編譯器、運算表達式計算、正則表達式、機器人等。
  • 使用解釋器可能帶來的問題:解釋器模式會引起類膨脹、解釋器模式採用遞歸調用方法,將會導致調試非常複雜、效率可能降低。

狀態模式

APP抽獎活動問題

請編寫程序完成APP抽獎活動具體要求如下:

  • 假如每參加一次這個活動要扣除用戶50積分,中獎概率是10%;
  • 獎品數量固定,抽完就不能抽獎;
  • 活動有四個狀態:可以抽獎、不能抽獎、發放獎品和獎品領完;
  • 活動的四個狀態轉換關係圖如下:
    在這裏插入圖片描述

狀態模式基本介紹

  • 狀態模式(State Pattern):它主要用來解決對象在多種狀態轉換時,需要對外輸出不同的行爲的問題。狀態和行爲是一一對應的,狀態之間可以相互轉換。
  • 當一個對象的內在狀態改變時,允許改變其行爲,這個對象看起來像是改變了其類。

狀態模式原理類圖

在這裏插入圖片描述

  • Context:爲環境角色,用於維護 State 實例,這個實例定義當前狀態
  • State:是抽象狀態角色,定義一個接口封裝,封裝與 Context 的一個特定接口相關的行爲
  • ConcreteState:具體的狀態角色,每個子類實現一個與 Context 的一個狀態相關的行爲

狀態模式解決APP抽獎問題

  • 思路分析:定義出一個接口叫狀態接口,每個狀態都實現它;接口有扣除積分方法、抽獎方法、發放獎品方法。
    在這裏插入圖片描述
  • 代碼實現:
public class StateCase {
    public static void main(String[] args) {
        // 創建活動對象,獎品池有2個獎品
        RaffleActivity activity = new RaffleActivity(2);
        for (int i = 0; i < 100; i++) {
            System.out.println("-----第" + (i + 1) + "次抽獎-----");
            // 參加抽獎,第一步點擊扣除積分
            activity.deductMoney();
            // 第二步抽獎
            activity.raffle();
        }
    }
}
/**
 * 抽象狀態類
 */
abstract class State {
    /**
     * 扣除積分
     */
    abstract void deductMoney();
    /**
     * 是否抽中獎品
     */
    abstract boolean raffle();
    /**
     * 發放獎品
     */
    abstract void dispensePrize();
}
@Data
class RaffleActivity {
    /**
     * 表示活動當前的狀態,是變化的
     */
    private State state;
    /**
     * 獎品數量
     */
    private int count;
    /**
     * 四個屬性,表示四種狀態
     */
    private State noRaffleState = new NoRaffleState(this);
    private State canRaffleState = new CanRaffleState(this);
    private State dispenseState = new DispenseState(this);
    private State dispenseOutState = new DispenseOutState(this);
    /**
     * 初始化當前的狀態爲 noRafflleState(即不能抽獎的狀態)
     * 初始化獎品的數量
     */
    public RaffleActivity(int count) {
        this.state = getNoRaffleState();
        this.count = count;
    }
    /**
     * 扣分,調用當前狀態爲deductMoney
     */
    public void deductMoney() {
        state.deductMoney();
    }
    /**
     * 抽獎
     */
    public void raffle() {
        // 如果當前的狀態是抽獎成功
        if (state.raffle()) {
            // 領取獎品
            state.dispensePrize();
        }
    }
    /**
     * 這裏注意,每領取一次獎品,count--
     */
    public int getCount() {
        return count--;
    }
}
/**
 * 不能抽獎的狀態
 */
class NoRaffleState extends State {
    private RaffleActivity activity;
    NoRaffleState(RaffleActivity activity) {
        this.activity = activity;
    }
    /**
     * 當前狀態可以扣積分,扣除積分後,將狀態設置爲可以抽獎狀態
     */
    @Override
    void deductMoney() {
        System.out.println("扣除50積分成功,您可以抽獎了");
        activity.setState(activity.getCanRaffleState());
    }
    /**
     * 當前在不能抽獎狀態
     */
    @Override
    boolean raffle() {
        System.out.println("扣了積分才能抽獎哦!");
        return false;
    }
    @Override
    void dispensePrize() {
        System.out.println("不能發放獎品");
    }
}
/**
 * 可以抽獎的狀態
 */
class CanRaffleState extends State {
    private RaffleActivity activity;
    CanRaffleState(RaffleActivity activity) {
        this.activity = activity;
    }
    /**
     * 已經扣除了積分,不能再扣
     */
    @Override
    void deductMoney() {
        System.out.println("已經扣除過積分了");
    }
    /**
     * 可以抽獎, 抽完獎後,根據實際情況,改成新的狀態
     */
    @Override
    boolean raffle() {
        System.out.println("正在抽獎,請稍等");
        int num = new Random().nextInt(10);
        // 10%中獎機會
        if (num == 0) {
            // 改變活動狀態爲發放獎品
            activity.setState(activity.getDispenseState());
            return true;
        } else {
            System.out.println("很遺憾沒有抽中獎品");
            activity.setState(activity.getNoRaffleState());
            return false;
        }
    }
    /**
     * 不能發放獎品
     */
    @Override
    void dispensePrize() {
        System.out.println("沒中獎,不能發放獎品");
    }
}
/**
 * 發放獎品的狀態
 */
class DispenseState extends State {
    private RaffleActivity activity;
    DispenseState(RaffleActivity activity) {
        this.activity = activity;
    }
    @Override
    void deductMoney() {
        System.out.println("不能扣除積分");
    }

    @Override
    boolean raffle() {
        System.out.println("不能抽獎");
        return false;
    }
    /**
     * 發放獎品
     */
    @Override
    void dispensePrize() {
        if (activity.getCount() > 0) {
            System.out.println("恭喜中獎了");
            // 改變狀態爲不能抽獎
            activity.setState(activity.getNoRaffleState());
        } else {
            System.out.println("很遺憾,獎品發放完了");
            // 改變狀態爲獎品發送完畢,後面就不能發放獎品了
            activity.setState(activity.getDispenseState());
        }
    }
}
/**
 * 獎品發放完畢的狀態
 * 當我們 activity 改變成 DispenseOutState, 抽獎活動結束
 */
class DispenseOutState extends State {
    private RaffleActivity activity;
    DispenseOutState(RaffleActivity activity) {
        this.activity = activity;
    }
    @Override
    void deductMoney() {
        System.out.println("獎品發放完了,請下次再參加");
    }

    @Override
    boolean raffle() {
        System.out.println("獎品發放完了,請下次再參加");
        return false;
    }

    @Override
    void dispensePrize() {
        System.out.println("獎品發放完了,請下次再參加");
    }
}

狀態模式在實際項目-借貸平臺源碼分析

  • 借貸平臺的訂單,有審覈-發佈-搶單等等步驟,隨着操作的不同,會改變訂單的狀態, 項目中的這個模塊實現就會使用到狀態模式。
  • 通常通過if/else判斷訂單的狀態,從而實現不同的邏輯,僞代碼如下
    在這裏插入圖片描述
  • 使用狀態模式完成 借貸平臺項目的審覈模塊。
  • UML類圖:
    在這裏插入圖片描述
  • 代碼實現:
// TODO

狀態模式的注意事項和細節

  • 代碼有很強的可讀性:狀態模式將每個狀態的行爲封裝到對應的一個類中。
  • 方便維護:將容易產生問題的if-else語句刪除了,如果把每個狀態的行爲都放到一個類中,每次調用方法時都要判斷當前是什麼狀態,不但會產出很多if-else語句,而且容易出錯。
  • 符合開閉原則:容易增刪狀態。
  • 會產生很多類。每個狀態都要一個對應的類,當狀態過多時會產生很多類,加大維護難度。
  • 應用場景:當一個事件或者對象有很多種狀態,狀態之間會相互轉換,對不同的狀態要求有不同的行爲的時候,可以考慮使用狀態模式。

策略模式

鴨子問題

  • 有各種鴨子(比如 野鴨、北京鴨、水鴨等, 鴨子有各種行爲,比如 叫、飛行等),顯示鴨子的信息
  • 傳統方案解決鴨子問題的分析:
    在這裏插入圖片描述
  • 代碼實現:
abstract class Duck {
    abstract void display();
    void quack() {
        System.out.println("鴨子嘎嘎叫");
    }
    void swim() {
        System.out.println("鴨子會游泳");
    }
    void fly() {
        System.out.println("鴨子會飛翔");
    }
}
class WildDuck extends Duck {
    @Override
    void display() {
        System.out.println("-- 野鴨 --");
    }
}
class PekingDuck extends Duck {
    @Override
    void display() {
        System.out.println("-- 北京鴨 --");
    }
    @Override
    void fly() {
        System.out.println("北京鴨不能飛翔");
    }
}
class ToyDuck extends Duck {
    @Override
    void display() {
        System.out.println("-- 玩具鴨 --");
    }
    @Override
    void quack() {
        System.out.println("玩具鴨不會叫");
    }
    @Override
    void swim() {
        System.out.println("玩具鴨不會游泳");
    }
    @Override
    void fly() {
        System.out.println("玩具鴨不會飛");
    }
}
  • 其它鴨子,都繼承了Duck類,所以fly讓所有子類都會飛了,這是不正確的。
  • 上面說的1的問題,其實是繼承帶來的問題:對類的局部改動,尤其超類的局部改動,會影響其他部分。會有溢出效應
  • 爲了改進1問題,我們可以通過覆蓋fly方法來解決=>覆蓋解決
  • 問題又來了,如果我們有一個玩具鴨子ToyDuck, 這樣就需要ToyDuck去覆蓋Duck的所有實現的方法 => 解決思路 策略模式 (strategy pattern)

策略模式基本介紹

  • 策略模式(Strategy Pattern)中,定義算法族,分別封裝起來,讓他們之間可以相替換,此模式讓算法的變化獨立於使用算法的客戶。
  • 這算法體現了幾個設計原則:第一、把變化的代碼從不變的代碼中分離出來;第二、針對接口編程而不是具體類(定義了策略接口);第三、多用組合/聚合,少用繼承(客戶通過組合方式使用策略)。

策略模式的原理類圖

在這裏插入圖片描述

  • Context(環境類):它是使用算法的角色,在解某個問題是,可以採用多種策略。
  • Strategy(抽象策略類):抽象策略類,爲所支持的算法聲明瞭抽象方法,是所有策略類的父類,可以是抽象類,也可以是接口。
  • ConcreteStrategy(具體策略類):實現了在抽象策略類中聲明的算法,在運行時具體策略類會覆蓋環境類中定義的抽象策略類對象,使用一種具體的算法實現某個業務功能。

策略模式解決鴨子問題

  • 思路分析:分別封裝行爲接口,實現算法族,超類裏放行爲接口對象,在子類裏具體設定行爲對象。原則就是分離變化部分,封裝接口,基於接口編程各種功能。
    在這裏插入圖片描述
  • 代碼實現:
/**
 * 飛翔行爲:抽象策略類
 */
interface FlyBehavior {
    void fly();
}
/**
 * 下面是具體飛翔:具體策略類
 */
class GoodFlyBehavior implements FlyBehavior{
    @Override
    public void fly() {
        System.out.println("飛翔技術高超");
    }
}
class BadFlyBehavior implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("飛翔水平一般");
    }
}
class NoFlyBehavior implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("不會飛翔");
    }
}
/**
 * 叫聲行爲:抽象策略類
 */
interface QuackBehavor {
    void quack();
}
/**
 * 下面是具體叫聲:具體策略類
 */
class GaGaQuackBehavor implements QuackBehavor {
    @Override
    public void quack() {
        System.out.println("嘎嘎叫");
    }
}
class GeGeQuackBehavor implements QuackBehavor {
    @Override
    public void quack() {
        System.out.println("咯咯叫");
    }
}
class NoQuackBehavor implements QuackBehavor {
    @Override
    public void quack() {
        System.out.println("不會叫");
    }
}
/**
 * 鴨子:環境類
 */
abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavor quackBehavor;
    abstract void display();
    void quack() {
    }
    void fly() {
        if (flyBehavior != null) {
            flyBehavior.fly();
        }
    }
}
class WildDuck extends Duck {
    WildDuck() {
        flyBehavior = new GoodFlyBehavior();
        quackBehavor = new GeGeQuackBehavor();
    }
    @Override
    void display() {
        System.out.println("-- 野鴨 --");
    }
}
class PekingDuck extends Duck {
    PekingDuck() {
        flyBehavior = new BadFlyBehavior();
        quackBehavor = new GaGaQuackBehavor();
    }
    @Override
    void display() {
        System.out.println("-- 北京鴨子 --");
    }
}
class ToyDuck extends Duck {
    ToyDuck() {
        flyBehavior = new NoFlyBehavior();
        quackBehavor = new NoQuackBehavor();
    }
    @Override
    void display() {
        System.out.println("-- 玩具鴨子 --");
    }
}

策略模式在JDK-Arrays 應用的源碼分析

  • JDK的 Arrays 的 Comparator 就使用了策略模式
public class JDKStrategy {
    public static void main(String[] args) {
        Integer[] data = {9, 1, 2, 8, 4, 3};
        // 實現了 Comparator 接口(策略接口) , 匿名類 對象 new Comparator<Integer>(){..}
        // 對象 new Comparator<Integer>(){..} 就是實現了 策略接口 的對象
        // public int compare(Integer o1, Integer o2){} 指定具體的處理方式
        Comparator<Integer> comparator = (o1, o2) -> (o1 > o2) ? 1 : -1;
        Arrays.sort(data, comparator);
        System.out.println(Arrays.toString(data));
    }
}
	↓↓↓↓↓
@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    //...省略
}
	↓↓↓↓↓
public static <T> void sort(T[] a, Comparator<? super T> c) {
    if (c == null) {
    	// 就按照自己的方法排序
        sort(a);
    } else {
        if (LegacyMergeSort.userRequested)
        	// 按照這個方式調用c
            legacyMergeSort(a, c);
        else
        	// 按照這個方式調用c比較器
            TimSort.sort(a, 0, a.length, c, null, 0, 0);
    }
}

策略模式的注意事項和細節

  • 策略模式的關鍵是:分析項目中變化部分與不變部分。
  • 策略模式的核心思想是:多用組合/聚合少用繼承;用行爲類組合,而不是行爲的繼承,更有彈性。
  • 體現了“對修改關閉,對擴展開放”原則,客戶端增加行爲不用修改原有代碼,只要添加一種策略(或者行爲)即可,避免了使用多重轉移語句(if…else if…else)。
  • 提供了可以替換繼承關係的辦法:策略模式將算法封裝在獨立的Strategy類中使得你可以獨立於其Context改變它,使它易於切換、易於理解、易於擴展。
  • 需要注意的是:每添加一個策略就要增加一個類,當策略過多是會導致類數目龐大

責任鏈模式

OA系統採購審批需求

採購員採購教學器材:

  • 如果金額小於等於5000,由教學主任審批(0<=x<=5000);
  • 如果金額小於等於10000,由院長審批(5000<x<=10000);
  • 如果金額小於等於30000,由副校長審批(10000<x<=30000);
  • 如果金額超過30000以上,有校長審批(30000<x)。請設計程序完成採購審批項目。

傳統方案解決OA系統審批問題分析

  • 傳統方式是:接收到一個採購請求後,根據採購金額來調用對應的Approver (審批人)完成審批。
  • 傳統方式的問題分析:客戶端這裏會使用到分支判斷(比如 switch) 來對不同的採購請求處理, 這樣就存在如下問題:
* 如果各個級別的人員審批金額髮生變化,在客戶端的也需要變化
* 客戶端必須明確的知道有多少個審批級別和訪問
  • 這樣對一個採購請求進行處理和Approver(審批人)就存在強耦合關係,不利於代碼的擴展和維護,解決方案 => 職責鏈模式。

職責鏈模式基本介紹

  • 職責鏈模式(Chain of Responsibility Pattern),又叫責任鏈模式,爲請求創建了一個接收者對象的鏈(簡單示意圖)。這種模式對請求的發送者和接收者進行解耦。
  • 職責鏈模式通常每個接收者都包含對另一個接收者的引用。如果一個對象不能處理該請求,那麼它會把相同的請求傳給下一個接收者,依此類推。
    在這裏插入圖片描述

職責鏈模式的原理類圖

在這裏插入圖片描述

  • Handler:抽象的處理者, 定義了一個處理請求的接口,同時含義另外一個 Handler 對象。
  • ConcreteHandlerA , B 是具體的處理者, 處理它自己負責的請求, 可以訪問它的後繼者(即下一個處理者),如果可以處理當前請求,則處理,否則就將該請求交個後繼者去處理,從而形成一個職責鏈。
  • Request , 含義很多屬性,表示一個請求。
  • 職責鏈模式(Chain Of Responsibility)使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係。將這個對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止。

職責鏈模式解決OA系統採購審批

  • 思路分析:
    在這裏插入圖片描述
  • 代碼實現:
public class ChainOfResponsibilityCase {
    public static void main(String[] args) {
        // 創建一個請求
        PurchaseRequest request = new PurchaseRequest(1, 31000, 1);
        // 創建各個級別的審批人
        DepartmentApprover departmentApprover = new DepartmentApprover();
        CollegeApprover collegeApprover = new CollegeApprover();
        ViceShcoolMasterApprover viceShcoolMasterApprover = new ViceShcoolMasterApprover();
        ShcoolMasterApprover shcoolMasterApprover = new ShcoolMasterApprover();
        // 將各個審批級別的下一個審批人設置好,形成環
        departmentApprover.setApprover(collegeApprover);
        collegeApprover.setApprover(viceShcoolMasterApprover);
        viceShcoolMasterApprover.setApprover(shcoolMasterApprover);
        shcoolMasterApprover.setApprover(departmentApprover);

        System.out.println("----開始從教學主任處理----");
        departmentApprover.processRequest(request);
        System.out.println("----開始從副校長處理----");
        viceShcoolMasterApprover.processRequest(request);
    }
}
/**
 * 請求類
 */
@Data
@AllArgsConstructor
class PurchaseRequest {
    private int type;
    private float price;
    private int id;
}
/**
 * 審批人
 */
abstract class Approver {
    /**
     * 下一個處理者
     */
    Approver approver;
    String name;
    Approver(String name) {
        this.name = name;
    }
    public void setApprover(Approver approver) {
        this.approver = approver;
    }
    abstract void processRequest(PurchaseRequest request);
}
/**
 * 下面是具體的審批人
 */
class DepartmentApprover extends Approver {
    DepartmentApprover() {
        super("教學主任");
    }
    @Override
    void processRequest(PurchaseRequest request) {
        if (request.getPrice() <= 5000) {
            System.out.println("採購單 id = " + request.getId() + " 被 " + this.name + " 審批");
        } else {
            System.out.println(this.name + " 審批不了");
            approver.processRequest(request);
        }
    }
}
class CollegeApprover extends Approver {
    CollegeApprover() {
        super("院長");
    }
    @Override
    void processRequest(PurchaseRequest request) {
        if (request.getPrice() > 5000 && request.getPrice() <= 10000) {
            System.out.println("採購單 id = " + request.getId() + " 被 " + this.name + " 審批");
        } else {
            System.out.println(this.name + " 審批不了");
            approver.processRequest(request);
        }
    }
}
class ViceShcoolMasterApprover extends Approver {
    ViceShcoolMasterApprover() {
        super("副校長");
    }
    @Override
    void processRequest(PurchaseRequest request) {
        if (request.getPrice() > 10000 && request.getPrice() <= 30000) {
            System.out.println("採購單 id = " + request.getId() + " 被 " + this.name + " 審批");
        } else {
            System.out.println(this.name + " 審批不了");
            approver.processRequest(request);
        }
    }
}
class ShcoolMasterApprover extends Approver {
    ShcoolMasterApprover() {
        super("校長");
    }
    @Override
    void processRequest(PurchaseRequest request) {
        if (request.getPrice() > 30000) {
            System.out.println("採購單 id = " + request.getId() + " 被 " + this.name + " 審批");
        } else {
            System.out.println(this.name + " 審批不了");
            approver.processRequest(request);
        }
    }
}
//----開始從教學主任處理----
//教學主任 審批不了
//院長 審批不了
//副校長 審批不了
//採購單 id = 1 被 校長 審批
//----開始從副校長處理----
//副校長 審批不了
//採購單 id = 1 被 校長 審批

職責鏈模式在SpringMVC的源碼分析

SpringMVC-HandlerExecutionChain 類就使用到職責鏈模式
在這裏插入圖片描述

  • springmvc 請求的流程圖中,執行了 攔截器相關方法 interceptor.preHandler 等等。
  • 在處理SpringMvc請求時,除了使用到職責鏈模式,還使用到適配器模式。
  • HandlerExecutionChain 主要負責的是請求攔截器的執行和請求處理,但是他本身不處理請求,只是將請求分配給鏈上註冊處理器執行,這是職責鏈實現方式,減少職責鏈本身與處理邏輯之間的耦合,規範了處理流程。
  • HandlerExecutionChain 維護了 HandlerInterceptor 的集合, 可以向其中註冊相應的攔截器。

職責鏈模式的注意事項和細節

  • 將請求和處理分開,實現解耦,提高系統的靈活性。
  • 簡化了對象,使對象不需要知道鏈的結構。
  • 性能會受到影響,特別是在鏈比較長的時候,因此需控制鏈中最大節點數量,一般通過在Handler中設置一個最大節點數量,在setNext()方法中判斷是否已經超過閥值,超過則不允許該鏈建立,避免出現超長鏈無意識地破壞系統性能。
  • 調試不方便。採用了類似遞歸的方式,調試時邏輯可能比較複雜。
  • 最佳應用場景:有多個對象可以處理同一個請求時,比如:多級請求、請假/加薪等審批流程、Java Web中Tomcat對Encoding的處理、攔截器。

設計模式在框架或項目中源碼分析的說明

  • 設計模式是程序員在編程中,有意或者是無意使用到的(也不所有的程序員都學習過設計模式),並且同一種設計模式實現方式也不是100%的一樣,設計模式主要是提高程序的擴展性,可讀性,可維護性、規範性。
  • 所以在我們講某個設計模式在源碼框架中使用時,和我們的標準的設計模式寫法可能會有些出入,比如組合模式 Component 可以是抽象類,接口,也可以是一個實現類, 我們講源碼時(JDK HashMap源碼),Component 就可能不一樣。
  • 對於框架源碼,源碼中部分使用了A設計模式,還部分使用了B設計模式,也是有可能的,也就是說設計模式是可以結合使用的。
  • 因爲設計模式主要是一種編程思想,既然是思想,具體實現方式,就不可能100%的一樣(當然,程序的設計結構基本是一樣的)。
  • 所以,提醒大家我們學習設計模式時,(包括看源碼分析),要抓住本質,就是使用這個設計模式到底帶了了什麼好處? 是 擴展性提高了,還是更加規範了,這樣我們才能領會設計模式的精妙之處。
    在這裏插入圖片描述
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章