前言
- 此篇博文內容續接的是 UML建模語言、設計原則、建立型設計模式 的內容,有興趣的可以點前面的連結去看一下
3.2、行為型
這類設計模式是專門用於:物件間的高效溝通和職責委派
* 3.2.1、責任鏈模式
定義:責任鏈模式又名職責鏈模式,指的是:對某個請求的所有處理構成一條鏈,如果鏈上的某一處理者可以處理,則處理後返回。如果不能處理則將請求傳遞給鏈上的下一個處理者
廢話文學:所謂責任鏈模式就是為了避免請求傳送者與多個請求處理者耦合在一起,於是將所有請求的處理者透過前一物件記住其下一個物件的引用而連成一條鏈;當有請求發生時,可將請求沿著這條鏈傳遞,直到有物件處理它為止
場景理解:開發中的捕獲異常,只有遇到對應的某一個 或 某一類異常時,才會有對應的處理機制;還有Servlet程式設計中的Filter過濾器;以及SpringMVC執行原理(請求經過DispatcherServlet,然後經其轉到HandletMapping,然後透過HandlerExcuttionChain這條執行鏈將結果返回給DispatcherServlet.....,)
圖示理解:使用的是Servlet的Filter舉例
生活中的例子:跳槽離職,要找好多人審批、以及擊鼓傳花.....
使用場景:在處理訊息的時候要過濾很多道時就可以使用責任鏈模式
責任鏈模式的角色
- 抽象處理者:一個請求介面,裡面包含抽象的處理請求的方法 和 後繼連結
- 具體處理者:抽象處理者的子類,實現了處理請求方法,判斷請求是否能夠處理,能則處理返回結果,否則將請求傳給它的後繼者
- 客戶端:建立處理鏈,並向鏈頭的具體處理者物件提交請求,它不關心處理細節和請求的傳遞過程
責任鏈模式的類圖
3.2.1.1、簡單邏輯
1、抽象處理者
package com.zixieqing.handler;
/**
* <p>@description : 該類功能 抽象處理者:做使用者名稱判斷
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public abstract class AbstractHakimHandler {
private AbstractHakimHandler next;
public AbstractHakimHandler getNext() {
return next;
}
public void setNext(AbstractHakimHandler next) {
this.next = next;
}
public abstract String hakim(String name);
}
2、具體處理者
-
package com.zixieqing.handler; import com.zixieqing.UsernameEnum; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <p>@description : 該類功能 第一個處理者 * </p> * <p>@package : com.zixieqing</p> * <p>@author : ZiXieqing</p> * <p>@version : V1.0.0</p> */ public class OneHakimHandler extends AbstractHakimHandler{ private Logger logger = LoggerFactory.getLogger(OneHakimHandler.class); @Override public String hakim(String name) { logger.info("進入OneHakimHandler處理器"); // 如果當前處理者能處理該請求,則進行處理,返回結果 if (UsernameEnum.ZIXIEQING.toString().equals(name.trim().toUpperCase())) { return "歡迎:" + name + " 進入系統"; } // 如果當前處理者不能處理改請求,則丟給後繼者 if (null != getNext()) return getNext().hakim(name); return "沒有處理者能夠處理該請求"; } }
-
package com.zixieqing.handler; import com.zixieqing.UsernameEnum; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <p>@description : 該類功能 第二個處理者 * </p> * <p>@package : com.zixieqing.handler</p> * <p>@author : ZiXieqing</p> * <p>@version : V1.0.0</p> */ public class TwoHakimHandler extends AbstractHakimHandler{ private Logger logger = LoggerFactory.getLogger(OneHakimHandler.class); @Override public String hakim(String name) { logger.info("進入TwoHakimHandler處理器"); if (UsernameEnum.ZIMINGXUAN.toString().equals(name.trim().toUpperCase())) { logger.info("正在進行資料查詢.........."); logger.info("查詢到使用者:{}", name); return "歡迎:" + name + " 進入系統"; } if (null != getNext()) return getNext().hakim(name); return "沒有處理者能夠處理該請求"; } }
-
上述涉及到的列舉類
-
package com.zixieqing; /** * <p>@description : 該類功能 使用者名稱集合 * </p> * <p>@package : com.zixieqing</p> * <p>@author : ZiXieqing</p> * <p>@version : V1.0.0</p> */ public enum UsernameEnum { /** * 紫邪情 */ ZIXIEQING, /** * 紫明軒 */ ZIMINGXUAN, ; }
-
3、客戶端
package com.zixieqing;
import com.zixieqing.handler.OneHakimHandler;
import com.zixieqing.handler.TwoHakimHandler;
/**
* <p>@description : 該類功能 客戶端:這部分的程式碼可以封裝起來,然後提供一個公共方法給外部呼叫即可
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class APITest{
public static void main(String[] args) {
// 建立處理鏈 並 將請求丟給鏈頭的處理者
OneHakimHandler handler1 = new OneHakimHandler();
handler1.setNext(new TwoHakimHandler());
System.out.println(handler1.hakim("LISI"));
System.out.println("============華麗的分隔符=============");
System.out.println(handler1.hakim("ZIXIEQING"));
System.out.println("============華麗的分隔符=============");
System.out.println(handler1.hakim("zimingxuan"));
}
}
責任鏈模式的不足
- 不能保證每個請求一定能被處理,如:上面的“LISI",原因:因為請求沒有明確的接收者,所以一個請求可能一直傳到處理鏈的末端都還未被處理
- 責任鏈是否合理需要靠客戶端來進行建立,所以增加了客戶端的複雜性,可能出現客戶端中責任鏈的錯誤設定而導致系統出錯
- 如果客戶端中的責任鏈過長,那麼就需要很多個處理物件的參與,所以系統效能會受到影響
* 3.2.2、命令模式
定義:指的是請求以命令的形式包裹在物件中,並傳給呼叫物件。呼叫物件尋找可以處理該命令的合適的物件,並把該命令傳給相應的物件,該物件執行命令
命令模式是將操作請求(行為請求者)和邏輯實現(行為實現者)進行了分離,從而降低耦合、方便擴充套件
適用場景: 只要認為是命令的地方都可以使用,如:CMD、GUI介面中的按鈕............
命令模式類圖
- 場景理解:
- 電視機換臺:1、想要換臺(呼叫者:發起請求);2、按下遙控器的換臺按鈕(將請求包裝成了命令物件);3、電視機進行換臺(接收者:執行請求相應的操作)
- 去飯店吃飯:1、你要吃飯(呼叫者:你要選單上的某菜,整個選單就是所有可以呼叫的命令);2、跟服務員講你要選單上的什麼菜(命令物件:服務員在本子上記下菜名,即:將請求轉成了命令物件);3、服務員將寫有菜名的紙交給廚師(接收者:廚師透過命令物件接收到呼叫者的請求,開始對應的操作:炒菜)
3.2.2.1、簡單邏輯
使用上面去飯店吃飯為例,對這個場景進行拆解,可以得到如下的邏輯圖
- 服務員:呼叫者
- 選單:命令
- 廚師:接收者
- 邏輯順序是:呼叫者(invoker)——> 命令(command) ——> 接收者(receiver);而我寫程式碼的習慣順序是逆向的
1、先決條件
- 菜組成
package com.zixieqing;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.util.List;
/**
* <p>@description : 該類功能 菜
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
@Data
@ToString
@Accessors(chain = true)
public class Food {
/**
* 菜名
*/
private String name;
/**
* 油:菜油、豬油、地溝油(^_^)......
*/
private String oil;
/**
* 辣椒型別:有線辣椒、小米椒、青椒、朝天椒..........
*/
private String chili;
/**
* 配料:食鹽、味精、八角、糖、醬油、耗油、醋、料酒、姜、蔥、蒜.........
*/
private List<String> excipients;
/**
* 容器:碗、鍋、盤子
*/
private String container;
}
- 選單列舉類
package com.zixieqing;
/**
* <p>@description : 該類功能 選單列舉類:就是去點菜時需要看有哪些菜的選單
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public enum MenuEnum {
/**
* 魚香肉絲
*/
YU_XIANG_ROU_SI("魚香肉絲", "18"),
/**
* 鴛鴦鍋
*/
YUAN_YANG_GUO("鴛鴦鍋", "50"),
;
private String foodName;
private String price;
MenuEnum(String foodName, String price) {
this.foodName = foodName;
this.price = price;
}
public String getFoodName() {
return foodName;
}
public void setFoodName(String foodName) {
this.foodName = foodName;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
}
2、接收者:廚師
package com.zixieqing.command;
import com.zixieqing.Food;
import java.util.List;
/**
* <p>@description : 該類功能 廚師
* </p>
* <p>@package : com.zixieqing.command</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface IChef {
Food cook(List<String> foodList);
}
- 張師:專做火鍋系列
package com.zixieqing.command.impl;
import com.zixieqing.Food;
import com.zixieqing.MenuEnum;
import com.zixieqing.command.IChef;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 該類功能 張師:假如他是專門做火鍋系列的
* </p>
* <p>@package : com.zixieqing.command.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ZhangChefImpl implements IChef {
private Logger logger = LoggerFactory.getLogger(ZhangChefImpl.class);
@Override
public Food cook(List<String> foodList) {
for (String name : foodList) {
if (MenuEnum.YUAN_YANG_GUO.getFoodName().equals(name)) {
logger.info("張師正在準備做:{}", name);
List<String> excipients = new ArrayList<>();
excipients.add("食鹽半勺");
excipients.add("味精少量");
excipients.add("八角微量");
excipients.add("糖少許");
excipients.add("醬油半勺");
excipients.add("耗油微量");
excipients.add("蔥薑蒜適量");
Food food = new Food();
food.setName(name)
.setOil("菜油")
.setChili("朝天椒")
.setExcipients(excipients)
.setContainer("鴛鴦鍋");
logger.info("張師做好了:{},成品為:{}", name, food);
return food;
}
}
return null;
}
}
- 李師:專做炒菜系列
package com.zixieqing.command.impl;
import com.zixieqing.Food;
import com.zixieqing.MenuEnum;
import com.zixieqing.command.IChef;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 該類功能 李師:假如他是專門做炒菜系列的
* </p>
* <p>@package : com.zixieqing.command.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class LiChefImpl implements IChef {
private Logger logger = LoggerFactory.getLogger(LiChefImpl.class);
@Override
public Food cook(List<String> foodList) {
for (String name : foodList) {
if (MenuEnum.YU_XIANG_ROU_SI.getFoodName().equals(name)) {
logger.info("李師正在準備做:{}", name);
List<String> excipients = new ArrayList<>();
excipients.add("食鹽少量");
excipients.add("味精少許");
excipients.add("蔥蒜少許");
Food food = new Food();
food.setName(name)
.setOil("豬油")
.setChili("青椒")
.setExcipients(excipients)
.setContainer("盤子");
logger.info("李師做好了:{},成品為:{}", name, food);
return food;
}
}
return null;
}
}
3、命令:選單
package com.zixieqing.command;
import java.util.List;
/**
* <p>@description : 該類功能 選單:就是服務員在紙上寫的那些菜名,即:命令物件
* </p>
* <p>@package : com.zixieqing.command</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface IMenu {
void make(List<String> foodList);
}
- 火鍋型別選單
package com.zixieqing.command.impl;
import com.zixieqing.command.IChef;
import com.zixieqing.command.IMenu;
import java.util.List;
/**
* <p>@description : 該類功能 火鍋型別的選單
* </p>
* <p>@package : com.zixieqing.command.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class HuoGuoMenuImpl implements IMenu {
private IChef chef;
public HuoGuoMenuImpl(IChef chef) {
this.chef = chef;
}
@Override
public void make(List<String> foodList) {
chef.cook(foodList);
}
}
- 炒菜型別選單
package com.zixieqing.command.impl;
import com.zixieqing.command.IChef;
import com.zixieqing.command.IMenu;
import java.util.List;
/**
* <p>@description : 該類功能 炒菜型別的選單
* </p>
* <p>@package : com.zixieqing.command.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ChaoCaiMenuImpl implements IMenu {
private IChef chef;
public ChaoCaiMenuImpl(IChef chef) {
this.chef = chef;
}
@Override
public void make(List<String> foodList) {
chef.cook(foodList);
}
}
4、呼叫者:服務員
package com.zixieqing.command;
import com.zixieqing.MenuEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 該類功能 服務員
* </p>
* <p>@package : com.zixieqing.command</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Waiter {
private Logger logger = LoggerFactory.getLogger(Waiter.class);
/**
* 服務員本子上記的菜名
*/
private List<String> foodList = new ArrayList<>();
/**
* 本店選單
*/
private MenuEnum[] values = MenuEnum.values();
private IMenu menu;
public Waiter(IMenu menu) {
this.menu = menu;
}
/**
* <p>@description : 該方法功能 點單
* </p>
* <p>@methodName : order</p>
* <p>@author: ZiXieqing</p>
* <p>@version: V1.0.0</p>
* @param foodName 菜名
*/
public void order(String foodName) {
for (MenuEnum value : values) {
// 判斷客人所說的菜名是否在本店的選單中
if (foodName.trim().equals(value.getFoodName()))
this.foodList.add(foodName);
}
}
/**
* <p>@description : 該方法功能 下單
* </p>
* <p>@methodName : placeOrder</p>
* <p>@author: ZiXieqing</p>
* <p>@version: V1.0.0</p>
*
*/
public void placeOrder() {
menu.make(foodList);
foodList.clear();
}
}
5、測試
package com.zixieqing;
import com.zixieqing.command.Waiter;
import com.zixieqing.command.impl.ChaoCaiMenuImpl;
import com.zixieqing.command.impl.HuoGuoMenuImpl;
import com.zixieqing.command.impl.LiChefImpl;
import com.zixieqing.command.impl.ZhangChefImpl;
/**
* <p>@description : 該類功能 測試
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class APITest {
public static void main(String[] args) {
Waiter waiter = new Waiter(new HuoGuoMenuImpl(new ZhangChefImpl()));
// 點單
waiter.order("鴛鴦鍋");
// 下單
waiter.placeOrder();
System.out.println("==========華麗的分隔符==========");
Waiter newWaiter = new Waiter(new ChaoCaiMenuImpl(new LiChefImpl()));
// 點單
newWaiter.order("魚香肉絲");
// 下單
newWaiter.placeOrder();
}
}
- 結果
11:43:54.317 [main] INFO c.z.command.impl.ZhangChefImpl - 張師正在準備做:鴛鴦鍋
11:43:54.320 [main] INFO c.z.command.impl.ZhangChefImpl - 張師做好了:鴛鴦鍋,成品為:Food(name=鴛鴦鍋, oil=菜油, chili=朝天椒, excipients=[食鹽半勺, 味精少量, 八角微量, 糖少許, 醬油半勺, 耗油微量, 蔥薑蒜適量], container=鴛鴦鍋)
==========華麗的分隔符==========
11:43:54.320 [main] INFO c.zixieqing.command.impl.LiChefImpl - 李師正在準備做:魚香肉絲
11:43:54.320 [main] INFO c.zixieqing.command.impl.LiChefImpl - 李師做好了:魚香肉絲,成品為:Food(name=魚香肉絲, oil=豬油, chili=青椒, excipients=[食鹽少量, 味精少許, 蔥蒜少許], container=盤子)
命令模式的優點:
- 將發出命令的責任和執行命令的責任分隔開(請求者 / 呼叫者 和 執行者 / 接收者沒有必然聯絡),請求的一方不必知道接收請求的一方的介面,更不必知道請求如何被接收、操作是否被執行、何時被執行,以及是怎麼被執行的,所以降低了請求發出者和請求執行者之間的耦合,當然也方便擴充套件(Command命令採用的是介面+實現類 或是 抽象類+實現類)
命令模式的缺點:
- 和簡單工廠模式差不多,要是命令Command很多的話,那麼實現類就會變多,造成類臃腫,比較繁瑣
* 3.2.3、迭代器模式
定義:迭代、迭代,就是遍歷唄,所以指的就是:提供一種順序訪問集合容器 / 複雜物件中各個元素的方法,又無須暴露集合容器的內部表示
本質:就是將一個集合容器的遍歷方法抽取出來,然後單獨弄到一個迭代器物件中,從而:做到讓我們以相同的方式,可以遍歷不同資料結構的集合容器(這也是這個模式的應用場景)
典型例子:List、Set系列的集合,他們都繼承了
Iterable
介面,都有Iterator
型別的iterator(
)方法,而hasNext()
和next()
方法就在Iterator
介面中
迭代器模式的角色
- 抽象迭代器(Iterator):負責定義訪問和遍歷元素的介面,通常包含
hasNext()
、next()
等方法 - 具體迭代器(Concretelterator):實現抽象迭代器介面中所定義的方法,完成對聚合物件的遍歷,記錄遍歷的當前位置
- 抽象容器(Aggregate):負責定義建立具體迭代器的介面、以及所謂的對集合容器 / 複雜物件的增刪改這些方法
- 具體容器(ConcreteAggregate):抽象容器的子類,負責建立具體迭代器 / 返回迭代器例項
迭代器模式的邏輯
- 從上面那個list的構成其實也知道了
3.2.3.1、簡單邏輯
- 注:下面這個邏輯沒多大意思,就意思意思而已,只是為了理解邏輯,因為搞的就是套娃
1、抽象迭代器
package com.zixieqing.o1simple;
/**
* <p>@description : 該類功能 抽象迭代器
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface Iterator<E> {
boolean hasNext();
E next();
}
2、具體迭代器
package com.zixieqing.o1simple.impl;
import com.zixieqing.o1simple.Iterator;
import java.util.List;
/**
* <p>@description : 該類功能 具體迭代器
* </p>
* <p>@package : com.zixieqing.o1simple.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class IteratorImpl<E> implements Iterator<E> {
/**
* 下一個元素的索引
*/
private int cursor;
/**
* 最後一個元素的索引,如果沒有就返回-1
*/
private int lastRet = -1;
private List<E> list;
public IteratorImpl(List<E> list) {
this.list = list;
}
@Override
public boolean hasNext() {
return this.cursor < list.size();
}
@Override
public E next() {
return this.list.get(cursor++);
}
}
3、抽象容器
package com.zixieqing.o1simple;
/**
* <p>@description : 該類功能 抽象容器
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface ICollection<E> {
Iterator<E> iterator();
boolean add(E element);
}
4、具體容器
package com.zixieqing.o1simple;
import com.zixieqing.o1simple.impl.IteratorImpl;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 該類功能 具體容器
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class NewList<E> implements ICollection<E>{
private List<E> list = new ArrayList<>();
@Override
public Iterator<E> iterator() {
return new IteratorImpl<>(list);
}
@Override
public boolean add(E element) {
return list.add(element);
}
}
5、測試
package com.zixieqing;
import com.zixieqing.o1simple.Iterator;
import com.zixieqing.o1simple.NewList;
/**
* <p>@description : 該類功能 測試
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) {
NewList<Integer> newList = new NewList<>();
newList.add(1);
newList.add(2);
newList.add(3);
newList.add(4);
newList.add(5);
Iterator<Integer> iterator = newList.iterator();
while (iterator.hasNext())
System.out.println(iterator.next());
}
}
迭代器模式的優點:
- 在不需要暴露聚合物件的內部結構的情況下,可以讓我們以相同的方式遍歷不同資料結構(連結串列、陣列、樹.......)的聚合物件 / 集合容器的各個元素
迭代器模式的缺點:
- 它的缺點和工廠方法模式的缺點是類似的,因為迭代器模式是將容器儲存資料和遍歷資料進行了分離,所以就會造成:新增聚合物件就可能需要新增迭代器類(主要為具體迭代器)
使用場景:
- 想要遍歷不同的聚合結構,這時提供一個統一的介面(迭代器)
- 想要為聚合物件提供多種遍歷方式
* 3.2.4、中介者模式
定義:我拿你的錢,辦你的事兒;所謂的中介者就是一個和事佬
處理的問題:物件之間存在的依賴關係(多個類相互耦合,形成了網狀結構),即:一個類的方法中用到了另一個的物件(可以是把A類物件當做B類方法的引數;可以是B類方法中new了A類的例項;也可以是B類中的屬性引用了A類物件[但:沒有new,即:聚合],然後在B類方法中new出例項。如果有很多個類之間存在依賴關係,那麼就會造成只要動一個類,則很多類都要進行整改,這就是所謂的牽一髮而動全身,典型例子就是如下的關係:
中介者模式的角色
- 抽象中介者(Mediator):定義統一的介面,用於各同事角色之間的通訊
- 具體中介者(ConcreteMediator):透過協調各同事物件實現協作行為,因此它必須依賴於各個同事角色
- 同事類(Colleague):每一個同事角色都知道中介者角色,而且與其他的同事角色通訊的時候,一定要透過中介者角色協作。每個同事類的行為分為兩種:
- 一種是同事本身的行為,比如改變物件本身的狀態,處理自己的行為等,這種行為叫做自發行為,與其他的同事類或中介者沒有任何的依賴
- 第二種是必須依賴中介者才能完成的行為,叫做依賴方法
- 當然:根據需要也可以把同事類進行抽取,進而變成:抽象同事類+具體同事類
3.2.4.1、簡單邏輯
情景:男女分手+中間調和者,類圖邏輯如下:
1、抽象中介者 / 抽象協調者
package com.zixieqing.o1simple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 該類功能 抽象協調者
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public abstract class Coordinator {
private Logger logger = LoggerFactory.getLogger(Coordinator.class);
protected Man man;
protected Woman woman;
public Coordinator() {
man = new Man(this);
woman = new Woman(this);
}
/**
* 中介者 / 協調者的核心方法:處理同事角色關係
*
* @param type 事件型別 1、傳話;2、退還物品
* @param thing 發生對應型別時要做的事
*/
public abstract void handler(int type, String thing);
/**
* 傳話
*
* @param context 傳話內容
*/
public abstract void message(String context);
/**
* 退還物品
*
* @param thing 要退還的東西
*/
public abstract void handover(String thing);
}
2、具體中介者
package com.zixieqing.o1simple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 該類功能 具體協調者
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ConsreteConnrdinator extends Coordinator {
private Logger logger = LoggerFactory.getLogger(ConsreteConnrdinator.class);
/**
* 代表吵架
*/
private static final int QUARREL = 1;
/**
* 代表退還物品
*/
private static final int HANDOVER = 2;
/**
* MAG_FLAG 訊息標識 1、給女傳話;2、給男傳話
* HANDOVER_FLAG 退還物品標識 1、給女退還物品;2、給男退還物品
*/
public static int MSG_FLAG = 1, HANDOVER_FLAG = 1;
/**
* 中介者 / 協調者的核心方法:處理同事角色關係
* @param type 事件型別 1、傳話;2、退還物品
* @param thing 發生對應型別時要做的事
*/
@Override
public void handler(int type, String thing) {
switch (type) {
case QUARREL:
this.message(thing);
break;
case HANDOVER:
this.handover(thing);
break;
default:
logger.info("貧道愛莫能助,只能擺爛..........");
}
}
/**
* 傳話
* @param context 傳話內容
*/
@Override
public void message(String context) {
if (1==MSG_FLAG)
super.woman.talkToFriend(context);
if (2==MSG_FLAG)
super.man.love(context);
}
/**
* 退還物品
*
* @param thing 要退還的東西
*/
@Override
public void handover(String thing) {
if (1==HANDOVER_FLAG)
super.woman.talkToFriend(thing);
if (2==HANDOVER_FLAG)
super.man.love(thing);
}
}
3、同事類:男人
package com.zixieqing.o1simple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Scanner;
/**
* <p>@description : 該類功能 男人:同事類A
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Man {
private Logger logger = LoggerFactory.getLogger(Man.class);
/**
* 聚合協調者
*/
private Coordinator coordinator;
/**
* 姓名
*/
private String name;
/**
* 年齡
*/
private int age;
/**
* 敏感詞
*/
private String filter = "不聽,不聽,分手";
public Man(Coordinator coordinator) {
this.coordinator = coordinator;
}
/**
* 吃
* @param foodName 食物名
*/
public void eat(String foodName) {
logger.info("這個男人今天吃了:{}", foodName);
}
/**
* 喝
* @param drinkName 飲料名
*/
public void drink(String drinkName) {
logger.info("這個男人剛剛喝了:{}", drinkName);
}
/**
* 做飯
* @param foodName 菜名
*/
public void cook(String foodName) {
logger.info("這個男人正在做:{}", foodName);
}
/**
* 談戀愛
* @param thing 做的是戀愛中的什麼事
*/
public void love(String thing) {
logger.info("女:{}", thing);
if (thing.trim().equals(filter)) {
ConsreteConnrdinator.MSG_FLAG = 1;
coordinator.handler(1,"你這娘們兒簡直不可理喻,分就分");
}
ConsreteConnrdinator.MSG_FLAG = 1;
Scanner input = new Scanner(System.in);
coordinator.handler(1,input.next());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
4、同事類:女人
package com.zixieqing.o1simple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Scanner;
/**
* <p>@description : 該類功能 女人:同事類B
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Woman {
private Logger logger = LoggerFactory.getLogger(Woman.class);
private Coordinator coordinator;
private String name;
private int age;
/**
* 火藥桶
*/
private String gunpowder = "你這娘們兒簡直不可理喻,分就分";
public Woman(Coordinator coordinator) {
this.coordinator = coordinator;
}
/**
* 逛街購物
*/
public void shopping() {
logger.info("這個仙女今天去哪裡逛街,又買了xxxxxxxxxxx");
}
/**
* 耍朋友
*/
public void talkToFriend(String context) {
logger.info("男:{}",context);
// 如果觸碰火藥桶,那就退還物品
if (context.trim().equals(gunpowder)) {
logger.info("分就分,把他的破東西拿回去..........");
ConsreteConnrdinator.HANDOVER_FLAG = 2;
ConsreteConnrdinator.MSG_FLAG = 2;
Scanner input = new Scanner(System.in);
String goods = input.next();
// 退還物品
coordinator.handler(2,goods);
}
// 否則就是繼續交流
ConsreteConnrdinator.MSG_FLAG = 2;
Scanner input = new Scanner(System.in);
coordinator.handler(1,input.next());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
5、測試
package com.zixieqing;
import com.zixieqing.o1simple.ConsreteConnrdinator;
import com.zixieqing.o1simple.Coordinator;
import com.zixieqing.o1simple.Man;
import com.zixieqing.o1simple.Woman;
/**
* <p>@description : 該類功能 測試
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) {
Coordinator connrdinator = new ConsreteConnrdinator();
Woman woman = new Woman(connrdinator);
// 同事類的獨有方法
woman.shopping();
Man man = new Man(connrdinator);
// 同事類獨有方法
man.eat("閉門羹");
man.drink("寂寞");
man.cook("空氣");
// 提供給外部,進行協調的方法
woman.talkToFriend("");
// 提供給外部,進行協調的方法
man.love("");
}
}
自娛自樂的聊天室就來了
當然:中介者被稱為協調者,那也得協調協調,做點事情嘛,不能只當個傳話筒,這個就根據開發場景自由發揮了
同時:上面的同事類是可以抽離成一個介面,變成抽象同事類+實現類
分析一丟丟中介者模式:
- 1、每個同事類物件只與中介者產生依賴關係,同事類彼此之間不產生依賴
- 2、具體協調者中加入了用private修飾的用於處理各同事類關係的方法,從而讓一個物件依賴多個物件的情況移到這個中介者中來進行處理了
- 3、同事類中都加入了中介者,從而讓各個同事類都具有中介者的特性,這樣做之後:各個同事類就可以只負責自己的行為(獨有方法),而不需要自己負責的 / 需要協調的就丟給中介者進行處理(這樣做也取消了各個同事類之間產生依賴或關聯關係)
- 同事類只負責自己的行為,不需要負責的就丟給中介者處理,這樣好處就是:各個同事類之間可以不用知道彼此的存在,它只需要和中介者打交道即可
- 同事類只負責自己的行為,需要協調的就丟給中介者進行處理,這樣可能會造成這個模式濫用的情況(慎用),因為這樣做一不注意就可能導致:同事類之間知道彼此的存在了,即:各同事類之間一不注意就產生關聯或依賴關係,那這模式架構就當白做了(別被我上面弄的男女耍朋友給搞混了,誰跟你說他們知道彼此存在的,我沒說,萬一這裡面有故事呢)
當然上面這些都可以說是中介者模式的優點,相應地就有缺點
- 原本是各個同事類之間產生依賴或關聯關係,現在是變成同事類和中介者之間產生依賴關係,這樣就造成:同事類越多,那麼中介者就越腫大(需要聚合的同事類就變多,核心方法[事件方法]中需要處理的邏輯就越複雜),這個缺點也是決定到底要不要用中介者模式的核心,否則就成為亂整,要用這個模式就一定要考慮是否會讓中介者變得腫大的問題
3.2.5、備忘錄模式
定義:備忘錄模式又稱為快照模式,備忘、備忘嘛,指的就是在不破壞封裝性的前提下,獲取到一個物件的內部狀態,並在物件之外記錄或儲存這個狀態,在有需要的時候可將該物件恢復到原先儲存的狀態。使用備忘錄模式可以對操作物件提供回滾操作,但對資源消耗過大,每操作一次都需要記錄操作前資料
人話:就是為了防丟失、撤銷、恢復等事情的(即:需要記錄一個物件的內部狀態,目的就是為了允許使用者取消不確定或者錯誤的操作,能夠恢復到他原先的狀態,有 "後悔藥" 可吃,這也是這個模式的適用場景)
注意上面的官方話定義:
獲取物件內部狀態(內部還有什麼狀態,就屬性值唄),並在物件之外儲存這個狀態(即:在要儲存的物件中會有一個save方法用來把其當前屬性值儲存到另一個物件[即:備忘錄物件]中
在有需要時可將物件恢復到原先儲存的狀態:也就是說在要儲存物件中還會有一個提供和撤銷 / 回滾類似的方法,從而讓物件回到上一步的狀態
備忘錄模式的角色
-
備忘錄(Memento):負責儲存發起人的內部狀態,在需要的時候提供這些內部狀態給發起人
-
發起人(Originator):需要備忘的物件,記錄當前時刻的內部狀態資訊,提供將當前時刻的內部狀態狀態資訊儲存到備忘錄中,並將備忘錄物件返回 以及 從備忘錄中記錄的狀態資訊恢復此物件狀態的方法
-
負責人(Caretaker):用於管理備忘錄,提供儲存與獲取備忘錄物件的功能(getter、setter),但其不能直接對備忘錄中的內容進行操作(要操作備忘錄中的內容只能找備忘錄本身才可以)
備忘錄模式邏輯關係草圖
3.4.5.1、簡單邏輯
1、備忘錄:儲存發起人的內部狀態資訊,並在需要時能將資料交出去
package com.zixieqing.o1simple;
/**
* <p>@description : 該類功能 備忘錄:儲存發起人的內部狀態資訊,並在需要時能夠將儲存的資料交出去
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Memento {
private Object state;
public Memento(Object state) {
this.state = state;
}
public Object getState() {
return state;
}
}
2、負責人:管理備忘錄,提供獲取、儲存備忘錄物件的方法,但不可對備忘錄中的內容進行操作
package com.zixieqing.o1simple;
/**
* <p>@description : 該類功能 負責人:管理備忘錄,提供獲取、儲存備忘錄物件的方法,但不可以對備忘錄中的內容進行操作
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
3、發起人:要進行備忘的物件,記錄當前時刻的內部屬性資訊到備忘錄中 並返回備忘錄物件,同時在需要時可從備忘錄中恢復當前物件的內部資訊
package com.zixieqing.o1simple;
/**
* <p>@description : 該類功能 發起人:需要進行備忘的物件,記錄當前時刻的內部屬性資訊到備忘錄中,並返回備忘錄物件
* 並在需要時可從備忘錄中記錄的狀態資訊恢復資料
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Orginal {
private Object field;
/**
* 儲存當前時刻的狀態資訊到備忘錄中,並返回備忘錄物件
*/
public Memento saveToMemento() {
return new Memento(this.field);
}
/**
* 從備忘錄中恢復當前物件的內部狀態資訊
* @param memento 備忘錄
*/
public void rollbackFromMemento(Memento memento) {
this.field = memento.getState();
}
@Override
public String toString() {
return "Orginal{" +
"field=" + field +
'}';
}
public Object getField() {
return field;
}
public void setField(Object field) {
this.field = field;
}
}
4、測試
package com.zixieqing;
import com.zixieqing.o1simple.Memento;
import com.zixieqing.o1simple.Orginal;
/**
* <p>@description : 該類功能 測試
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) {
// 要進行儲存的物件
Orginal orginal = new Orginal();
orginal.setField("稀里嘩啦儲存了一堆的東西");
// 將物件的當前時刻的內部資訊儲存到備忘錄中
Memento memento = orginal.saveToMemento();
System.out.println(orginal);
// 模擬
orginal.setField("噼裡啪啦又是一頓操作,儲存了資料");
System.out.println(orginal);
System.out.println("=============蕪湖~斷電咯================");
System.out.println("。。。。。。。。。。。。。。。。。。。。");
System.out.println("=============耶吼~電力恢復啦=============");
// 從記錄的備忘錄中恢復物件內部狀態
orginal.rollbackFromMemento(memento);
System.out.println("=============物件恢復之後的狀態===========");
System.out.println(orginal);
}
}
備忘錄模式的缺點:
- 如果要儲存的物件的內部狀態資訊(屬性)很多的話,那記憶體資源消耗是很大的,所以有時可以使用單例、命令模式這些來進行資料儲存,當然更可以透過第三方中介軟體儲存
* 3.2.6、觀察者模式
定義:指的是多個觀察者同時監聽一個主題物件,每當主題物件發生變化時,都會通知監聽 / 依賴它的所有觀察者,從而讓觀察者自動更新自身相關的資料。因此主題物件和觀察者之間就是一對多的依賴關係(這定義就是它的適用場景)
核心:將觀察者和被觀察者進行了解耦,並透過類似於訊息傳送的機制讓兩者進行聯動,從而做到被觀察者發生變化時,監聽 / 依賴於它的所有觀察者都會得到通知並做出響應(更新自身資料)
觀察者模式的角色(可以當做釋出訂閱模式來理解,但:這二者不能完全等價,釋出訂閱模式在觀察者和被觀察者之間再套了一層[套的這一層就是釋出訂閱中的事件機制 Event Channel],自行面向百度程式設計):
- 抽象主題(Subject):被觀察的物件,是介面或抽象類。也就是釋出者,包含了:增加、刪除、通知觀察者物件的方法
- 具體主題(ConcreteSubject):具體被觀察者,是抽象主題的子類。當其內部狀態發生改變時會通知所有依賴與它的觀察者
- 抽象觀察者(Observer):觀察者模型,也就是訂閱者,定義了響應通知的更新方法
- 具體觀察者(ConcreteObserver):是抽象觀察者的子類,在收到主題物件狀態發生改變的通知後,會立刻對其進行響應,並更新自身的相關資料
觀察者模式邏輯草圖
3.2.6.1、簡單邏輯
場景:博主與粉絲,博主發文章,粉絲收到通知
1、先決條件:文章類
package com.zixieqing.o1simple;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* <p>@description : 該類功能 文章
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
@Data
@ToString
@Accessors(chain = true)
public class Article {
/**
* 文章標題
*/
private String title;
/**
* 文章內容
*/
private String context;
/**
* 釋出人編號
*/
private Long publisherId;
/**
* 釋出時間
*/
private Date createTime;
/**
* 文章所屬標籤
*/
private String articleLabel;
}
2、博主介面:抽象主題 即釋出者,包含增加、刪除、通知觀察者物件等方法
package com.zixieqing.o1simple;
/**
* <p>@description : 該類功能 博主介面:抽象主題 包含增加、刪除、通知觀察者物件的方法
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface IBlogger {
/**
* 將粉絲新增到通知列表中,能夠讓粉絲接受到通知
* @param fan 粉絲
* @return true / false
*/
boolean add(IFan fan);
/**
* 將粉絲拉入黑名單,不讓其接收到通知
* @param fan 粉絲
* @return true / false
*/
boolean remove(IFan fan);
/**
* 通知觀察者
* @param article 訊息體
*/
void notice(Article article);
}
- 具體博主:具體主題
package com.zixieqing.o1simple.impl;
import com.zixieqing.o1simple.Article;
import com.zixieqing.o1simple.IBlogger;
import com.zixieqing.o1simple.IFan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 該類功能 一個叫紫邪情的厚臉皮博主
* </p>
* <p>@package : com.zixieqing.o1simple.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Zixieqing implements IBlogger {
private Logger logger = LoggerFactory.getLogger(Zixieqing.class);
/**
* 粉絲列表:一對多
*/
private List<IFan> fanList = new ArrayList<>();
@Override
public boolean add(IFan fan) {
if (!fanList.contains(fan))
return fanList.add(fan);
return false;
}
@Override
public boolean remove(IFan fan) {
if (!add(fan))
return fanList.remove(fan);
return false;
}
@Override
public void notice(Article article) {
for (IFan fan : fanList) {
fan.response(article);
}
}
}
- 注:還是補上吧,上面通知的方法,若是再加上"滿足什麼條件就觸發通知方法",把這句話再進行抽取一下搞到另外合適的地方去,就變成了觸發機制,即:被觀察者滿足什麼條件觸發通知,從而讓所依賴的所有觀察者都收到通知,並更新自身相關資料。這裡抽取出來的觸發機制就是事件通道Event channel,抽取出來之後,這個觀察者模式就可以變成釋出-訂閱模式了
3、粉絲介面:即訂閱者 得到通知之後進行響應並改變自身和主題相關的資料
package com.zixieqing.o1simple;
/**
* <p>@description : 該類功能 粉絲介面:抽象觀察者 得到通知之後,更新自身相關資料 / 做自己要做的事情
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public abstract class IFan {
public abstract boolean response(Article article);
}
- 具體粉絲Q
package com.zixieqing.o1simple.impl;
import com.zixieqing.o1simple.Article;
import com.zixieqing.o1simple.IFan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 該類功能 一個叫Q的粉絲
* </p>
* <p>@package : com.zixieqing.o1simple.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class QFan extends IFan {
private Logger logger = LoggerFactory.getLogger(QFan.class);
/**
* 粉絲感興趣的博文列表
*/
private static List<String> interestList = new ArrayList<>();
static {
interestList.add("設計模式");
interestList.add("愛碼有道");
interestList.add("Java");
}
@Override
public boolean response(Article article) {
for (String interest : interestList) {
if (article.getTitle().equals(interest)) {
logger.info("粉絲:{},接收了通知:{},並開始去做一系列和自身資料相關的事情",
this.getClass().getSimpleName(),
article);
return true;
}
}
return false;
}
}
- 具體粉絲X
package com.zixieqing.o1simple.impl;
import com.zixieqing.o1simple.Article;
import com.zixieqing.o1simple.IFan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 該類功能 一個叫X的粉絲
* </p>
* <p>@package : com.zixieqing.o1simple.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class XFan extends IFan {
private Logger logger = LoggerFactory.getLogger(XFan.class);
/**
* 粉絲感興趣的博文列表
*/
private static List<String> interestList = new ArrayList<>();
static {
interestList.add("訊息中介軟體");
interestList.add("愛碼有道");
interestList.add("分散式事務");
interestList.add("分散式事務");
}
@Override
public boolean response(Article article) {
for (String interest : interestList) {
if (article.getTitle().equals(interest)) {
logger.info("粉絲:{},接收了通知:{},並開始去做一系列和自身資料相關的事情",
this.getClass().getSimpleName(),
article);
return true;
}
}
return false;
}
}
4、測試
package com.zixieqing;
import com.zixieqing.o1simple.Article;
import com.zixieqing.o1simple.IBlogger;
import com.zixieqing.o1simple.impl.QFan;
import com.zixieqing.o1simple.impl.XFan;
import com.zixieqing.o1simple.impl.Zixieqing;
import java.util.Date;
/**
* <p>@description : 該類功能 測試
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) {
IBlogger blogger = new Zixieqing();
// 讓指定粉絲能收到通知
QFan qFan = new QFan();
XFan xFan = new XFan();
blogger.add(qFan);
blogger.add(xFan);
// 博主開始寫文章
Article article = new Article();
String context = "設計莫斯一共有23種,分為建立型、行為型、結構型,這三類其實就是不同層別的架構," +
"如:建立型就是專門用於new例項的架構;即:物件,而行為型體現在行為上,也就是所謂的方法," +
"變來變去其實本質就是對類中方法的架構" +
"學完全部設計模式之後,感受就會起來了,然後只需要知曉每個設計模式解決的場景是什麼," +
"最後開發中遇到對應的設計模式場景就套用,然後思路就出來,也熟練了";
article.setTitle("設計模式")
.setPublisherId(System.nanoTime())
.setContext(context)
.setArticleLabel("設計")
.setCreateTime(new Date());
// 將文章推給粉絲
blogger.notice(article);
System.out.println("=============華麗的分隔符===============");
// blogger.remove(xFan);
article.setTitle("愛碼有道")
.setPublisherId(System.nanoTime())
.setContext("正式開發時還是遵循開發規範比較好")
.setArticleLabel("程式碼整潔")
.setCreateTime(new Date());
// 將文章推給粉絲
blogger.notice(article);
}
}
3.2.6.2、分析觀察者模式
觀察者模式的優點
- 被觀察者(Subject主題) 和 觀察者(Observer)是松耦合的,包含關係嘛,同時也是面向介面程式設計的(即:符合依賴倒置原則)
- 將表示層(觀察者) 和 資料邏輯層(被觀察者)進行了分離,只要被觀察者的自身狀態發生改變就可以讓觀察者相應的資料也發生改變,即:制定了一套觸發機制,從而做到資料邏輯層的資料變化可以相應到多個表示層上
- 使用了一對多的機制,被觀察者的狀態發生改變,只有滿足同一套觸發機制的觀察者才能接收到通知,這樣的機制可以做很多事,比如:興趣分發、事件註冊等機制
觀察者模式的缺點
- 因為觀察者是被放在被觀察者的一個容器中,那眾多的觀察者在這個容器中就產生了類似於鏈化的情況,而只要是容器那就逃不開遍歷的事(像連結串列一樣,或者說線性關係更恰當),這就有缺點了,若是觀察者很多的話,那通知所有的觀察者就需要耗費時間了
- 基於前面的線性關係的情況,還有造成一個問題:只要一個觀察者沒處理好,導致出現卡死的情況,那麼後續的觀察者也別通知了(換言之:後續觀察者可能出現接收不到通知的情況),相當於後續觀察者死翹翹了
* 3.2.7、狀態模式
定義:一個物件在其內部狀態改變時改變它的行為
解決的問題:解決複雜物件的狀態轉換以及不同狀態下行為的封裝問題,一句話:物件在不同狀態下有不同行為時就可以使用狀態模式
核心:將一個物件的狀態從該物件中分離出來,封裝到專門的狀態類中(抽象狀態+實現類),從而使得物件狀態可以靈活變化
本質:在程式碼中對物件屬性進行的大量
if-else
判斷進行抽離(如程式碼:if(p.getxxxx().equals("yyyy")){}
,狀態模式解決的差不多就是這種程式碼),在物件內部提供外部呼叫的方法,從而讓其自身根據狀態的不同去走對應的邏輯(狀態實現類中的邏輯),如:一個介面登入是一種樣子,未登入又是另一種樣子,狀態不同行為不同。這也是這個模式的適用場景,即:程式碼中包含大量與物件狀態有關的條件語句時(if-else
和switch
)就可以使用狀態模式進行改造
狀態模式的角色
- 上下文(Context):狀態所屬者,維護具體狀態類的例項,這個例項儲存其當前狀態
- 抽象狀態(State):封裝與Context的一個特定狀態相關的行為操作(這裡面可以定義多個方法的,這是和命令模式的一個小區別)
- 具體狀態(ConcreteState):每一個具體的狀態類實現一個與Context的一個狀態相關的行為,在需要時也可以進行狀態切換
狀態模式的邏輯草圖
3.2.7.1、簡單邏輯
場景:去ATM中取款
1、抽象狀態:ATM狀態對應的行為封裝
package com.zixieqing;
/**
* <p>@description : 該類功能 抽象狀態:ATM狀態對應的行為
* 封裝與Context狀態所屬者的狀態 / 屬性相關的行為
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface ATMState {
/**
* 插卡
*/
void insertCard();
/**
* 提交密碼
*/
void submitPwd();
/**
* 取款
*/
void getCash();
/**
* 查詢餘額
*/
void queryBalance();
/**
* 退卡
*/
void checkOut();
}
2、具體狀態
- 準備狀態
package com.zixieqing.impl;
import com.zixieqing.ATMContext;
import com.zixieqing.ATMState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 該類功能 具體狀態:準備狀態
* 具體狀態:1、實現Context的一個狀態的相關行為邏輯
* 2、在需要時進行狀態切換
* 狀態切換可以是本狀態中的不同行為切換,也可以是切換到不同具體狀態者
* </p>
* <p>@package : com.zixieqing.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ReadyState implements ATMState {
private Logger logger = LoggerFactory.getLogger(ReadyState.class);
/**
* 依賴:保留狀態所屬者Context的引用,作用:方便對其進行操作
* 介面ATMState 是對狀態進行了抽象
* 實現類(ReadyState或其他實現類)是具體的狀態
* 這些具體狀態者(實現類)都只是狀態本身而已,而狀態所屬者是Context,最後狀態是要回到所屬者身邊的
* 如果這個具體狀態者中沒有 對 狀態所屬者Context進行相應操作,那:具體狀態者 和 狀態所屬者就是割裂 / 分開的
*/
private ATMContext atmContext;
public ReadyState(ATMContext atmContext) {
this.atmContext = atmContext;
}
@Override
public void insertCard() {
logger.info("完成插卡");
}
@Override
public void submitPwd() {
logger.info("正在進行密碼校驗............");
if ("123456".equals(atmContext.getPwd())) {
logger.info("密碼校驗成功");
}else {
logger.info("密碼不正確");
// 狀態切換:退卡 / 重新進入準備狀態..........
checkOut();
}
}
@Override
public void getCash() {
// 如果ATM中沒錢了,那就不能再提供服務
if (atmContext.getATMBalance() == 0) {
logger.info("無法使用服務,請去另外機子進行業務辦理");
// 彈卡
checkOut();
/*
狀態切換
注意這裡:這裡沒有用new,而是用的atmContext的setter,這也是前面說保留atmContext引用的好處之一
好處:降低耦合(迪米特原則:最少知道原則),獲取彈性
*/
atmContext.setCurrState(atmContext.getNoServiceState());
}else {
if (atmContext.getMoney() <= atmContext.getATMBalance() && atmContext.getMoney() <= atmContext.getBalance()) {
// 出鈔、減少使用者的賬戶餘額
logger.info("出鈔¥:{}",atmContext.getMoney());
atmContext.setBalance(atmContext.getBalance() - atmContext.getMoney());
// 減少ATM中的鈔票金額
atmContext.setATMBalance(atmContext.getATMBalance() - atmContext.getMoney());
// 列印發票、回到準備狀態...........
// 彈卡
checkOut();
}else {
logger.info("餘額不足");
checkOut();
}
}
}
@Override
public void queryBalance() {
logger.info("賬戶餘額為:{}", atmContext.getBalance());
}
@Override
public void checkOut() {
logger.info("退卡成功");
}
}
- 無法提供服務狀態
package com.zixieqing.impl;
import com.zixieqing.ATMContext;
import com.zixieqing.ATMState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 該類功能 具體狀態:停止服務
* </p>
* <p>@package : com.zixieqing.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class NoServiceState implements ATMState {
private ATMContext atmContext;
public NoServiceState(ATMContext atmContext) {
this.atmContext = atmContext;
}
private Logger logger = LoggerFactory.getLogger(NoServiceState.class);
@Override
public void insertCard() {
logger.info("服務停止,請去其他取款機進行業務辦理");
}
@Override
public void submitPwd() {
logger.info("服務停止,請去其他取款機進行業務辦理");
}
@Override
public void getCash() {
logger.info("服務停止,請去其他取款機進行業務辦理");
}
@Override
public void queryBalance() {
logger.info("服務停止,請去其他取款機進行業務辦理");
}
@Override
public void checkOut() {
logger.info("服務停止,請去其他取款機進行業務辦理");
}
}
3、核心:Context上下文物件
package com.zixieqing;
import com.zixieqing.impl.NoServiceState;
import com.zixieqing.impl.ReadyState;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
/**
* <p>@description : 該類功能 Context上下文物件(這個Context是核心)
* 狀態所屬者:1、維護具體狀態者例項;
* 2、也可以有它自己的特有狀態;
* 3、將狀態改變時其對應的行為交給對應的狀態物件,即:狀態改變時讓其走對應的邏輯(供外部呼叫的)
* 上面2、3根據情況決定可有可無
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
@Data
@ToString
@Accessors(chain = true)
public class ATMContext {
// 假設此Context物件有如下的獨有狀態(實質是:老衲待會兒測試需要,一次性寫在這裡了)
/**
* 密碼
*/
private String pwd;
/**
* 使用者取款金額
*/
private int money;
/**
* 使用者的賬戶餘額(假設為int型別)
*/
private int balance;
/**
* ATM機中的鈔票餘額
*/
private int ATMBalance;
// 下面這些就是這個Context所維護的具體狀態者例項
/**
* 當前狀態
*/
private ATMState currState;
/**
* 準備狀態
*/
private ATMState readyState;
/**
* 無服務狀態
*/
private ATMState noServiceState;
/**
* 初始化所有狀態
* @param pwd 密碼
* @param money 使用者取款金額
* @param balance 使用者的賬戶餘額
* @param ATMBalance ATM機中的鈔票餘額
*/
public ATMContext(String pwd, int money, int balance, int ATMBalance) throws Exception {
this.pwd = pwd;
this.money = money;
this.balance = balance;
this.ATMBalance = ATMBalance;
this.readyState = new ReadyState(this);
this.noServiceState = new NoServiceState(this);
if (this.getATMBalance() > 0) {
this.currState = readyState;
} else if (this.getATMBalance() < 0) {
this.currState = noServiceState;
}else {
throw new Exception();
}
}
// 下面這些就是這個Context在其狀態改變時將具體行為委託給具體狀態物件(可以直接供外部呼叫)
/**
* 轉換到插卡行為
*/
public void insertCard() {
this.currState.insertCard();
}
/**
* 提交密碼
*/
public void submitPwd() {
this.currState.submitPwd();
}
/**
* 取款
*/
public void getCash() {
this.currState.getCash();
}
/**
* 查詢餘額
*/
public void queryBalance() {
this.currState.queryBalance();
}
public void checkOut() {
this.currState.checkOut();
}
}
4、測試
package com.zixieqing;
/**
* <p>@description : 該類功能 測試
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) throws Exception {
ATMContext atmContext = new ATMContext("123456", 300, 200000, 100000);
System.out.println("===============初始狀態===================");
System.out.println(atmContext);
atmContext.insertCard();
atmContext.submitPwd();
atmContext.getCash();
atmContext.queryBalance();
atmContext.checkOut();
System.out.println("===============結束狀態===================");
System.out.println(atmContext);
}
}
- 結果
===============初始狀態===================
ATMContext(pwd=123456, money=300, balance=200000, ATMBalance=100000, currState=com.zixieqing.impl.ReadyState@5d6f64b1, readyState=com.zixieqing.impl.ReadyState@5d6f64b1, noServiceState=com.zixieqing.impl.NoServiceState@32a1bec0)
17:50:01.410 [main] INFO com.zixieqing.impl.ReadyState - 完成插卡
17:50:01.413 [main] INFO com.zixieqing.impl.ReadyState - 正在進行密碼校驗............
17:50:01.413 [main] INFO com.zixieqing.impl.ReadyState - 密碼校驗成功
17:50:01.413 [main] INFO com.zixieqing.impl.ReadyState - 出鈔¥:300
17:50:01.413 [main] INFO com.zixieqing.impl.ReadyState - 退卡成功
17:50:01.413 [main] INFO com.zixieqing.impl.ReadyState - 賬戶餘額為:199700
17:50:01.413 [main] INFO com.zixieqing.impl.ReadyState - 退卡成功
===============結束狀態===================
ATMContext(pwd=123456, money=300, balance=199700, ATMBalance=99700, currState=com.zixieqing.impl.ReadyState@5d6f64b1, readyState=com.zixieqing.impl.ReadyState@5d6f64b1, noServiceState=com.zixieqing.impl.NoServiceState@32a1bec0)
3.2.7.2、分析狀態模式
狀態模式的優點
- 消除了使用物件屬性的條件分支語句(
if-else
或switch
),透過Context狀態所屬者中的狀態行為託管,將屬性 / 狀態的邏輯處理遷移到了State子類來進行,降低了相互間的依賴。使程式碼結構清晰的同時也易擴充套件和易維護 - 顯式化進行狀態轉換:為不同的狀態引入獨立的物件,使得狀態的轉換變得更加明確。而且狀態物件可以保證上下文不會發生內部狀態不一致的狀況,因為上下文中只有一個變數來記錄狀態物件,只要為這一個變數賦值就可以了
- 將所有與某個狀態有關的行為放到一個類中,並且可以方便地增加新的狀態,只需要改變物件狀態即可改變物件的行為
狀態模式的缺點
- 因為此模式就是讓物件隨著其狀態改變而改變行為,從而讓其本身看起來像改變了一樣,這其實就違背了“開閉原則”,因為若是新增新的具體狀態類,那就需要在Context中維護此具體狀態例項(修改負責狀態轉換的原始碼),否則就無法切換到新增狀態
- 狀態模式的結構和實現邏輯一般都是很複雜的,如果一不注意搞不好就會導致程式結構和程式碼混亂
使用狀態模式的建議:
1、具體狀態類一般不要超過5個左右,不然維護起來也是很麻煩的,因為每一個具體狀態類中不同行為邏輯本身就很複雜,而且不同行為之間會進行切換,甚至會切換到另外的狀態去,例子如下(這還不是複雜的,一般真要用這個模式時複雜程度不算低):
3.2.7.3、狀態模式 VS 命令模式
這兩個模式的思路,或者說結構吧,是有點像的,但還是有區別的,還有一個策略模式,但這個還沒玩呢,後續比較時再加入
狀態模式和命令模式最大的區別是下面這些
- 首先第一點:狀態模式中在抽象狀態中是可以定義很多方法的,而命令模式的抽象命令中一般都是一個方法
- 其次第二點:要區分開二者的定義
- 狀態模式:Context狀態所屬者自己內部維護著狀態,會隨著公共方法(前面示例中哪些供外部呼叫的,如:下圖的)的呼叫而切換狀態,外界不需要知道狀態的真實變化情況(變化情況實質在具體狀態類中,會根據情況進行狀態切換)
- 命令模式:根據請求封裝相應的命令,請求發起者不需要知道真正的命令是什麼、該怎麼處理(如:電視換臺,只需要按下相應按鈕,這按鈕對應的底層命令是什麼、會怎麼進行處理,按下按鈕的人不需要知道),只需要去呼叫抽象命令中的
execute()
介面就行(當然:也不是一定要叫這個介面名
- 狀態模式:Context狀態所屬者自己內部維護著狀態,會隨著公共方法(前面示例中哪些供外部呼叫的,如:下圖的)的呼叫而切換狀態,外界不需要知道狀態的真實變化情況(變化情況實質在具體狀態類中,會根據情況進行狀態切換)
* 3.2.8、策略模式
定義:又名政策模式(Policy),指的是針對一組演算法,將每一個演算法封裝到具有共同介面的獨立的類中,使得它們可以互換(實現同一個介面:里氏替換原則),可以讓演算法隨著呼叫者的變化而變化
解決的問題 / 適用場景:在有多種演算法相似(同類可替代的行為邏輯演算法)的情況下,去掉使用條件分支語句(
if-else
或switch
)所帶來的複雜性和難以維護場景理解:不同型別的交易方式(微信、支付寶、銀行卡);生成唯一主鍵策略(UUID、DB自增、DB自增+Redis、Leaf演算法),這些就可以使用策略模式對不同的行為進行封裝,供外部呼叫
策略模式的角色
-
上下文 / 環境類(Context):負責查詢要做什麼。這裡面也可以封裝著具體策略物件需要的資料,具體策略物件透過回撥上下文的方法來獲取這些資料,這玩意兒可以在必要時當做引數(即:關聯)傳給具體策略物件
-
抽象策略(Strategy):演算法的抽象 / 演算法的規範。介面或抽象類都可以
-
具體策略(ConcreteStrategy):具體的某一個演算法 / 行為實現。若是有必要可以從上下文中回撥它裡面的方法來獲取所需要的資料
策略模式的邏輯草圖(可以對照命令模式來看,二者結構很像的)
3.2.8.1、簡單邏輯
場景:就用上面說的不同型別交易方式(微信、支付寶)
1、上下文Context:在必要時儲存具體策略需要的資料;負責呼叫者要做什麼
package com.zixieqing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 該類功能 上下文 / 環境類
* 1、可以在必要時儲存具體策略物件需要的資料
* 2、負責呼叫者要做什麼
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class PayContext {
private Logger logger = LoggerFactory.getLogger(PayContext.class);
private PayStrategy payStrategy;
/**
* 讓呼叫者自己決定用哪種支付策略
* @param payStrategy 支付方式
*/
public PayContext(PayStrategy payStrategy) {
this.payStrategy = payStrategy;
}
/**
* 負責要做什麼:支付
*/
public void pay() {
this.payStrategy.pay();
}
}
2、抽象策略:對同組不同演算法進行抽象,制定規範
package com.zixieqing;
/**
* <p>@description : 該類功能 抽象策略:對同組不同演算法進行抽象
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface PayStrategy {
void pay();
}
- 微信支付
package com.zixieqing.impl;
import com.zixieqing.PayStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 該類功能 具體策略:微信支付方式
* </p>
* <p>@package : com.zixieqing.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class WeChatPayImpl implements PayStrategy {
private Logger logger = LoggerFactory.getLogger(WeChatPayImpl.class);
@Override
public void pay() {
logger.info("本微信系統:收到商戶平臺中傳來的 建立訂單請求");
logger.info("本微信系統:正在建立 預付單");
logger.info("本微信系統:預付單生成成功:返回商戶平臺預付單標識");
logger.info("這中間就是商戶平臺生成帶簽名的支付資訊、使用者發起支付請求、商戶平臺找微信客戶端調起微信支付");
logger.info("本微信系統:收到微信客戶端(即:微信APP)發起的支付請求,開始驗證支付授權許可權");
logger.info("本微信系統:給微信客戶端返回支付授權");
logger.info("使用者:確認支付、輸入密碼,即提交授權給本微信支付系統");
logger.info("本微信系統:正在驗證支付授權");
logger.info("本微信系統:非同步通知商戶平臺:支付結果");
logger.info("商戶平臺:儲存支付通知,並返回本微信支付系統成功接收、處理與否");
logger.info("本微信系統:給微信客戶端返回支付結果,併發微信訊息提醒(即:微信中的那個微信支付的訊息)");
logger.info("最後就是另外的一堆邏輯處理情況");
}
}
- 支付寶支付
package com.zixieqing.impl;
import com.zixieqing.PayStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 該類功能 具體策略:支付寶支付
* </p>
* <p>@package : com.zixieqing.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class AliPayImpl implements PayStrategy {
private Logger logger = LoggerFactory.getLogger(AliPayImpl.class);
@Override
public void pay() {
logger.info("支付寶支付和微信支付邏輯差不多的,假設這裡就是一堆的支付寶支付邏輯,意思意思");
}
}
3、測試
package com.zixieqing;
import com.zixieqing.impl.AliPayImpl;
import com.zixieqing.impl.WeChatPayImpl;
/**
* <p>@description : 該類功能 測試
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) {
PayContext payContext = new PayContext(new WeChatPayImpl());
payContext.pay();
System.out.println("==================華麗的分隔符====================");
PayContext payContext1 = new PayContext(new AliPayImpl());
payContext1.pay();
}
}
- 結果
16:56:43.248 [main] INFO com.zixieqing.impl.WeChatPayImpl - 本微信系統:收到商戶平臺中傳來的 建立訂單請求
16:56:43.250 [main] INFO com.zixieqing.impl.WeChatPayImpl - 本微信系統:正在建立 預付單
16:56:43.250 [main] INFO com.zixieqing.impl.WeChatPayImpl - 本微信系統:預付單生成成功:返回商戶平臺預付單標識
16:56:43.250 [main] INFO com.zixieqing.impl.WeChatPayImpl - 這中間就是商戶平臺生成帶簽名的支付資訊、使用者發起支付請求、商戶平臺找微信客戶端調起微信支付
16:56:43.250 [main] INFO com.zixieqing.impl.WeChatPayImpl - 本微信系統:收到微信客戶端(即:微信APP)發起的支付請求,開始驗證支付授權許可權
16:56:43.250 [main] INFO com.zixieqing.impl.WeChatPayImpl - 本微信系統:給微信客戶端返回支付授權
16:56:43.250 [main] INFO com.zixieqing.impl.WeChatPayImpl - 使用者:確認支付、輸入密碼,即提交授權給本微信支付系統
16:56:43.251 [main] INFO com.zixieqing.impl.WeChatPayImpl - 本微信系統:正在驗證支付授權
16:56:43.251 [main] INFO com.zixieqing.impl.WeChatPayImpl - 本微信系統:非同步通知商戶平臺:支付結果
16:56:43.251 [main] INFO com.zixieqing.impl.WeChatPayImpl - 商戶平臺:儲存支付通知,並返回本微信支付系統成功接收、處理與否
16:56:43.251 [main] INFO com.zixieqing.impl.WeChatPayImpl - 本微信系統:給微信客戶端返回支付結果,併發微信訊息提醒(即:微信中的那個微信支付的訊息)
16:56:43.251 [main] INFO com.zixieqing.impl.WeChatPayImpl - 最後就是另外的一堆邏輯處理情況
==================華麗的分隔符====================
16:56:43.251 [main] INFO com.zixieqing.impl.AliPayImpl - 支付寶支付和微信支付邏輯差不多的,假設這裡就是一堆的支付寶支付邏輯,意思意思
3.2.8.2、分析策略模式
策略模式的優點
- 首先就是它的適用場景就是優點:避免程式碼中使用多重條件分支語句來判定採用哪種策略方式,從而變為採用new的方式,即:物件導向
-
符合開閉原則,當要新增策略時,只需要去實現介面,然後編寫相應的策略邏輯即可
-
由於實現同一介面,同時在Context上下文中擁有介面的引用,所以方便客戶端對不同策略進行自由切換
策略模式的缺點
- 可能會造成過多的具體策略類,即:在需要的程式碼中可以需要new好多個具體策略類(可以透過後續玩的亨元模式來減少一定的物件數量)
- 因為具體採用哪種策略時客戶端來決定,所以:就會導致客戶端需要知道所有的具體策略類,並且知道這些具體策略類之間有什麼不同
3.2.8.3、策略模式 VS 狀態模式 VS 命令模式
狀態模式和命令模式已在前面玩狀態模式時進行了對比
3.2.8.3.1、策略模式 VS 狀態模式
這二者可以說是"親兄弟",二者在結構上很像,但是二者有區別
- 最主要的區別在定義上,也就是適用場景
- 1、策略模式講究的是同組不同演算法,從而讓這些相似的演算法之間根據客戶端需要可以自由切換,具體演算法之間沒有互動。即:策略模式講的是客戶端控制物件使用什麼策略。策略可自由切換是因為Context類中有Strategy策略類的引用(即:策略類之間依賴注入到Context類中)
- 2、狀態模式講究的是物件內部狀態時行為也跟著改變,同時這些行為、狀態之間可能有互動(即:狀態切換)。即:狀態模式講的是自動改變狀態(具體狀態類中會根據相關邏輯進行狀態切換)。可進行自動改變狀態是因為具體狀態類中有狀態所屬者Context的引用,從而在狀態子類中已根據情況進行狀態切換
- 1、策略模式講究的是同組不同演算法,從而讓這些相似的演算法之間根據客戶端需要可以自由切換,具體演算法之間沒有互動。即:策略模式講的是客戶端控制物件使用什麼策略。策略可自由切換是因為Context類中有Strategy策略類的引用(即:策略類之間依賴注入到Context類中)
- 其次區別於做的事情上
- 策略模式做的是同一件事情。如:前面的例子微信支付、支付寶支付,這些都是針對於支付方式這一件事
- 狀態模式在不同狀態下會做不同事情。如:前面的例子ATM在準備狀態、無服務狀態對於同一方法(插卡、提交密碼、取款......)做的都是不同的事情,方法之間不具有可替代性
- 然後就是對於Context這個核心的上下文物件
- 策略模式在具體策略類中並不持有Context的引用,只供Context本身使用
- 狀態模式在具體狀態類中會持有Context的引用,目的就是對Context類進行操作,從而讓狀態最後迴歸到Context這個所屬者身上(即:方便進行狀態切換)
3.2.8.3.2、策略模式 VS 命令模式
這二者在結構上"有點像",命令模式可以抽象地認為是一種策略模式(命令模式多了一個"接收者") 且 命令處理的問題也更復雜,二者區別如下:
-
首先從定義上來看:
-
策略模式:是同組不同演算法。它認為"演算法"是一個已經不可拆分的業務(即:一件事,如:支付),只是這個業務有不同方式的實現而已,目的是為了讓每個"具體演算法"獨立,並彼此之間可替代,讓客戶端來抉擇使用哪一種"演算法"。所以:此模式的關注重心在於"演算法"的可替代問題
-
命令模式:它是將動作進行解耦(即:將請求包裹在命令物件中傳給呼叫物件,由呼叫物件來找能處理該命令的物件進行處理),換言之:就是將一個動作分為了執行行為(命令物件)、執行物件(接收者角色),讓這二者彼此獨立而不相互影響。所以:此模式的關注重心在於動作的解耦問題
-
-
負責點不同(策略模式的抽象策略+具體策略、命令的接收者[也可拆為抽象接收者+具體接收者])
- 策略模式:具體策略是一個完整的邏輯,對客戶端負責,所以具體策略要做即做完,同時一旦修改就意味著對"演算法"的整體調整
- 命令模式:只對命令負責,至於請求是什麼跟它關係不大(如:前面示例的廚師,它只需要負責做菜即可,至於使用者請求是什麼關其叼事);接收者可以不是一個完整的邏輯,事情沒做好不直接影響客戶端(影響的是命令物件),所以修改它不需要客戶端操心(如:某個廚師可以不做某個菜、菜怎麼做不需要客戶單教他)
-
適用場景不同
- 策略模式:適用於"演算法"要求變換的場景
- 命令模式:適用於解耦兩個有緊耦合關係的物件場合 或 多命令多撤銷的場景
* 3.2.9、模板模式
定義:模板模式也可以叫模板方法模式,指的是:在一個方法中定義一個演算法的骨架(即:演算法的結構),而一些其他非公共的步驟推遲到子類中去實現
一句話:封裝公有部分,擴充套件非公有
場景理解:建房子。工地上會把地基、走線、水管.....通用的東西搭建好(毛坯房),然後根據情況加壁櫃、加柵欄....這些有差異的就是其他接手了這個毛坯房的裝修者來弄
適用場景:有多個子類共有的方法,並且邏輯相同;當然對於重要的、複雜的方法,也可以使用模板模式
注意點:因為是在父類中定義"演算法"骨架,並實現一些公有操作,而子類是不可以改變這個"演算法"骨架的,因此:模板方法一般加上
final
關鍵字,即:不可更改
模板方法模式的角色:
- 抽象模板(Template):定義"演算法"模板、公有的具體操作、抽象操作
- 具體模板(ConcreteTemplate):抽象模板的子類,實現抽象操作(即:非公有部分的邏輯)
模板方法模式的邏輯草圖:
3.2.9.1、簡單邏輯
場景:搞豆漿,分為選原料、新增原料、浸泡、打碎,新增原料是非公有的(可為紅豆、綠豆、黑豆、赤小豆.........)
1、抽象模板
package com.zixieqing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 該類功能 抽象模板
* 1、定義模板方法 制定要做的這件事的演算法結構
* 2、封裝公有部分 實現其邏輯
* 3、抽象非公有部分 等著子類來實現邏輯
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public abstract class AbstractSoyMilk {
private Logger logger = LoggerFactory.getLogger(AbstractSoyMilk.class);
/**
* 模板方法:制定"演算法"結構
*/
protected final void make() {
// 1、選擇原料
choose();
// 2、新增調料
add();
// 3、浸泡
soak();
// 4、打碎
smashed();
}
/**
* 新增調料:非公有邏輯交由子類來實現
*/
protected abstract void add();
/**
* 選擇原料:封裝公有部分
*/
protected void choose() {
logger.info("{} 正在準備新增原料 ",this.getClass().getSimpleName());
logger.info("正在選擇原料");
}
/**
* 浸泡:封裝公有部分
*/
protected void soak() {
logger.info("{} 正在準備新增原料 ",this.getClass().getSimpleName());
logger.info("正在進行浸泡!");
}
/**
* 打碎:封裝公有部分
*/
protected void smashed() {
logger.info("{} 正在準備新增原料 ",this.getClass().getSimpleName());
logger.info("正在進行打碎");
}
}
2、具體模板
- 紅豆
package com.zixieqing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 該類功能 具體模板:對非公有部分進行實現
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class RedBeans extends AbstractSoyMilk{
private Logger logger = LoggerFactory.getLogger(RedBeans.class);
@Override
protected void add() {
logger.info("{} 正在準備新增原料 ",this.getClass().getSimpleName());
logger.info("價值3個W的紅豆已新增");
}
}
- 綠豆
package com.zixieqing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 該類功能 具體模板:對非公有部分進行實現
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class GreenBeans extends AbstractSoyMilk{
private Logger logger = LoggerFactory.getLogger(GreenBeans.class);
@Override
protected void add() {
logger.info("{} 正在準備新增原料 ",this.getClass().getSimpleName());
logger.info("已經新增成功適合你顏色的綠豆");
}
}
3、測試
package com.zixieqing;
/**
* <p>@description : 該類功能 測試
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) {
// 紅豆型豆漿
AbstractSoyMilk redBeans = new RedBeans();
redBeans.make();
System.out.println("=====================華麗的分隔符==================");
// 綠豆型豆漿
AbstractSoyMilk greenBeans = new GreenBeans();
greenBeans.make();
}
}
- 結果
15:05:06.896 [main] INFO com.zixieqing.AbstractSoyMilk - RedBeans 正在準備新增原料
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - 正在選擇原料
15:05:06.899 [main] INFO com.zixieqing.RedBeans - RedBeans 正在準備新增原料
15:05:06.899 [main] INFO com.zixieqing.RedBeans - 價值3個W的紅豆已新增
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - RedBeans 正在準備新增原料
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - 正在進行浸泡!
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - RedBeans 正在準備新增原料
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - 正在進行打碎
=====================華麗的分隔符==================
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - GreenBeans 正在準備新增原料
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - 正在選擇原料
15:05:06.899 [main] INFO com.zixieqing.GreenBeans - GreenBeans 正在準備新增原料
15:05:06.899 [main] INFO com.zixieqing.GreenBeans - 已經新增成功適合你顏色的綠豆
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - GreenBeans 正在準備新增原料
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - 正在進行浸泡!
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - GreenBeans 正在準備新增原料
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - 正在進行打碎
3.2.9.2、分析模板方法模式
模板方法模式的優點
- 提取公共程式碼,便於維護;封裝了不變部分,擴充套件可變部分
- 演算法的結構由父類控制,子類只實現部分邏輯。 即擴大了程式碼的複用,父類中定義了模板方法、公有部分邏輯,子類在需要時可以直接複用
模板方法模式的缺點
- 對於可變部分,每一個不同的實現方式需要不同的類來實現(如:前面示例的紅豆、綠豆),這樣類的個數變多時,系統就變得複雜
3.2.10、訪問者模式
定義·:使用了一個訪問者類,它改變了元素類的執行演算法。透過這種方式,元素的執行演算法可以隨著訪問者改變而改變。 元素物件已接受訪問者物件,這樣訪問者物件就可以處理元素物件上的操作
解決的問題: 穩定的資料結構和易變的操作耦合問題(方式:在被訪問的類裡面加一個對外提供接待訪問者的介面)
適用場景: 需要對一個物件結構中的物件進行很多不同的並且不相關的操作,而需要避免讓這些操作"玷汙"這些物件的類(即:這些物件所在類的原有的資料結構),使用訪問者模式將這些封裝到類中。通俗易懂的話:就是一個東西不同的人看到的是不一樣的結果也可以用,如:公司查賬和稅務局查賬
訪問者模式的角色
- 抽象元素(Element): 接收訪問者訪問的方法
- 具體元素(ConcreteElement): 抽象元素的子類,實現方法
- 抽象訪問者(Visitor): 定義訪問每個具體元素的方法
- 具體訪問者(ConcreteVisitor): 抽象訪問者的子類,對方法進行實現
- 物件結構(Subject Structure): 定義維護/新增元素、提供訪問者能夠訪問所有元素的方法。注:這個是可以根據情況來變的
- 一是可以採用直接實現抽象介面,和抽象元素併為一個層次,然後裡面做物件結構相應的事情即可
- 二是可以直接起一個物件結構的類,這裡面一樣的做物件結構相應的事情即可
3.2.10.1、訪問者模式是怎麼變出來的
1、首先知道原有資料結構指的是什麼。 肯定用過如下的程式碼
@Data
public class Person {
private String name;
}
class Test{
public static void main(String[] args) {
Person person = new Person();
person.setName("紫邪情");
System.out.println(person.getName());
}
}
- 這整個
Person
就是資料結構,而name
就是所謂的元素,而person.setName("紫邪情")
就是對元素的操作,而訪問者模式就是解決對這種資料結構中的物件(元素)進行很多不同且不相關的操作,從而避免這些操作"玷汙"這些物件(元素,如上面的name·
)的類(如上面的Person
)的原有資料結構(即:Person
原來的結構)
package com.zixieqing.o1derive.impl;
import com.zixieqing.o1derive.People;
import com.zixieqing.o1derive.Visitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 該類功能 具體元素
* 1、讓訪問者能夠對原有類中的屬性進行操作
* </p>
* <p>@package : com.zixieqing.o1derive.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Name implements People {
private Logger logger = LoggerFactory.getLogger(Name.class);
@Override
public void accept(Visitor visitor) {
logger.info("{}這個類就是原來類中的屬性,這個{}類中就可以進行這個{}屬性的不同且不相關的操作",
this.getClass().getSimpleName(),
this.getClass().getSimpleName(),
this.getClass().getSimpleName().toLowerCase());
}
}
3、訪問者:提供訪問所有元素的方法 訪問所有元素,就是訪問原有類(如:Perosn
)的屬性,而現在屬性變成了一個單獨的類(即:具體元素)
import com.zixieqing.o1derive.impl.Name;
/**
* <p>@description : 該類功能 抽象訪問者:提供能夠訪問所有元素(原來類中屬性)的方法
* </p>
* <p>@package : com.zixieqing.o1derive</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface Visitor {
/**
* 訪問原有類的name屬性 / 訪問name元素
* 要是還有其他的就繼續加 如:sex........
* @param name 要訪問的元素
*/
void visit(Name name);
}
- 具體訪問者就是去訪問元素,而元素的引用也有了,那執行
package com.zixieqing.o1derive.impl;
import com.zixieqing.o1derive.Visitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 該類功能 具體訪問者
* </p>
* <p>@package : com.zixieqing.o1derive.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ConcreteVisitor implements Visitor {
private Logger logger = LoggerFactory.getLogger(ConcreteVisitor.class);
@Override
public void visit(Name name) {
logger.info("{}訪問者即將開始元素訪問",this.getClass().getSimpleName());
// 需要訪問者,當前這個就是一個具體的訪問者,當然:也可以根據情況來弄
name.accept(this);
}
}
4、物件結構:維護元素、提供訪問者訪問所有元素的方法
- 原有類Person類中要進行不同且不相關操作的屬性被抽象化為了:抽象元素(接收訪問者訪問)+具體操作(該元素進行的不同且不相關的操作)
- 訪問每個具體元素的也有了:抽象訪問者+具體訪問者
- 那現在還得有一個東西將元素+訪問者進行連線,即:物件結構(新增元素+讓訪問者能訪問所有的元素)
package com.zixieqing.o1derive;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 該類功能 物件結構
* 1、新增元素 / 維護元素
* 2、讓訪問者能訪問所有元素
* </p>
* <p>@package : com.zixieqing.o1derive</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class SubjectStructure {
private List<People> peoples = new ArrayList<>();
/**
* 新增元素
* @param people 要新增的元素
* @return true/false
*/
public boolean addElement(People people) {
return peoples.add(people);
}
/**
* 讓訪問者能夠訪問所有元素
* @param visitor 訪問者
*/
public void getPeoples(Visitor visitor) {
for (People people : peoples) {
people.accept(visitor);
}
}
}
5、現在我們要新增元素、獲取元素就找"物件結構"+訪問者
package com.zixieqing;
import com.zixieqing.o1derive.SubjectStructure;
import com.zixieqing.o1derive.impl.ConcreteVisitor;
import com.zixieqing.o1derive.impl.Name;
/**
* <p>@description : 該類功能 測試
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) {
SubjectStructure subjectStructure = new SubjectStructure();
// 新增要訪問的元素
subjectStructure.addElement(new Name());
// 透過物件結構+具體訪問者進行元素訪問
subjectStructure.getPeoples(new ConcreteVisitor());
}
}
因此:現在這個模式的邏輯草圖就出來了
3.2.10.2、簡單邏輯
場景:同一問題不同角度觀察,校長對於學生、老師的關注點(升學率);家長對學生老師的關注點(成績)
1、抽象元素:接收訪問者訪問
package com.zixieqing.o2simple.user;
import com.zixieqing.o2simple.visitor.Visitor;
/**
* <p>@description : 該類功能 抽象元素:使用者資訊
* 1、接收訪問者訪問
* </p>
* <p>@package : com.zixieqing.o2simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public abstract class User {
/**
* 姓名
*/
public String name;
/**
* 身份;重點班、普通班 | 特級教師、普通教師、實習教師
*/
public String identity;
/**
* 班級
*/
public String clazz;
public User(String name, String identity, String clazz) {
this.name = name;
this.identity = identity;
this.clazz = clazz;
}
/**
* 核心訪問方法
* @param visitor 訪問者
*/
public abstract void accept(Visitor visitor);
}
- 具體元素:學生
package com.zixieqing.o2simple.user.impl;
import com.zixieqing.o2simple.user.User;
import com.zixieqing.o2simple.visitor.Visitor;
/**
* <p>@description : 該類功能 具體元素:學生
* </p>
* <p>@package : com.zixieqing.o2simple.user.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Student extends User {
public Student(String name, String identity, String clazz) {
super(name, identity, clazz);
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
/**
* 對這個元素進行的不同且不相關操作之一:排名
* @return int 排名
*/
public int ranking() {
return (int) (Math.random() * 100);
}
}
- 具體元素:老師
package com.zixieqing.o2simple.user.impl;
import com.zixieqing.o2simple.user.User;
import com.zixieqing.o2simple.visitor.Visitor;
import java.math.BigDecimal;
/**
* <p>@description : 該類功能 具體元素:老師
* </p>
* <p>@package : com.zixieqing.o2simple.user.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Teacher extends User {
public Teacher(String name, String identity, String clazz) {
super(name, identity, clazz);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
/**
* 對這個具體元素進行的不同且不相關的操作之一:升本率
* @return double 升學率
*/
public double entranceRatio() {
return BigDecimal.valueOf(Math.random() * 100).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
}
}
2、訪問者:訪問每個具體元素
package com.zixieqing.o2simple.visitor;
import com.zixieqing.o2simple.user.impl.*;
/**
* <p>@description : 該類功能 訪問者:訪問每個具體元素
* </p>
* <p>@package : com.zixieqing.o2simple.visitor</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface Visitor {
/**
* 訪問學生資訊
* @param student 具體元素:學生
*/
void visit(Student student);
/**
* 訪問老師資訊
* @param teacher 具體元素:老師
*/
void visit(Teacher teacher);
}
- 具體訪問者:校長
package com.zixieqing.o2simple.visitor.impl;
import com.zixieqing.o2simple.visitor.Visitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zixieqing.o2simple.user.impl.*;
/**
* <p>@description : 該類功能 具體訪問者:校長
* </p>
* <p>@package : com.zixieqing.o2simple.visitor.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Principal implements Visitor {
private Logger logger = LoggerFactory.getLogger(Principal.class);
public void visit(Student student) {
logger.info("學生資訊 姓名:{} 班級:{}", student.name, student.clazz);
}
public void visit(Teacher teacher) {
logger.info("學生資訊 姓名:{} 班級:{} 升學率:{}", teacher.name, teacher.clazz, teacher.entranceRatio());
}
}
- 具體訪問者:家長
package com.zixieqing.o2simple.visitor.impl;
import com.zixieqing.o2simple.visitor.Visitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zixieqing.o2simple.user.impl.*;
/**
* <p>@description : 該類功能 具體訪問者:家長
* </p>
* <p>@package : com.zixieqing.o2simple.visitor.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Parent implements Visitor {
private Logger logger = LoggerFactory.getLogger(Parent.class);
public void visit(Student student) {
logger.info("學生資訊 姓名:{} 班級:{} 排名:{}", student.name, student.clazz, student.ranking());
}
public void visit(Teacher teacher) {
logger.info("老師資訊 姓名:{} 班級:{} 級別:{}", teacher.name, teacher.clazz, teacher.identity);
}
}
3、物件結構/資料看板
package com.zixieqing.o2simple;
import com.zixieqing.o2simple.user.User;
import com.zixieqing.o2simple.user.impl.Student;
import com.zixieqing.o2simple.user.impl.Teacher;
import com.zixieqing.o2simple.visitor.Visitor;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 該類功能 物件結構/資料看板
* 1、新增元素
* 2、訪問所有元素
* </p>
* <p>@package : com.zixieqing.o2simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class DataView {
private List<User> userList = new ArrayList<User>();
public DataView() {
userList.add(new Student("謝飛機", "重點班", "一年一班"));
userList.add(new Student("windy", "重點班", "一年一班"));
userList.add(new Student("大毛", "普通班", "二年三班"));
userList.add(new Student("Shing", "普通班", "三年四班"));
userList.add(new Teacher("BK", "特級教師", "一年一班"));
userList.add(new Teacher("娜娜Goddess", "特級教師", "一年一班"));
userList.add(new Teacher("dangdang", "普通教師", "二年三班"));
userList.add(new Teacher("澤東", "實習教師", "三年四班"));
}
/**
* 獲取所有元素
* @param visitor 訪問者
*/
public void show(Visitor visitor) {
for (User user : userList) {
user.accept(visitor);
}
}
}
4、測試
package com.zixieqing;
import com.zixieqing.o2simple.DataView;
import com.zixieqing.o2simple.visitor.impl.Parent;
import com.zixieqing.o2simple.visitor.impl.Principal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 該類功能 測試
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(ApiTest.class);
DataView dataView = new DataView();
logger.info("\r\n家長視角訪問:");
// 家長
dataView.show(new Parent());
logger.info("\r\n校長視角訪問:");
// 校長
dataView.show(new Principal());
}
}
- 結果
10:53:55.565 [main] INFO com.zixieqing.ApiTest -
家長視角訪問:
10:53:55.568 [main] INFO c.z.o2simple.visitor.impl.Parent - 學生資訊 姓名:謝飛機 班級:一年一班 排名:54
10:53:55.568 [main] INFO c.z.o2simple.visitor.impl.Parent - 學生資訊 姓名:windy 班級:一年一班 排名:83
10:53:55.568 [main] INFO c.z.o2simple.visitor.impl.Parent - 學生資訊 姓名:大毛 班級:二年三班 排名:84
10:53:55.568 [main] INFO c.z.o2simple.visitor.impl.Parent - 學生資訊 姓名:Shing 班級:三年四班 排名:91
10:53:55.568 [main] INFO c.z.o2simple.visitor.impl.Parent - 老師資訊 姓名:BK 班級:一年一班 級別:特級教師
10:53:55.568 [main] INFO c.z.o2simple.visitor.impl.Parent - 老師資訊 姓名:娜娜Goddess 班級:一年一班 級別:特級教師
10:53:55.568 [main] INFO c.z.o2simple.visitor.impl.Parent - 老師資訊 姓名:dangdang 班級:二年三班 級別:普通教師
10:53:55.568 [main] INFO c.z.o2simple.visitor.impl.Parent - 老師資訊 姓名:澤東 班級:三年四班 級別:實習教師
10:53:55.568 [main] INFO com.zixieqing.ApiTest -
校長視角訪問:
10:53:55.569 [main] INFO c.z.o2simple.visitor.impl.Principal - 學生資訊 姓名:謝飛機 班級:一年一班
10:53:55.569 [main] INFO c.z.o2simple.visitor.impl.Principal - 學生資訊 姓名:windy 班級:一年一班
10:53:55.569 [main] INFO c.z.o2simple.visitor.impl.Principal - 學生資訊 姓名:大毛 班級:二年三班
10:53:55.569 [main] INFO c.z.o2simple.visitor.impl.Principal - 學生資訊 姓名:Shing 班級:三年四班
10:53:55.571 [main] INFO c.z.o2simple.visitor.impl.Principal - 學生資訊 姓名:BK 班級:一年一班 升學率:44.54
10:53:55.571 [main] INFO c.z.o2simple.visitor.impl.Principal - 學生資訊 姓名:娜娜Goddess 班級:一年一班 升學率:45.49
10:53:55.571 [main] INFO c.z.o2simple.visitor.impl.Principal - 學生資訊 姓名:dangdang 班級:二年三班 升學率:70.92
10:53:55.571 [main] INFO c.z.o2simple.visitor.impl.Principal - 學生資訊 姓名:澤東 班級:三年四班 升學率:78.14
3.2.10.3、分析訪問者模式
訪問者模式的優點:
- 符合單一職責
- 擁有良好的擴充套件性,要擴充套件只需新增相應的元素+訪問者即可
訪問者模式的缺點:
- 因為元素類中有訪問者的引用,即:元素的細節對訪問者是公開的,這違反了迪米特法則(最少知道原則)
- 在訪問者中用的是具體元素的引用,而不是抽象元素的引用,所以:這違反了里氏替換原則(即:多型)
訪問者模式的重點:元素+訪問者