一篇文章帶你了解設計模式——行為型模式
在之前的文章我們已經介紹了設計模式中的創建者模式和結構型模式,下面我們來介紹最后一部分行為型模式
行為型模式用于描述程式在運行時復雜的流程控制,即描述多個類或物件之間怎樣相互協作共同完成單個物件都無法單獨完成的任務
行為型模式分為類行為模式和物件行為模式,前者采用繼承機制來在類間分派行為,后者采用組合或聚合在物件間分配行為,
由于組合關系或聚合關系比繼承關系耦合度低,滿足“合成復用原則”,所以物件行為模式比類行為模式具有更大的靈活性,
下面我們將介紹十一種行為型模式:
- 模板方法模式
- 策略模式
- 命令模式
- 責任鏈模式
- 狀態模式
- 觀察者模式
- 中介者模式
- 迭代器模式
- 訪問者模式
- 解釋器模式
模板方法模式
首先我們來介紹模板方法模式
模板方法模式簡述
首先我們給出模板方法模式的概念:
- 定義一個操作中的演算法骨架
- 將演算法的一些步驟延遲到子類中,使得子類可以不改變該演算法結構的情況下重定義該演算法的某些特定步驟,
模板方法模式結構
模板方法(Template Method)模式包含以下主要角色:
-
抽象類(Abstract Class):負責給出一個演算法的輪廓和骨架,它由一個模板方法和若干個基本方法構成,
-
模板方法:定義了演算法的骨架,按某種順序呼叫其包含的基本方法,
-
基本方法:是實作演算法各個步驟的方法,是模板方法的組成部分,基本方法又可以分為三種:
-
抽象方法(Abstract Method) :一個抽象方法由抽象類宣告、由其具體子類實作,
-
具體方法(Concrete Method) :一個具體方法由一個抽象類或具體類宣告并實作,其子類可以進行覆寫也可以直接繼承,
-
鉤子方法(Hook Method) :在抽象類中已經實作,包括用于判斷的邏輯方法和需要子類重寫的空方法兩種,
一般鉤子方法是用于判斷的邏輯方法,這類方法名一般為isXxx,回傳值型別為boolean型別,
-
-
-
具體子類(Concrete Class):實作抽象類中所定義的抽象方法和鉤子方法,它們是一個頂級邏輯的組成步驟,
模板方法模式案例
我們給出一個簡單的例子來介紹模板方法模式:

具體分析:
/*
【例】炒菜
炒菜的步驟是固定的,分為倒油、熱油、倒蔬菜、倒調料品、翻炒等步驟,現通過模板方法模式來用代碼模擬,
上述的AbstractClass就是抽象類,我們在抽象類給出一個模板方法cookProcess,里面會給出其他基本方法的執行順序,部分基本方法會有具體內容,部分基本方法屬于Abstract方法,由子類去實作
下面的ConcreteClass_BaoCai和ConcreteClass_CaiXin屬于子類實作類,他們會繼承父類的模板方法,同時重寫抽象基本方法完成自己的需求
*/
/* 具體代碼 */
// 抽象類
public abstract class AbstractClass {
// 模板方法(為防止惡意操作,一般模板方法都加上 final 關鍵詞)
public final void cookProcess() {
//第一步:倒油
this.pourOil();
//第二步:熱油
this.heatOil();
//第三步:倒蔬菜
this.pourVegetable();
//第四步:倒調味料
this.pourSauce();
//第五步:翻炒
this.fry();
}
// 下述均為基本方法
public void pourOil() {
System.out.println("倒油");
}
//第二步:熱油是一樣的,所以直接實作
public void heatOil() {
System.out.println("熱油");
}
//第三步:倒蔬菜是不一樣的(一個下包菜,一個是下菜心)
public abstract void pourVegetable();
//第四步:倒調味料是不一樣
public abstract void pourSauce();
//第五步:翻炒是一樣的,所以直接實作
public void fry(){
System.out.println("炒啊炒啊炒到熟啊");
}
}
// Baocai實作類
public class ConcreteClass_BaoCai extends AbstractClass {
@Override
public void pourVegetable() {
System.out.println("下鍋的蔬菜是包菜");
}
@Override
public void pourSauce() {
System.out.println("下鍋的醬料是辣椒");
}
}
// Caixin實作類
public class ConcreteClass_CaiXin extends AbstractClass {
@Override
public void pourVegetable() {
System.out.println("下鍋的蔬菜是菜心");
}
@Override
public void pourSauce() {
System.out.println("下鍋的醬料是蒜蓉");
}
}
public class Client {
public static void main(String[] args) {
//炒手撕包菜
ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
baoCai.cookProcess();
//炒蒜蓉菜心
ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();
caiXin.cookProcess();
}
}
模板方法模式分析
首先我們給出模板方法模式的適用場景:
- 演算法的整體步驟很固定,但其中個別部分易變時,這時候可以使用模板方法模式,將容易變的部分抽象出來,供子類實作,
- 需要通過子類來決定父類演算法中某個步驟是否執行,實作子類對父類的反向控制,
然后我們給出模板方法模式的優點:
-
提高代碼復用性
將相同部分的代碼放在抽象的父類中,而將不同的代碼放入不同的子類中,
-
實作了反向控制
通過一個父類呼叫其子類的操作,通過對子類的具體實作擴展不同的行為,實作了反向控制 ,并符合“開閉原則”,
最后我們給出模板方法模式的缺點:
- 對每個不同的實作都需要定義一個子類,這會導致類的個數增加,系統更加龐大,設計也更加抽象,
- 父類中的抽象方法由子類實作,子類執行的結果會影響父類的結果,這導致一種反向的控制結構,它提高了代碼閱讀的難度,
策略模式
下面我們來介紹策略模式
策略模式簡述
首先我們給出策略模式的概念:
-
策略模式和模板模式其實比較相似,只不過前者使用聚合,后者使用繼承,
-
該模式定義了一系列演算法,并將每個演算法封裝起來,使它們可以相互替換,且演算法的變化不會影響使用演算法的客戶,
-
物件行為模式,它通過對演算法進行封裝,把使用演算法的責任和演算法的實作分割開來,并委派給不同的物件對這些演算法進行管理,
我們給出一個簡單的例子說明:
- 在日常作業種,我們開發需要選擇一款開發工具,當然可以進行代碼開發的工具有很多
- 可以選擇Idea進行開發,也可以使用eclipse進行開發,也可以使用其他的一些開發工具,這些工具就是策略
策略模式結構
策略模式的主要角色如下:
- 抽象策略(Strategy)類:這是一個抽象角色,通常由一個介面或抽象類實作,此角色給出所有的具體策略類所需的介面,
- 具體策略(Concrete Strategy)類:實作了抽象策略定義的介面,提供具體的演算法實作或行為,
- 環境(Context)類:持有一個策略類的參考,最終給客戶端呼叫,
策略模式案例
我們同樣給出一個簡單的案例講解策略模式:

具體分析:
/*
【例】促銷活動
一家百貨公司在定年度的促銷活動,針對不同的節日(春節、中秋節、圣誕節)推出不同的促銷活動,由促銷員將促銷活動展示給客戶,
其中SalesMan就是環境類,Strategy是抽象策略類,下面的方案就是具體策略類
SalesMan中聚合一個Strategy,然后會用子類去填充,具有一定格式但不同實作的子類就可以不斷更替Strategy而實作策略更換
*/
/* 代碼展示 */
// 環境類(呼叫更換策略類)
public class SalesMan {
//持有抽象策略角色的參考
private Strategy strategy;
public SalesMan(Strategy strategy) {
this.strategy = strategy;
}
//向客戶展示促銷活動
public void salesManShow(){
strategy.show();
}
}
// 抽象策略類
public interface Strategy {
void show();
}
// 具體策略類
//為春節準備的促銷活動A
public class StrategyA implements Strategy {
public void show() {
System.out.println("買一送一");
}
}
//為中秋準備的促銷活動B
public class StrategyB implements Strategy {
public void show() {
System.out.println("滿200元減50元");
}
}
//為圣誕準備的促銷活動C
public class StrategyC implements Strategy {
public void show() {
System.out.println("滿1000元加一元換購任意200元以下商品");
}
}
策略模式分析
首先我們給出策略模式的適用場景:
- 系統中各演算法彼此完全獨立,且要求對客戶隱藏具體演算法的實作細節時,
- 一個系統需要動態地在幾種演算法中選擇一種時,可將每個演算法封裝到策略類中,
- 多個類只區別在表現行為不同,可以使用策略模式,在運行時動態選擇具體要執行的行為,
- 系統要求使用演算法的客戶不應該知道其操作的資料時,可使用策略模式來隱藏與演算法相關的資料結構,
- 一個類定義了多種行為,并且這些行為在這個類的操作中以多個條件陳述句的形式出現,可將每個條件分支移入它們各自的策略類中以代替這些條件陳述句,
然后我們給出策略模式的優點:
-
策略類之間可以自由切換
由于策略類都實作同一個介面,所以使它們之間可以自由切換,
-
易于擴展
增加一個新的策略只需要添加一個具體的策略類即可,基本不需要改變原有的代碼,符合“開閉原則“
-
避免使用多重條件選擇陳述句(if else),充分體現面向物件設計思想,
最后我們給出策略模式的缺點:
- 客戶端必須知道所有的策略類,并自行決定使用哪一個策略類,
- 策略模式將造成產生很多策略類,可以通過使用享元模式在一定程度上減少物件的數量,
命令模式
下面我們來介紹命令模式
命令模式簡述
首先我們給出命令模式的概念:
- 將一個請求封裝為一個物件,使發出請求的責任和執行請求的責任分割開,
- 這樣兩者之間通過命令物件進行溝通,這樣方便將命令物件進行存盤、傳遞、呼叫、增加與管理,
我們給出一個簡單示例:
- 我們在餐廳吃飯,我們給出的訂單就是命令,我們將訂單封裝為一個物件,這就是請求的責任
- 我們給出的訂單會有大廚去完成,那么大廚就是執行請求的物件
- 然后在這之間會有服務員,服務員就是一個命令物件,她會負責將請求和執行兩方面保存并傳遞呼叫
命令模式結構
命令模式包含以下主要角色:
- 抽象命令類(Command)角色: 定義命令的介面,宣告執行的方法,
- 具體命令(Concrete Command)角色:具體的命令,實作命令介面;通常會持有接收者,并呼叫接收者的功能來完成命令要執行的操作,
- 實作者/接收者(Receiver)角色: 接收者,真正執行命令的物件,任何類都可能成為一個接收者,只要它能夠實作命令要求實作的相應功能,
- 呼叫者/請求者(Invoker)角色: 要求命令物件執行請求,通常會持有命令物件,可以持有很多的命令物件,這個是客戶端真正觸發命令并要求命令執行相應操作的地方,也就是說相當于使用命令物件的入口,
命令模式案例
我們給出一個簡單的案例來介紹命令模式:

具體分析:
/*
將上面的案例用代碼實作,那我們就需要分析命令模式的角色在該案例中由誰來充當,
服務員: 就是呼叫者角色,由她來發起命令,
資深大廚: 就是接收者角色,真正命令執行的物件,
訂單: 命令中包含訂單,
注意:
訂單就是命令,命令是自己具有的,有指定的接收物件
服務員只負責將啟動命令,實則還是呼叫命令內部的方法
*/
/* 代碼展示 */
// 抽象命令類
public interface Command {
void execute();//只需要定義一個統一的執行方法
}
// 具體命令類
public class OrderCommand implements Command {
// 持有接受者物件(就是實作者,當呼叫者呼叫命令后實作者會去執行該命令)
private SeniorChef receiver;
// 執行的內容存盤
private Order order;
public OrderCommand(SeniorChef receiver, Order order){
this.receiver = receiver;
this.order = order;
}
// 具體的命令執行,呼叫者只負責呼叫該方法
public void execute() {
System.out.println(order.getDiningTable() + "桌的訂單:");
Set<String> keys = order.getFoodDic().keySet();
for (String key : keys) {
receiver.makeFood(order.getFoodDic().get(key),key);
}
try {
Thread.sleep(100);//停頓一下 模擬做飯的程序
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(order.getDiningTable() + "桌的飯弄好了");
}
}
// domain物體類
public class Order {
// 餐桌號碼
private int diningTable;
// 用來存盤餐名并記錄份數
private Map<String, Integer> foodDic = new HashMap<String, Integer>();
public int getDiningTable() {
return diningTable;
}
public void setDiningTable(int diningTable) {
this.diningTable = diningTable;
}
public Map<String, Integer> getFoodDic() {
return foodDic;
}
public void setFoodDic(String name, int num) {
foodDic.put(name,num);
}
}
// 資深大廚類 是命令的Receiver
public class SeniorChef {
public void makeFood(int num,String foodName) {
System.out.println(num + "份" + foodName);
}
}
// 呼叫者,負責協調請求和接收者
public class Waitor {
private ArrayList<Command> commands;//可以持有很多的命令物件
public Waitor() {
commands = new ArrayList();
}
public void setCommand(Command cmd){
commands.add(cmd);
}
// 發出命令 喊 訂單來了,廚師開始執行
public void orderUp() {
System.out.println("美女服務員:叮咚,大廚,新訂單來了.......");
for (int i = 0; i < commands.size(); i++) {
Command cmd = commands.get(i);
if (cmd != null) {
cmd.execute();
}
}
}
}
// 測驗類
public class Client {
public static void main(String[] args) {
//創建2個order
Order order1 = new Order();
order1.setDiningTable(1);
order1.getFoodDic().put("西紅柿雞蛋面",1);
order1.getFoodDic().put("小杯可樂",2);
Order order2 = new Order();
order2.setDiningTable(3);
order2.getFoodDic().put("尖椒肉絲蓋飯",1);
order2.getFoodDic().put("小杯雪碧",1);
//創建接收者
SeniorChef receiver=new SeniorChef();
//將訂單和接收者封裝成命令物件
OrderCommand cmd1 = new OrderCommand(receiver, order1);
OrderCommand cmd2 = new OrderCommand(receiver, order2);
//創建呼叫者 waitor
Waitor invoker = new Waitor();
invoker.setCommand(cmd1);
invoker.setCommand(cmd2);
//將訂單帶到柜臺 并向廚師喊 訂單來了
invoker.orderUp();
}
}
命令模式分析
我們首先給出命令模式的適用場景:
- 系統需要將請求呼叫者和請求接收者解耦,使得呼叫者和接收者不直接互動,
- 系統需要在不同的時間指定請求、將請求排隊和執行請求,
- 系統需要支持命令的撤銷(Undo)操作和恢復(Redo)操作,
然后我們給出命令模式的優點:
- 降低系統的耦合度,命令模式能將呼叫操作的物件與實作該操作的物件解耦,
- 增加或洗掉命令非常方便,采用命令模式增加與洗掉命令不會影響其他類,它滿足“開閉原則”,對擴展比較靈活,
- 可以實作宏命令,命令模式可以與組合模式結合,將多個命令裝配成一個組合命令,即宏命令,
- 方便實作 Undo 和 Redo 操作,命令模式可以與后面介紹的備忘錄模式結合,實作命令的撤銷與恢復,
最后我們給出命令模式的缺點:
- 使用命令模式可能會導致某些系統有過多的具體命令類,
- 系統結構更加復雜,
責任鏈模式
下面我們來介紹責任鏈模式
責任鏈模式簡述
首先我們先來簡單介紹一下責任鏈模式:
- 為了避免請求發送者與多個請求處理者耦合在一起,將所有請求的處理者通過前一物件記住其下一個物件的參考而連成一條鏈;
- 當有請求發生時,可將請求沿著這條鏈傳遞,直到有物件處理它為止
我們給出一個簡單例子:
- 我們身為一個小員工,如果請假需要經過上級的同意
- 但不同的上級具有不同的權限,例如小組長簽一天,部門經理簽三天,總經理簽七天
- 正常情況下,我們需要記住每個領導,自己根據假期時間找各個領導,導致我們和每個領導都需要有關系,耦合性過大
責任鏈模式結構
職責鏈模式主要包含以下角色:
- 抽象處理者(Handler)角色:定義一個處理請求的介面,包含抽象處理方法和一個后繼連接,
- 具體處理者(Concrete Handler)角色:實作抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉給它的后繼者,
- 客戶類(Client)角色:創建處理鏈,并向鏈頭的具體處理者物件提交請求,它不關心處理細節和請求的傳遞程序,
責任鏈模式案例
我們同樣給出一個簡單案例來講解責任鏈模式:

具體分析:
/*
【例】
現需要開發一個請假流程控制系統,
請假一天以下的假只需要小組長同意即可;請假1天到3天的假還需要部門經理同意;請求3天到7天還需要總經理同意才行,
LeaveRequest:請假條,記錄任命,日期,資訊;屬于物體類
Handler:抽象處理者
Leader:具體處理者
*/
/* 代碼展示 */
// 請假條(物體類,僅用于記錄資訊)
public class LeaveRequest {
private String name;//姓名
private int num;//請假天數
private String content;//請假內容
public LeaveRequest(String name, int num, String content) {
this.name = name;
this.num = num;
this.content = content;
}
public String getName() {
return name;
}
public int getNum() {
return num;
}
public String getContent() {
return content;
}
}
// 處理者抽象類
public abstract class Handler {
// 請假日期分界線,用于子類使用
protected final static int NUM_ONE = 1;
protected final static int NUM_THREE = 3;
protected final static int NUM_SEVEN = 7;
// 該領導處理的請假天數區間
private int numStart;
private int numEnd;
// 領導上面還有領導(責任鏈的下一位)
private Handler nextHandler;
// 設定請假天數范圍 上不封頂
public Handler(int numStart) {
this.numStart = numStart;
}
// 設定請假天數范圍
public Handler(int numStart, int numEnd) {
this.numStart = numStart;
this.numEnd = numEnd;
}
// 設定上級領導
public void setNextHandler(Handler nextHandler){
this.nextHandler = nextHandler;
}
// 提交請假條(這里是一個提交方法,引數為leaveRequest,主要是給子類的領導層使用的)
public final void submit(LeaveRequest leave){
// 首先判斷是否請假
if(0 == this.numStart){
return;
}
//如果請假天數達到該領導者的處理要求
if(leave.getNum() >= this.numStart){
this.handleLeave(leave);
//如果還有上級 并且請假天數超過了當前領導的處理范圍
if(null != this.nextHandler && leave.getNum() > numEnd){
this.nextHandler.submit(leave);//繼續提交
} else {
System.out.println("流程結束");
}
}
}
// 各級領導處理請假條方法
protected abstract void handleLeave(LeaveRequest leave);
}
// 小組長
public class GroupLeader extends Handler {
public GroupLeader() {
//小組長處理1-3天的請假
super(Handler.NUM_ONE, Handler.NUM_THREE);
}
@Override
protected void handleLeave(LeaveRequest leave) {
System.out.println(leave.getName() + "請假" + leave.getNum() + "天," + leave.getContent() + ",");
System.out.println("小組長審批:同意,");
}
}
// 部門經理
public class Manager extends Handler {
public Manager() {
//部門經理處理3-7天的請假
super(Handler.NUM_THREE, Handler.NUM_SEVEN);
}
@Override
protected void handleLeave(LeaveRequest leave) {
System.out.println(leave.getName() + "請假" + leave.getNum() + "天," + leave.getContent() + ",");
System.out.println("部門經理審批:同意,");
}
}
// 總經理
public class GeneralManager extends Handler {
public GeneralManager() {
//部門經理處理7天以上的請假
super(Handler.NUM_SEVEN);
}
@Override
protected void handleLeave(LeaveRequest leave) {
System.out.println(leave.getName() + "請假" + leave.getNum() + "天," + leave.getContent() + ",");
System.out.println("總經理審批:同意,");
}
}
// 測驗類
public class Client {
public static void main(String[] args) {
//請假條來一張
LeaveRequest leave = new LeaveRequest("小花",5,"身體不適");
//各位領導
GroupLeader groupLeader = new GroupLeader();
Manager manager = new Manager();
GeneralManager generalManager = new GeneralManager();
groupLeader.setNextHandler(manager);//小組長的領導是部門經理
manager.setNextHandler(generalManager);//部門經理的領導是總經理
//之所以在這里設定上級領導,是因為可以根據實際需求來更改設定,如果實戰中上級領匯入都是固定的,則可以移到領導實作類中,
//提交申請
groupLeader.submit(leave);
}
}
責任鏈模式分析
首先我們給出責任鏈模式的優點:
-
降低了物件之間的耦合度
該模式降低了請求發送者和接收者的耦合度,
-
增強了系統的可擴展性
可以根據需要增加新的請求處理類,滿足開閉原則,
-
增強了給物件指派職責的靈活性
當作業流程發生變化,可以動態地改變鏈內的成員或者修改它們的次序,也可動態地新增或者洗掉責任,
-
責任鏈簡化了物件之間的連接
一個物件只需保持一個指向其后繼者的參考,不需保持其他所有處理者的參考,這避免了使用眾多的 if 或者 if···else 陳述句,
-
責任分擔
每個類只需要處理自己該處理的作業,不能處理的傳遞給下一個物件完成,明確各類的責任范圍,符合類的單一職責原則,
然后我們給出責任鏈模式的缺點:
- 對比較長的職責鏈,請求的處理可能涉及多個處理物件,系統性能將受到一定影響
- 不能保證每個請求一定被處理,由于一個請求沒有明確的接收者,所以不能保證它一定會被處理
- 職責鏈建立的合理性要靠客戶端來保證,增加了客戶端的復雜性,可能會由于職責鏈的錯誤設定而導致系統出錯
狀態模式
下面我們來介紹狀態模式
狀態模式簡述
首先我們給出狀態模式的概念:
- 對有狀態的物件,把復雜的“判斷邏輯”提取到不同的狀態物件中,允許狀態物件在其內部狀態發生改變時改變其行為,
狀態模式結構
狀態模式包含以下主要角色,
- 環境(Context)角色:也稱為背景關系,它定義了客戶程式需要的介面,維護一個當前狀態,并將與狀態相關的操作委托給當前狀態物件來處理,
- 抽象狀態(State)角色:定義一個介面,用以封裝環境物件中的特定狀態所對應的行為,
- 具體狀態(Concrete State)角色:實作抽象狀態所對應的行為,
狀態模式案例
我們首先給出一個非狀態模式:

具體分析:
/*
【例】通過按鈕來控制一個電梯的狀態,一個電梯有開門狀態,關門狀態,停止狀態,運行狀態,每一種狀態改變,都有可能要根據其他狀態來更新處理,例如,如果電梯門現在處于運行時狀態,就不能進行開門操作,而如果電梯門是停止狀態,就可以執行開門操作,
下述代碼問題:
- 使用了大量的switch…case這樣的判斷(if…else也是一樣),使程式的可閱讀性變差,
- 擴展性很差,如果新加了斷電的狀態,我們需要修改上面判斷邏輯
*/
/* 代碼展示 */
public interface ILift {
//電梯的4個狀態
//開門狀態
public final static int OPENING_STATE = 1;
//關門狀態
public final static int CLOSING_STATE = 2;
//運行狀態
public final static int RUNNING_STATE = 3;
//停止狀態
public final static int STOPPING_STATE = 4;
//設定電梯的狀態
public void setState(int state);
//電梯的動作
public void open();
public void close();
public void run();
public void stop();
}
public class Lift implements ILift {
private int state;
@Override
public void setState(int state) {
this.state = state;
}
//執行關門動作
@Override
public void close() {
switch (this.state) {
case OPENING_STATE:
System.out.println("電梯關門了,,,");//只有開門狀態可以關閉電梯門,可以對應電梯狀態表來看
this.setState(CLOSING_STATE);//關門之后電梯就是關閉狀態了
break;
case CLOSING_STATE:
//do nothing //已經是關門狀態,不能關門
break;
case RUNNING_STATE:
//do nothing //運行時電梯門是關著的,不能關門
break;
case STOPPING_STATE:
//do nothing //停止時電梯也是關著的,不能關門
break;
}
}
//執行開門動作
@Override
public void open() {
switch (this.state) {
case OPENING_STATE://門已經開了,不能再開門了
//do nothing
break;
case CLOSING_STATE://關門狀態,門打開:
System.out.println("電梯門打開了,,,");
this.setState(OPENING_STATE);
break;
case RUNNING_STATE:
//do nothing 運行時電梯不能開門
break;
case STOPPING_STATE:
System.out.println("電梯門開了,,,");//電梯停了,可以開門了
this.setState(OPENING_STATE);
break;
}
}
//執行運行動作
@Override
public void run() {
switch (this.state) {
case OPENING_STATE://電梯不能開著門就走
//do nothing
break;
case CLOSING_STATE://門關了,可以運行了
System.out.println("電梯開始運行了,,,");
this.setState(RUNNING_STATE);//現在是運行狀態
break;
case RUNNING_STATE:
//do nothing 已經是運行狀態了
break;
case STOPPING_STATE:
System.out.println("電梯開始運行了,,,");
this.setState(RUNNING_STATE);
break;
}
}
//執行停止動作
@Override
public void stop() {
switch (this.state) {
case OPENING_STATE: //開門的電梯已經是是停止的了(正常情況下)
//do nothing
break;
case CLOSING_STATE://關門時才可以停止
System.out.println("電梯停止了,,,");
this.setState(STOPPING_STATE);
break;
case RUNNING_STATE://運行時當然可以停止了
System.out.println("電梯停止了,,,");
this.setState(STOPPING_STATE);
break;
case STOPPING_STATE:
//do nothing
break;
}
}
}
public class Client {
public static void main(String[] args) {
Lift lift = new Lift();
lift.setState(ILift.STOPPING_STATE);//電梯是停止的
lift.open();//開門
lift.close();//關門
lift.run();//運行
lift.stop();//停止
}
}
然后我們給出狀態模式下的修改案例:

具體分析:
/*
對上述電梯的案例使用狀態模式進行改進
*/
/* 代碼展示 */
//抽象狀態類
public abstract class LiftState {
//定義一個環境角色,也就是封裝狀態的變化引起的功能變化
protected Context context;
public void setContext(Context context) {
this.context = context;
}
//電梯開門動作
public abstract void open();
//電梯關門動作
public abstract void close();
//電梯運行動作
public abstract void run();
//電梯停止動作
public abstract void stop();
}
//開啟狀態
public class OpenningState extends LiftState {
//開啟當然可以關閉了,我就想測驗一下電梯門開關功能
@Override
public void open() {
System.out.println("電梯門開啟...");
}
@Override
public void close() {
//狀態修改
super.context.setLiftState(Context.closeingState);
//動作委托為CloseState來執行,也就是委托給了ClosingState子類執行這個動作
super.context.getLiftState().close();
}
//電梯門不能開著就跑,這里什么也不做
@Override
public void run() {
//do nothing
}
//開門狀態已經是停止的了
@Override
public void stop() {
//do nothing
}
}
//運行狀態
public class RunningState extends LiftState {
//運行的時候開電梯門?你瘋了!電梯不會給你開的
@Override
public void open() {
//do nothing
}
//電梯門關閉?這是肯定了
@Override
public void close() {//雖然可以關門,但這個動作不歸我執行
//do nothing
}
//這是在運行狀態下要實作的方法
@Override
public void run() {
System.out.println("電梯正在運行...");
}
//這個事絕對是合理的,光運行不停止還有誰敢做這個電梯?!估計只有上帝了
@Override
public void stop() {
super.context.setLiftState(Context.stoppingState);
super.context.stop();
}
}
//停止狀態
public class StoppingState extends LiftState {
//停止狀態,開門,那是要的!
@Override
public void open() {
//狀態修改
super.context.setLiftState(Context.openningState);
//動作委托為CloseState來執行,也就是委托給了ClosingState子類執行這個動作
super.context.getLiftState().open();
}
@Override
public void close() {//雖然可以關門,但這個動作不歸我執行
//狀態修改
super.context.setLiftState(Context.closeingState);
//動作委托為CloseState來執行,也就是委托給了ClosingState子類執行這個動作
super.context.getLiftState().close();
}
//停止狀態再跑起來,正常的很
@Override
public void run() {
//狀態修改
super.context.setLiftState(Context.runningState);
//動作委托為CloseState來執行,也就是委托給了ClosingState子類執行這個動作
super.context.getLiftState().run();
}
//停止狀態是怎么發生的呢?當然是停止方法執行了
@Override
public void stop() {
System.out.println("電梯停止了...");
}
}
//關閉狀態
public class ClosingState extends LiftState {
@Override
//電梯門關閉,這是關閉狀態要實作的動作
public void close() {
System.out.println("電梯門關閉...");
}
//電梯門關了再打開,逗你玩呢,那這個允許呀
@Override
public void open() {
super.context.setLiftState(Context.openningState);
super.context.open();
}
//電梯門關了就跑,這是再正常不過了
@Override
public void run() {
super.context.setLiftState(Context.runningState);
super.context.run();
}
//電梯門關著,我就不按樓層
@Override
public void stop() {
super.context.setLiftState(Context.stoppingState);
super.context.stop();
}
}
//環境角色
public class Context {
//定義出所有的電梯狀態
public final static OpenningState openningState = new OpenningState();//開門狀態,這時候電梯只能關閉
public final static ClosingState closeingState = new ClosingState();//關閉狀態,這時候電梯可以運行、停止和開門
public final static RunningState runningState = new RunningState();//運行狀態,這時候電梯只能停止
public final static StoppingState stoppingState = new StoppingState();//停止狀態,這時候電梯可以開門、運行
//定義一個當前電梯狀態
private LiftState liftState;
public LiftState getLiftState() {
return this.liftState;
}
public void setLiftState(LiftState liftState) {
//當前環境改變
this.liftState = liftState;
//把當前的環境通知到各個實作類中
this.liftState.setContext(this);
}
public void open() {
this.liftState.open();
}
public void close() {
this.liftState.close();
}
public void run() {
this.liftState.run();
}
public void stop() {
this.liftState.stop();
}
}
//測驗類
public class Client {
public static void main(String[] args) {
Context context = new Context();
context.setLiftState(new ClosingState());
context.open();
context.close();
context.run();
context.stop();
}
}
狀態模式分析
我們首先給出狀態模式的適用場景:
- 當一個物件的行為取決于它的狀態,并且它必須在運行時根據狀態改變它的行為時,就可以考慮使用狀態模式,
- 一個操作中含有龐大的分支結構,并且這些分支決定于物件的狀態時,
然后我們給出狀態模式的優點:
- 將所有與某個狀態有關的行為放到一個類中,并且可以方便地增加新的狀態,只需要改變物件狀態即可改變物件的行為,
- 允許狀態轉換邏輯與狀態物件合成一體,而不是某一個巨大的條件陳述句塊,
最后我們給出狀態模式的缺點:
- 狀態模式的使用必然會增加系統類和物件的個數,
- 狀態模式的結構與實作都較為復雜,如果使用不當將導致程式結構和代碼的混亂,
- 狀態模式對"開閉原則"的支持并不太好,
觀察者模式
下面我們來介紹觀察者模式
觀察者模式簡述
首先我們給出觀察者模式的概念:
- 又被稱為發布-訂閱(Publish/Subscribe)模式,它定義了一種一對多的依賴關系,讓多個觀察者物件同時監聽某一個主題物件,
- 這個主題物件在狀態變化時,會通知所有的觀察者物件,使他們能夠自動更新自己,
觀察者模式結構
在觀察者模式中有如下角色:
- Subject:抽象主題(抽象被觀察者),抽象主題角色把所有觀察者物件保存在一個集合里,每個主題都可以有任意數量的觀察者,抽象主題提供一個介面,可以增加和洗掉觀察者物件,
- ConcreteSubject:具體主題(具體被觀察者),該角色將有關狀態存入具體觀察者物件,在具體主題的內部狀態發生改變時,給所有注冊過的觀察者發送通知,
- Observer:抽象觀察者,是觀察者的抽象類,它定義了一個更新介面,使得在得到主題更改通知時更新自己,
- ConcrereObserver:具體觀察者,實作抽象觀察者定義的更新介面,以便在得到主題更改通知時更新自身的狀態,
觀察者模式案例
我們通過一個案例來解釋觀察者模式:

具體分析:
/*
【例】微信公眾號
在使用微信公眾號時,大家都會有這樣的體驗,當你關注的公眾號中有新內容更新的話,它就會推送給關注公眾號的微信用戶端,
我們使用觀察者模式來模擬這樣的場景,微信用戶就是觀察者,微信公眾號是被觀察者,有多個的微信用戶關注了這個公眾號,
其中微信公眾號就是被觀察者,被觀察者可以存盤多個觀察者,當被觀察者做出一些修改后,就會呼叫一個方法去通知觀察者并修改內容
*/
/* 代碼展示 */
// 抽象觀察者(觀察者中具有一個修改方法,當被觀察者被修改后,會導致觀察者呼叫update方法)
public interface Observer {
void update(String message);
}
// 具體觀察者(這里是指用戶,這里指wx公眾號發表文章后,觀察者會收到一條提示資訊)
public class WeixinUser implements Observer {
// 微信用戶名
private String name;
public WeixinUser(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + "-" + message);
}
}
// 抽象主題類(被觀察者)
public interface Subject {
// 增加訂閱者
public void attach(Observer observer);
// 洗掉訂閱者
public void detach(Observer observer);
// 通知訂閱者更新訊息
public void notify(String message);
}
// 具體主題類(被觀察者)
public class SubscriptionSubject implements Subject {
// 儲存訂閱公眾號的微信用戶
private List<Observer> weixinUserlist = new ArrayList<Observer>();
@Override
public void attach(Observer observer) {
weixinUserlist.add(observer);
}
@Override
public void detach(Observer observer) {
weixinUserlist.remove(observer);
}
// 當wx公眾號發表文章,就會呼叫該方法,然后通知各個觀察者去update
@Override
public void notify(String message) {
for (Observer observer : weixinUserlist) {
observer.update(message);
}
}
}
// 客戶端資訊
public class Client {
public static void main(String[] args) {
SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject();
//創建微信用戶
WeixinUser user1=new WeixinUser("孫悟空");
WeixinUser user2=new WeixinUser("豬悟能");
WeixinUser user3=new WeixinUser("沙悟凈");
//訂閱公眾號
mSubscriptionSubject.attach(user1);
mSubscriptionSubject.attach(user2);
mSubscriptionSubject.attach(user3);
//公眾號更新發出訊息給訂閱的微信用戶
mSubscriptionSubject.notify("傳智黑馬的專欄更新了");
}
}
觀察者模式分析
首先我們給出觀察者模式的適用場景:
- 物件間存在一對多關系,一個物件的狀態發生改變會影響其他物件,
- 當一個抽象模型有兩個方面,其中一個方面依賴于另一方面時,
然后我們給出觀察者模式的優點:
- 降低了目標與觀察者之間的耦合關系,兩者之間是抽象耦合關系,
- 被觀察者發送通知,所有注冊的觀察者都會收到資訊【可以實作廣播機制】
最后我們給出觀察者模式的缺點:
- 如果觀察者非常多的話,那么所有的觀察者收到被觀察者發送的通知會耗時
- 如果被觀察者有回圈依賴的話,那么被觀察者發送通知會使觀察者回圈呼叫,會導致系統崩潰
中介者模式
下面我們來介紹中介者模式
中介者模式簡述
首先我們給出中介者模式的概念:
- 又叫調停模式,定義一個中介角色來封裝一系列物件之間的互動,使原有物件之間的耦合松散,且可以獨立地改變它們之間的互動,
我們給出一個簡單的示例:
- 假設我們的作業組有六個人,我們其中只要有一個發生改變,那么其他人對于該員工的所有屬性方法都需要去更換
- 但是如果我們有一個中介者,我們與其他人的交際本身就只有中介者,那么即使一個員工發生改變,也只有該員工和中介者需要修改
中介者模式結構
中介者模式包含以下主要角色:
-
抽象中介者(Mediator)角色:它是中介者的介面,提供了同事物件注冊與轉發同事物件資訊的抽象方法,
-
具體中介者(ConcreteMediator)角色:實作中介者介面,定義一個 List 來管理同事物件,協調各個同事角色之間的互動關系,因此它依賴于同事角色,
-
抽象同事類(Colleague)角色:定義同事類的介面,保存中介者物件,提供同事物件互動的抽象方法,實作所有相互影響的同事類的公共功能,
-
具體同事類(Concrete Colleague)角色:是抽象同事類的實作者,當需要與其他同事物件互動時,由中介者物件負責后續的互動,
中介者模式案例
我們通過一個簡單的案例來介紹中介者模式:

具體分析:
/*
【例】租房
現在租房基本都是通過房屋中介,房主將房屋托管給房屋中介,而租房者從房屋中介獲取房屋資訊,房屋中介充當租房者與房屋所有者之間的中介者,
租房者本身需要和所有房東聯系才能獲得房屋資訊,但是可以通過中介獲得所有房屋資訊,這就實作了解耦操作
*/
/* 代碼展示 */
// 抽象中介者
public abstract class Mediator {
//申明一個聯絡方法(前者是資訊,后者是資訊發送人)
public abstract void constact(String message,Person person);
}
// 抽象同事類
public abstract class Person {
protected String name;
protected Mediator mediator;
public Person(String name,Mediator mediator){
this.name = name;
this.mediator = mediator;
}
}
// 具體同事類 房屋擁有者
public class HouseOwner extends Person {
public HouseOwner(String name, Mediator mediator) {
super(name, mediator);
}
//與中介者聯系
public void constact(String message){
mediator.constact(message, this);
}
//獲取資訊
public void getMessage(String message){
System.out.println("房主" + name +"獲取到的資訊:" + message);
}
}
// 具體同事類 承租人
public class Tenant extends Person {
public Tenant(String name, Mediator mediator) {
super(name, mediator);
}
//與中介者聯系
public void constact(String message){
mediator.constact(message, this);
}
//獲取資訊
public void getMessage(String message){
System.out.println("租房者" + name +"獲取到的資訊:" + message);
}
}
//中介機構
public class MediatorStructure extends Mediator {
//首先中介結構必須知道所有房主和租房者的資訊
private HouseOwner houseOwner;
private Tenant tenant;
public HouseOwner getHouseOwner() {
return houseOwner;
}
public void setHouseOwner(HouseOwner houseOwner) {
this.houseOwner = houseOwner;
}
public Tenant getTenant() {
return tenant;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
public void constact(String message, Person person) {
// 如果是房主進行聯系,則對應的租房者獲得房主的資訊
if (person == houseOwner) {
tenant.getMessage(message);
} else { //反之則是房主獲得資訊
houseOwner.getMessage(message);
}
}
}
//測驗類
public class Client {
public static void main(String[] args) {
//一個房主、一個租房者、一個中介機構
MediatorStructure mediator = new MediatorStructure();
//房主和租房者只需要知道中介機構即可
HouseOwner houseOwner = new HouseOwner("張三", mediator);
Tenant tenant = new Tenant("李四", mediator);
//中介結構要知道房主和租房者
mediator.setHouseOwner(houseOwner);
mediator.setTenant(tenant);
tenant.constact("需要租三室的房子");
houseOwner.constact("我這有三室的房子,你需要租嗎?");
}
}
中介者模式分析
首先我們給出中介者模式的適用場景:
- 系統中物件之間存在復雜的參考關系,系統結構混亂且難以理解,
- 當想創建一個運行于多個類之間的物件,又不想生成新的子類時,
然后我們給出中介者模式的優點:
-
松散耦合
中介者模式通過把多個同事物件之間的互動封裝到中介者物件里面,從而使得同事物件之間松散耦合,基本上可以做到互補依賴,這樣一來,同事物件就可以獨立地變化和復用,而不再像以前那樣“牽一處而動全身”了,
-
集中控制互動
多個同事物件的互動,被封裝在中介者物件里面集中管理,使得這些互動行為發生變化的時候,只需要修改中介者物件就可以了,當然如果是已經做好的系統,那么就擴展中介者物件,而各個同事類不需要做修改,
-
一對多關聯轉變為一對一的關聯
沒有使用中介者模式的時候,同事物件之間的關系通常是一對多的,引入中介者物件以后,中介者物件和同事物件的關系通常變成雙向的一對一,這會讓物件的關系更容易理解和實作,
最后我們給出中介者模式的缺點:
- 當同事類太多時,中介者的職責將很大,它會變得復雜而龐大,以至于系統難以維護,
迭代器模式
下面我們來介紹迭代器模式
迭代器模式簡述
首先我們來簡單介紹一下迭代器模式:
- 提供一個物件來順序訪問聚合物件中的一系列資料,而不暴露聚合物件的內部表示,
迭代器模式結構
迭代器模式主要包含以下角色:
-
抽象聚合(Aggregate)角色:定義存盤、添加、洗掉聚合元素以及創建迭代器物件的介面,
-
具體聚合(ConcreteAggregate)角色:實作抽象聚合類,回傳一個具體迭代器的實體,
-
抽象迭代器(Iterator)角色:定義訪問和遍歷聚合元素的介面,通常包含 hasNext()、next() 等方法,
-
具體迭代器(Concretelterator)角色:實作抽象迭代器介面中所定義的方法,完成對聚合物件的遍歷,記錄遍歷的當前位置,
迭代器模式案例
我們通過一個簡單的案例來介紹迭代器:

具體分析:
/*
【例】定義一個可以存盤學生物件的容器物件,將遍歷該容器的功能交由迭代器實作
Student:學生物體類
StudentIterator:抽象迭代器,宣告hasNext、next方法
StudentIteratorImpl:具體迭代器,重寫所有的抽象方法
StudentAggregate:抽象容器類,包含添加元素,洗掉元素,獲取迭代器物件的方法
StudentAggregateImpl:具體的容器類,重寫所有的方法
*/
/* 代碼展示 */
// 抽象迭代器
public interface StudentIterator {
boolean hasNext();
Student next();
}
// 具體迭代器
public class StudentIteratorImpl implements StudentIterator {
private List<Student> list;
private int position = 0;
public StudentIteratorImpl(List<Student> list) {
this.list = list;
}
@Override
public boolean hasNext() {
return position < list.size();
}
@Override
public Student next() {
Student currentStudent = list.get(position);
position ++;
return currentStudent;
}
}
// 抽象容器類
public interface StudentAggregate {
void addStudent(Student student);
void removeStudent(Student student);
StudentIterator getStudentIterator();
}
// 具體容器類(僅僅只是一個容器,用于生成一個統一的迭代器介面,實際內部還是呼叫我們上面定義的抽象迭代器的子類迭代器)
public class StudentAggregateImpl implements StudentAggregate {
private List<Student> list = new ArrayList<Student>(); // 學生串列
@Override
public void addStudent(Student student) {
this.list.add(student);
}
@Override
public void removeStudent(Student student) {
this.list.remove(student);
}
@Override
public StudentIterator getStudentIterator() {
return new StudentIteratorImpl(list);
}
}
迭代器模式分析
首先我們給出迭代器模式的適用場景:
- 當需要為聚合物件提供多種遍歷方式時,
- 當需要為遍歷不同的聚合結構提供一個統一的介面時,
- 當訪問一個聚合物件的內容而無須暴露其內部細節的表示時,
然后我們給出迭代器模式的優點:
- 它支持以不同的方式遍歷一個聚合物件,在同一個聚合物件上可以定義多種遍歷方式,在迭代器模式中只需要用一個不同的迭代器來替換原有迭代器即可改變遍歷演算法,我們也可以自己定義迭代器的子類以支持新的遍歷方式,
- 迭代器簡化了聚合類,由于引入了迭代器,在原有的聚合物件中不需要再自行提供資料遍歷等方法,這樣可以簡化聚合類的設計,
- 在迭代器模式中,由于引入了抽象層,增加新的聚合類和迭代器類都很方便,無須修改原有代碼,滿足 “開閉原則” 的要求,
最后我們給出迭代器模式的缺點:
- 增加了類的個數,這在一定程度上增加了系統的復雜性,
訪問者模式
下面我們來介紹訪問者模式
訪問者模式簡述
首先我們來簡單介紹一下訪問者模式:
- 封裝一些作用于某種資料結構中的各元素的操作,它可以在不改變這個資料結構的前提下定義作用于這些元素的新的操作,
訪問者模式結構
訪問者模式包含以下主要角色:
- 抽象訪問者(Visitor)角色:定義了對每一個元素
(Element)訪問的行為,它的引數就是可以訪問的元素,它的方法個數理論上來講與元素類個數(Element的實作類個數)是一樣的,從這點不難看出,訪問者模式要求元素類的個數不能改變, - 具體訪問者(ConcreteVisitor)角色:給出對每一個元素類訪問時所產生的具體行為,
- 抽象元素(Element)角色:定義了一個接受訪問者的方法(
accept),其意義是指,每一個元素都要可以被訪問者訪問, - 具體元素(ConcreteElement)角色: 提供接受訪問方法的具體實作,而這個具體的實作,通常情況下是使用訪問者提供的訪問該元素類的方法,
- 物件結構(Object Structure)角色:定義當中所提到的物件結構,物件結構是一個抽象表述,具體點可以理解為一個具有容器性質或者復合物件特性的類,它會含有一組元素(
Element),并且可以迭代這些元素,供訪問者訪問,
訪問者模式案例
我們給出一個簡單的案例來解釋訪問者模式:

具體分析:
/*
【例】給寵物喂食
現在養寵物的人特別多,我們就以這個為例,當然寵物還分為狗,貓等,要給寵物喂食的話,主人可以喂,其他人也可以喂食,
- 訪問者角色:給寵物喂食的人
- 具體訪問者角色:主人、其他人
- 抽象元素角色:動物抽象類
- 具體元素角色:寵物狗、寵物貓
- 結構物件角色:主人家
*/
/* 代碼展示 */
// 抽象訪問者
public interface Person {
void feed(Cat cat);
void feed(Dog dog);
}
// 具體訪問者(Owner主人,Someone其他人)
public class Owner implements Person {
@Override
public void feed(Cat cat) {
System.out.println("主人喂食貓");
}
@Override
public void feed(Dog dog) {
System.out.println("主人喂食狗");
}
}
public class Someone implements Person {
@Override
public void feed(Cat cat) {
System.out.println("其他人喂食貓");
}
@Override
public void feed(Dog dog) {
System.out.println("其他人喂食狗");
}
}
// 抽象節點
public interface Animal {
void accept(Person person);
}
// 具體節點
public class Dog implements Animal {
@Override
public void accept(Person person) {
person.feed(this);
System.out.println("好好吃,汪汪汪!!!");
}
}
public class Cat implements Animal {
@Override
public void accept(Person person) {
person.feed(this);
System.out.println("好好吃,喵喵喵!!!");
}
}
// 物件結構
public class Home {
// 需要操作的節點
private List<Animal> nodeList = new ArrayList<Animal>();
// 進行操作節點的訪問者
public void action(Person person) {
for (Animal node : nodeList) {
// 訪問者觸動了節點,以節點的形式呼叫方法
node.accept(person);
}
}
//添加操作
public void add(Animal animal) {
nodeList.add(animal);
}
}
// 測驗類
public class Client {
public static void main(String[] args) {
Home home = new Home();
home.add(new Dog());
home.add(new Cat());
Owner owner = new Owner();
home.action(owner);
Someone someone = new Someone();
home.action(someone);
}
}
訪問者模式分析
首先我們給出訪問者模式的適用場景:
- 物件結構相對穩定,但其操作演算法經常變化的程式,
- 物件結構中的物件需要提供多種不同且不相關的操作,而且要避免讓這些操作的變化影響物件的結構,
然后我們給出訪問者模式的優點:
-
擴展性好
在不修改物件結構中的元素的情況下,為物件結構中的元素添加新的功能,
-
復用性好
通過訪問者來定義整個物件結構通用的功能,從而提高復用程度,
-
分離無關行為
通過訪問者來分離無關的行為,把相關的行為封裝在一起,構成一個訪問者,這樣每一個訪問者的功能都比較單一,
最后我們給出訪問者模式的缺點:
-
物件結構變化很困難
在訪問者模式中,每增加一個新的元素類,都要在每一個具體訪問者類中增加相應的具體操作,這違背了“開閉原則”,
-
違反了依賴倒置原則
訪問者模式依賴了具體類,而沒有依賴抽象類,
備忘錄模式
下面我們來介紹備忘錄模式
備忘錄模式簡述
我們首先給出備忘錄模式的概念:
- 快照模式,在不破壞封裝性的前提下,捕獲一個物件的內部狀態,并在該物件之外保存這個狀態,以便以后當需要時能將該物件恢復到原先保存的狀態,
備忘錄模式結構
備忘錄模式的主要角色如下:
- 發起人(Originator)角色:記錄當前時刻的內部狀態資訊,提供創建備忘錄和恢復備忘錄資料的功能,實作其他業務功能,它可以訪問備忘錄里的所有資訊,
- 備忘錄(Memento)角色:負責存盤發起人的內部狀態,在需要的時候提供這些內部狀態給發起人,
- 管理者(Caretaker)角色:對備忘錄進行管理,提供保存與獲取備忘錄的功能,但其不能對備忘錄的內容進行訪問與修改,
備忘錄有兩個等效的介面:
- 窄介面:管理者(Caretaker)物件(和其他發起人物件之外的任何物件)看到的是備忘錄的窄介面(narror Interface),這個窄介面只允許他把備忘錄物件傳給其他的物件,
- 寬介面:與管理者看到的窄介面相反,發起人物件可以看到一個寬介面(wide Interface),這個寬介面允許它讀取所有的資料,以便根據這些資料恢復這個發起人物件的內部狀態,
白箱備忘錄模式
我們通過一個案例來介紹白箱備忘錄:

/*
【例】游戲挑戰BOSS
游戲中的某個場景,一游戲角色有生命力、攻擊力、防御力等資料,在打Boss前和后一定會不一樣的,我們允許玩家如果感覺與Boss決斗的效果不理想可以讓游戲恢復到決斗之前的狀態,
【白箱備忘錄】
備忘錄角色對任何物件都提供一個介面,即寬介面,備忘錄角色的內部所存盤的狀態就對所有物件公開,
由于備忘錄角色對任何物件都提供寬介面(所有權限),所以實際上是不安全的
*/
/* 代碼展示 */
// 游戲角色類(發起人)
public class GameRole {
private int vit; //生命力
private int atk; //攻擊力
private int def; //防御力
//初始化狀態
public void initState() {
this.vit = 100;
this.atk = 100;
this.def = 100;
}
//戰斗
public void fight() {
this.vit = 0;
this.atk = 0;
this.def = 0;
}
// 保存角色狀態(創建出一個備忘錄,該備忘錄記錄當前狀況)
public RoleStateMemento saveState() {
return new RoleStateMemento(vit, atk, def);
}
// 回復角色狀態(需要一個備忘錄,記錄需要恢復的狀態)
public void recoverState(RoleStateMemento roleStateMemento) {
this.vit = roleStateMemento.getVit();
this.atk = roleStateMemento.getAtk();
this.def = roleStateMemento.getDef();
}
public void stateDisplay() {
System.out.println("角色生命力:" + vit);
System.out.println("角色攻擊力:" + atk);
System.out.println("角色防御力:" + def);
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
// 游戲狀態存盤類(備忘錄類)
public class RoleStateMemento {
private int vit;
private int atk;
private int def;
public RoleStateMemento(int vit, int atk, int def) {
this.vit = vit;
this.atk = atk;
this.def = def;
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
// 角色狀態管理者類(管理者,內置一個備忘錄)
public class RoleStateCaretaker {
// 但是請注意,由于我們的備忘錄是一個類,我們的管理者可以對其roleStateMemento進行修改,因此是不符合要求的
private RoleStateMemento roleStateMemento;
public RoleStateMemento getRoleStateMemento() {
return roleStateMemento;
}
public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
this.roleStateMemento = roleStateMemento;
}
}
// 測驗類
public class Client {
public static void main(String[] args) {
System.out.println("------------大戰Boss前------------");
//大戰Boss前
GameRole gameRole = new GameRole();
gameRole.initState();
gameRole.stateDisplay();
//保存進度
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setRoleStateMemento(gameRole.saveState());
System.out.println("------------大戰Boss后------------");
//大戰Boss時,損耗嚴重
gameRole.fight();
gameRole.stateDisplay();
System.out.println("------------恢復之前狀態------------");
//恢復之前狀態
gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());
gameRole.stateDisplay();
}
}
黑箱備忘錄模式
我們通過一個案例來介紹黑箱備忘錄:

具體分析:
/*
【例】游戲挑戰BOSS
游戲中的某個場景,一游戲角色有生命力、攻擊力、防御力等資料,在打Boss前和后一定會不一樣的,我們允許玩家如果感覺與Boss決斗的效果不理想可以讓游戲恢復到決斗之前的狀態,
【黑箱備忘錄】
備忘錄角色對發起人物件提供一個寬介面,而為其他物件提供一個窄介面,
在Java語言中,實作雙重介面的辦法就是將備忘錄類設計成發起人類的內部成員類,
*/
/* 代碼展示 */
// 備忘錄 窄介面(只有一個標識,除發起人外都使用該介面,只有一個概念,無法對其進行操作)
public interface Memento {
}
// 游戲角色類
public class GameRole {
// 前面的設定都是一樣的,但是在內部定義了備忘錄寬介面
private int vit; //生命力
private int atk; //攻擊力
private int def; //防御力
//初始化狀態
public void initState() {
this.vit = 100;
this.atk = 100;
this.def = 100;
}
//戰斗
public void fight() {
this.vit = 0;
this.atk = 0;
this.def = 0;
}
//保存角色狀態
public Memento saveState() {
return new RoleStateMemento(vit, atk, def);
}
//回復角色狀態
public void recoverState(Memento memento) {
RoleStateMemento roleStateMemento = (RoleStateMemento) memento;
this.vit = roleStateMemento.getVit();
this.atk = roleStateMemento.getAtk();
this.def = roleStateMemento.getDef();
}
public void stateDisplay() {
System.out.println("角色生命力:" + vit);
System.out.println("角色攻擊力:" + atk);
System.out.println("角色防御力:" + def);
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
// 備忘錄寬介面,基于備忘錄窄介面的擴展,由于屬于發起人的內部類,發起人可以對其進行操作,備忘錄也可以獲得對應值
private class RoleStateMemento implements Memento {
private int vit;
private int atk;
private int def;
public RoleStateMemento(int vit, int atk, int def) {
this.vit = vit;
this.atk = atk;
this.def = def;
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
}
// 角色狀態管理者類(由于只有一個窄介面,所以無法進行操作,安全!)
public class RoleStateCaretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
// 客戶端
public class Client {
public static void main(String[] args) {
System.out.println("------------大戰Boss前------------");
//大戰Boss前
GameRole gameRole = new GameRole();
gameRole.initState();
gameRole.stateDisplay();
//保存進度
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setMemento(gameRole.saveState());
System.out.println("------------大戰Boss后------------");
//大戰Boss時,損耗嚴重
gameRole.fight();
gameRole.stateDisplay();
System.out.println("------------恢復之前狀態------------");
//恢復之前狀態
gameRole.recoverState(roleStateCaretaker.getMemento());
gameRole.stateDisplay();
}
}
備忘錄模式分析
首先我們給出備忘錄模式的適用場景:
- 需要保存與恢復資料的場景,如玩游戲時的中間結果的存檔功能,
- 需要提供一個可回滾操作的場景,如 Word、記事本、Photoshop,idea等軟體在編輯時按 Ctrl+Z 組合鍵,還有資料庫中事務操作,
然后我們給出備忘錄模式的優點:
- 提供了一種可以恢復狀態的機制,當用戶需要時能夠比較方便地將資料恢復到某個歷史的狀態,
- 實作了內部狀態的封裝,除了創建它的發起人之外,其他物件都不能夠訪問這些狀態資訊,
- 簡化了發起人類,發起人不需要管理和保存其內部狀態的各個備份,所有狀態資訊都保存在備忘錄中,并由管理者進行管理,這符合單一職責原則,
最后我們給出備忘錄模式的缺點:
- 資源消耗大,如果要保存的內部狀態資訊過多或者特別頻繁,將會占用比較大的記憶體資源,
解釋器模式
最后我們介紹一下解釋器模式
解釋器模式簡述
我們首先給出解釋器模式的概念:
- 給定一個語言,定義它的文法表示,并定義一個解釋器,這個解釋器使用該標識來解釋語言中的句子,
我們再來解釋一下文法:
- 文法是用于描述語言的語法結構的形式規則,
我們給出一個文法案例:
expression ::= value | plus | minus
plus ::= expression ‘+’ expression
minus ::= expression ‘-’ expression
value ::= integer
// 注意:這里的符號“::=”表示“定義為”的意思,豎線 | 表示或,左右的其中一個,引號內為字符本身,引號外為語法,
// 上面規則描述為 :
// 運算式可以是一個值,也可以是plus或者minus運算,而plus和minus又是由運算式結合運算子構成,值的型別為整型數,
// 該內容比較生澀難懂,可以看一下案例會好懂很多~
解釋器模式結構
解釋器模式包含以下主要角色,
-
抽象運算式(Abstract Expression)角色:定義解釋器的介面,約定解釋器的解釋操作,主要包含解釋方法 interpret(),
-
終結符運算式(Terminal Expression)角色:是抽象運算式的子類,用來實作文法中與終結符相關的操作,文法中的每一個終結符都有一個具體終結運算式與之相對應,
-
非終結符運算式(Nonterminal Expression)角色:也是抽象運算式的子類,用來實作文法中與非終結符相關的操作,文法中的每條規則都對應于一個非終結符運算式,
-
環境(Context)角色:通常包含各個解釋器需要的資料或是公共的功能,一般用來傳遞被所有解釋器共享的資料,后面的解釋器可以從這里獲取這些值,
-
客戶端(Client):主要任務是將需要分析的句子或運算式轉換成使用解釋器物件描述的抽象語法樹,然后呼叫解釋器的解釋方法,當然也可以通過環境角色間接訪問解釋器的解釋方法,
解釋器模式案例
我們通過一個基本案例來理解解釋器模式:

具體分析:
/*
【例】設計實作加減法的軟體
首先我們來解釋幾個概念:
- 抽象運算式:書寫在運算程序中的所有Expression
- 終結運算式:不需要再計算,直接給出最終結果(Value,Variable)
- 非終結運算式:還不是最終結果,還需要繼續運算(Plus,Minus)
AbstractExpreesion:
核心點,明白來說就是Expreesion運算式,這里的運算式包括有Variable變數,Value常量,也包括Plus和Minus運算
只有一個方法interpret,用于給子類去實作,如果是終結運算式直接回傳結果,如果是非終結運算式則進一步運算,計算時均呼叫方法
*/
/* 代碼展示 */
// 抽象角色AbstractExpression
public abstract class AbstractExpression {
// 只有一個方法,在運算時呼叫
public abstract int interpret(Context context);
}
// 終結符運算式角色(常量,例:1,2,3...)
public class Value extends AbstractExpression {
private int value;
public Value(int value) {
this.value = https://www.cnblogs.com/qiuluoyuweiliang/archive/2023/02/04/value;
}
// 直接回傳結果即可
@Override
public int interpret(Context context) {
return value;
}
@Override
public String toString() {
return new Integer(value).toString();
}
}
// 非終結符運算式角色 加法運算式
public class Plus extends AbstractExpression {
// 既然是加法,那么就有左右兩側,左右兩側同樣是Expression,既可以是終結符運算式,也可以是非終結符運算式
private AbstractExpression left;
private AbstractExpression right;
public Plus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
// 運算時需要將左右兩側進行解釋并相加
@Override
public int interpret(Context context) {
return left.interpret(context) + right.interpret(context);
}
@Override
public String toString() {
return"(" + left.toString() + " + " + right.toString() + ")";
}
}
// 非終結符運算式角色 減法運算式
public class Minus extends AbstractExpression {
private AbstractExpression left;
private AbstractExpression right;
// 減法原理相同
public Minus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
// 運算時需要將左右兩側進行解釋并相減
@Override
public int interpret(Context context) {
return left.interpret(context) - right.interpret(context);
}
@Override
public String toString() {
return "(" + left.toString() + " - " + right.toString() + ")";
}
}
// 終結符運算式角色 變數運算式(例:a=1,b=2...)
public class Variable extends AbstractExpression {
private String name;
public Variable(String name) {
this.name = name;
}
@Override
public int interpret(Context ctx) {
return ctx.getValue(this);
}
@Override
public String toString() {
return name;
}
}
// 環境類(存放變數的值)
public class Context {
private Map<Variable, Integer> map = new HashMap<Variable, Integer>();
public void assign(Variable var, Integer value) {
map.put(var, value);
}
public int getValue(Variable var) {
Integer value = https://www.cnblogs.com/qiuluoyuweiliang/archive/2023/02/04/map.get(var);
return value;
}
}
// 測驗類
public class Client {
public static void main(String[] args) {
Context context = new Context();
Variable a = new Variable("a");
Variable b = new Variable("b");
Variable c = new Variable("c");
Variable d = new Variable("d");
Variable e = new Variable("e");
//Value v = new Value(1);
context.assign(a, 1);
context.assign(b, 2);
context.assign(c, 3);
context.assign(d, 4);
context.assign(e, 5);
AbstractExpression expression = new Minus(new Plus(new Plus(new Plus(a, b), c), d), e);
System.out.println(expression + "= " + expression.interpret(context));
}
}
解釋器模式分析
我們首先給出解釋器模式的適用場景:
-
當語言的文法較為簡單,且執行效率不是關鍵問題時,
-
當問題重復出現,且可以用一種簡單的語言來進行表達時,
-
當一個語言需要解釋執行,并且語言中的句子可以表示為一個抽象語法樹的時候,
然后我們給出解釋器模式的優點:
-
易于改變和擴展文法,
由于在解釋器模式中使用類來表示語言的文法規則,因此可以通過繼承等機制來改變或擴展文法,每一條文法規則都可以表示為一個類,因此可以方便地實作一個簡單的語言,
-
實作文法較為容易,
在抽象語法樹中每一個運算式節點類的實作方式都是相似的,這些類的代碼撰寫都不會特別復雜,
-
增加新的解釋運算式較為方便,
如果用戶需要增加新的解釋運算式只需要對應增加一個新的終結符運算式或非終結符運算式類,原有運算式類代碼無須修改,符合 "開閉原則",
最后我們給出解釋器模式的缺點:
-
對于復雜文法難以維護,
在解釋器模式中,每一條規則至少需要定義一個類,因此如果一個語言包含太多文法規則,類的個數將會急劇增加,導致系統難以管理和維護,
-
執行效率較低,
由于在解釋器模式中使用了大量的回圈和遞回呼叫,因此在解釋較為復雜的句子時其速度很慢,而且代碼的除錯程序也比較麻煩,
結束語
關于行為型模式就介紹到這里了,目前我們已經學習了完整的二十三種設計模式,希望能為你帶來幫助~
附錄
該文章屬于學習內容,具體參考B站黑馬程式員的Java設計模式詳解
這里附上視頻鏈接:10.設計模式-行為型模式概述_嗶哩嗶哩_bilibili
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/543038.html
標籤:其他
下一篇:構建億級別的訊息推送基礎模型
