行爲型主要解決類或對象之間交互的經典結構。行爲型的設計模式有觀察者模式,模板模式,策略模式,職責鏈模式,狀態模式,迭代器模式,訪問者模式,備忘錄模式,命令模式,解釋器模式和中介模式。
接下來以3個W和1個H來學習下這十一種設計模式
十一種模式介紹
觀察者模式-Observer
什麼是觀察者模式
在對象之間定義一個一對多的依賴,當一個對象(被觀察者)狀態改變的時候,所有依賴的對象(觀察者)都會自動收到通知。
爲什麼使用觀察者模式
降低被觀察者和觀察者的耦合性,提高易用性。
如何使用觀察者模式
被觀察者,該被觀察者有一組註冊的觀察者,當被觀察者改變時,通知每一個觀察者
// 觀察者
public interface Observer {
void update(Observable o, Object arg);
}
// 被觀察者的封裝類
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
// something
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
// something
protected synchronized void setChanged() {
changed = true;
}
}
// 被觀察者
public class MyObservable extends Observable {
public synchronized void setChanged() {
super.setChanged();
}
}
// 調用類
public class Main {
public static void main(String[] args) {
// java.util.Observable
MyObservable observable = new MyObservable();
observable.addObserver(new java.util.Observer() {
@Override
public void update(Observable o, Object arg) {
System.out.println("A"+arg.toString());
o.deleteObserver(this);
}
});
observable.addObserver(new java.util.Observer() {
@Override
public void update(Observable o, Object arg) {
System.out.println("B"+arg.toString());
}
});
observable.setChanged();
observable.notifyObservers("message1");
observable.setChanged();
observable.notifyObservers("message2");
}
}
// 打印如下:
Bmessage1
Amessage1
Bmessage2
額外學習
在jdk9,Observable被廢棄,使用java.beans包下的PropertyChangeEvent和PropertyChangeListener來代替Observer和Observable的功能。
使用觀察者模式實現簡單的EventBus,git倉庫:https://github.com/CNXMBuyu/design-pattern-study.git
- 改進方案-消息隊列
設計模式核心是對程序進行解耦,在觀察者模式中被觀察者需要添加觀察者,且當發起通知時要遍歷所有的觀察者。爲了使解耦的更加徹底,就出現了消息隊列。
模板模式-Template
什麼是模板模式
將算法整體架構定義好,讓子類在不改變算法整體結構的情況下,重新定義算法中的某些步驟。
爲什麼使用模板模式
代碼複用
如何使用模板模式
定義算法整體架構(抽象類),將需要子類來實現的方法定義爲抽象方法。
public abstract class Player {
public void initPlayer() {
// 保存玩家信息
savePlayer();
// 初始化玩家數據,因爲每款遊戲都不同,所以需要有子類具體實現
initData();
}
protected void savePlayer() {
System.out.println("已保存玩家信息");
}
/**
* 初始化玩家數據
*/
protected abstract void initData();
}
public class GamePlayer extends Player {
@Override
protected void initData() {
System.out.println("初始化遊戲玩家");
}
}
相似功能:回調
A類事先註冊某個函數F到B類,A類在調用B類的P函數的時候,B類反過來調用A類註冊給它的F函數。
public class Player {
public void initPlayer(PlayerCallback playerCallback){
// 保存玩家信息
savePlayer();
// 初始化玩家數據,因爲每款遊戲都不同,所以需要有子類具體實現
playerCallback.initData();
}
protected void savePlayer(){
System.out.println("已保存玩家信息");
}
}
public interface PlayerCallback {
/**
* 初始化玩家數據
*/
void initData();
}
// 調用類
public class Main {
public static void main(String[] args) {
Player player = new Player();
player.initPlayer(new PlayerCallback() {
@Override
public void initData() {
System.out.println("初始化遊戲玩家數據");
}
});
}
}
回調函數的經典案例:spring的JdbcTemplate
模板模板是使用繼承來實現,而回調函數是使用組合來實現。
策略模式-Strategy
什麼是策略模式
策略模式就是定義一族算法類,將每個算法分別封裝起來,讓它們可以互相替換。
爲什麼使用策略模式
解耦策略的定義、創建、使用這三部分
如何使用策略模式
分爲定義、創建、使用三個步驟。
// 以下爲策略的定義
// 策略接口
public interface Cache {
void addCache(String key, String content);
String getCache(String key);
}
// 策略的實現者
public class MemoryCache implements Cache {
private ConcurrentMap<String, String> cache = new ConcurrentHashMap<>();
@Override
public void addCache(String key, String content) {
cache.put(key, content);
System.out.println("memory add cache; key:" + key + ", content:" + content);
}
@Override
public String getCache(String key) {
System.out.println("memory get key:" + key + ", content:" + cache.get(key));
return cache.get(key);
}
}
// 策略的實現者
public class RedisCache implements Cache {
@Override
public void addCache(String key, String content) {
System.out.println("redis add cache; key:" + key + ", content:" + content);
}
@Override
public String getCache(String key) {
String content = "redis get key:" + key;
System.out.println(content);
return content;
}
}
// 策略的創建
public class CacheFactory {
public Cache getCache(String type){
String redisType = "redis";
if(redisType.equals(type)){
return RedisCacheSingleton.INSTANCE;
} else {
return MemoryCacheSingleton.INSTANCE;
}
}
/**
* 使用內部類的單例
*/
private static class RedisCacheSingleton{
private static RedisCache INSTANCE = new RedisCache();
}
/**
* 使用內部類的單例
*/
private static class MemoryCacheSingleton{
private static MemoryCache INSTANCE = new MemoryCache();
}
}
// 策略的使用
public class Main {
public static void main(String[] args) {
Cache redisCache = new CacheFactory().getCache("redis");
redisCache.addCache("a", "a content");
redisCache.getCache("a");
Cache memoryCache = new CacheFactory().getCache("memory");
memoryCache.addCache("a", "a content");
memoryCache.getCache("a");
}
}
職責鏈模式-Chain
什麼是職責鏈模式
職責鏈模式是將請求的發送和接收解耦,讓多個接收對象都有機會處理這個請求。將這些接收對象串成一條鏈,並沿着這條鏈傳遞這個請求,直到鏈上的某個接收對象能夠處理它爲止。
爲什麼使用職責鏈模式
將請求的發送和接收解耦。
如何使用職責鏈模式
public abstract class AbstractScript{
/**
* 執行初始化
*/
public abstract void doInit();
}
public class ScriptA extends AbstractScript {
@Override
public void doInit() {
System.out.println(this.getClass().getName());
}
}
public class ScriptB extends AbstractScript {
@Override
public void doInit() {
System.out.println(this.getClass().getName());
}
}
// 這裏對職責器模式進行了一個小的修改,職責器模式定義“直到鏈上的某個接收對象能夠處理它爲止”,而我們處理它之後依然沒有停止,知道執行結束
public class ScriptChain {
public void init(List<AbstractScript> initScriptList) {
// 排序
Collections.sort(initScriptList);
// 遍歷執行所有腳本
for (AbstractScript script : initScriptList) {
if (script.filter(predicate -> {
return true;
})) {
script.doInit();
}
}
}
}
狀態模式-State
什麼是狀態模式
狀態機又叫有限狀態機,由3個部分組成:狀態、事件、動作。其中,事件也稱爲轉移條件。事件觸發狀態的轉移及動作的執行。
爲什麼使用狀態模式
當狀態條狀觸發的動作比較複雜時,使用狀態模式,可以提高代碼的可維護性。
如何使用狀態模式
訂單有5個狀態,分別爲待付款,已關閉,已付款,已退款,已完成。事件有同意和拒絕。示例代碼簡單沒有動作。
public class Order {
private OrderStateEnum status = OrderStateEnum.unpaid;
public OrderStateEnum getStatus() {
return status;
}
public void setStatus(OrderStateEnum status) {
this.status = status;
}
}
public enum OrderStateEnum {
unpaid, closed, paid, refund, finish
}
// 狀態接口
public interface OrderState {
default void agree(OrderStateMachine orderStateMachine){
// nothing
}
default void reject(OrderStateMachine orderStateMachine){
// nothing
}
}
// 待付款的狀態改變
public class UnpaidOrderState implements OrderState {
@Override
public void agree(OrderStateMachine orderStateMachine) {
// 處理已付款的相關事項
// .....
// 狀態調整
orderStateMachine.setState(new PaidOrderState());
}
@Override
public void reject(OrderStateMachine orderStateMachine) {
orderStateMachine.setState(new ClosedOrderState());
}
}
// 已付款的狀態改變
public class PaidOrderState implements OrderState {
@Override
public void agree(OrderStateMachine orderStateMachine) {
orderStateMachine.setState(new FinishOrderState());
}
@Override
public void reject(OrderStateMachine orderStateMachine) {
orderStateMachine.setState(new RefundedOrderState());
}
}
// 已退款-最終狀態,沒有任何操作
public class RefundedOrderState implements OrderState {}
// 已關閉-最終狀態,沒有任何操作
public class ClosedOrderState implements OrderState {}
// 已完成-最終狀態,沒有任何操作
public class FinishOrderState implements OrderState {}
// 狀態機
public class OrderStateMachine {
private OrderState state;
private static Map<OrderStateEnum, OrderState> orderStateMap = new HashMap<>(10);
static{
orderStateMap.put(OrderStateEnum.unpaid, new UnpaidOrderState());
orderStateMap.put(OrderStateEnum.closed, new ClosedOrderState());
orderStateMap.put(OrderStateEnum.paid, new UnpaidOrderState());
orderStateMap.put(OrderStateEnum.refund, new RefundedOrderState());
orderStateMap.put(OrderStateEnum.finish, new FinishOrderState());
}
public OrderStateMachine(Order order){
state = orderStateMap.get(order.getStatus());
}
public void agree(){
state.agree(this);
}
public void reject(){
state.reject(this);
}
public OrderState getState() {
return state;
}
public void setState(OrderState state) {
this.state = state;
}
}
// 調用代碼
public class Main {
public static void main(String[] args) {
Order order = new Order();
OrderStateMachine orderStateMachine = new OrderStateMachine(order);
orderStateMachine.agree();
System.out.println(orderStateMachine.getState().toString());
orderStateMachine.agree();
System.out.println(orderStateMachine.getState().toString());
}
}
// 打印日誌如下:
cn.hgy.state.PaidOrderState@136432db
cn.hgy.state.FinishOrderState@7382f612
迭代器模式-Iterator
什麼是迭代器模式
迭代器模式將集合對象的遍歷操作從集合類中拆分出來,放到迭代器類中,讓兩者的職責更加單一。
爲什麼使用迭代器模式
- 迭代器模式封裝集合內部的複雜數據結構,開發者不需要了解如何遍歷,直接使用容器提供的迭代器即可;
- 迭代器模式將集合對象的遍歷操作從集合類中拆分出來,放到迭代器類中,讓兩者的職責更加單一;
- 迭代器模式讓添加新的遍歷算法更加容易,更符合開閉原則。除此之外,因爲迭代器都實現自相同的接口,在開發中,基於接口而非實現編程,替換迭代器也變得更加容易。
如何使用迭代器模式
定義迭代器接口和具體的實現。
public interface Iterator<E> {
boolean hasNext();
E next();
}
訪問者模式-Visitor
什麼是訪問者模式
訪問者模式允許一個或者多個操作應用到一組對象上。
爲什麼使用訪問者模式
解耦操作和對象本身,保持類職責單一、滿足開閉原則以及應對代碼的複雜性。
如何使用訪問者模式
public abstract class AbstractFile {
public abstract void read(IRead read);
}
public class Doc extends AbstractFile {
@Override
public void read(IRead read) {
read.read(this);
}
}
public class Pdf extends AbstractFile {
@Override
public void read(IRead read) {
read.read(this);
}
}
public interface IRead {
void read(Doc doc);
void read(Pdf pdf);
}
public class ReadImpl implements IRead {
@Override
public void read(Doc doc) {
System.out.println("doc");
}
@Override
public void read(Pdf pdf) {
System.out.println("pdf");
}
}
public class Main {
public static void main(String[] args) {
List<AbstractFile> fileList = new ArrayList<>();
fileList.add(new Doc());
fileList.add(new Pdf());
IRead read = new ReadImpl();
fileList.forEach(abstractFile -> {
// 編譯會失敗,所以改用abstractFile.read(read);
// read.read(abstractFile);
abstractFile.read(read);
});
}
}
備忘錄模式-Memento
什麼是備忘錄模式
備忘錄模式也叫快照模式,具體來說,就是在不違背封裝原則的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態,以便之後恢復對象爲先前的狀態。
命令模式-Command
什麼是命令模式
命令模式將請求封裝爲一個對象,這樣可以使用不同的請求參數化其他對象,並且能夠支持請求的排隊執行、記錄日誌、撤銷等附加功能。
爲什麼使用命令模式
解耦行爲請求者與行爲實現者。
在大部分編程語言中,函數是沒法作爲參數傳遞給其他函數的,也沒法賦值給變量。藉助命令模式,我們將函數封裝成對象,這樣就可以實現把函數像對象一樣使用。
如何使用命令模式
public interface Command {
void execute();
}
// 燈
public class Light {
public void on() {
System.out.println("燈亮了");
}
public void off() {
System.out.println("燈暗了");
}
}
// 開燈的命令
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
}
// 關燈的命令
public class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
}
// 使用類
public class Main {
public static void main(String[] args) {
Queue<Command> request = new LinkedList<>();
Light light = new Light();
request.add(new LightOnCommand(light));
request.add(new LightOffCommand(light));
request.forEach(command -> {
command.execute();
});
}
}
解釋器模式-Interpreter
什麼是解釋器模式
解釋器模式爲某個語言定義它的語法(或者叫文法)表示,並定義一個解釋器用來處理這個語法。
中介模式-Mediator
什麼是中介模式
中介模式定義了一個單獨的對象,來封裝一組對象之間的交互。將這組對象之間的交互委派給與中介對象交互,來避免對象之間的直接交互。
總結
觀察者模式
觀察者將自己註冊到被觀察者中,當被觀察者發生改變時,將改變推送給每一個被觀察中。
可以通過框架或者反射解決註冊到被觀察者中,或者使用消息隊列解耦觀察者和被觀察者;可以通過異步執行觀察者函數。
模板模式
定義一套流程,將其中部分功能放在子類實現。
模板模式需要繼承實現,可以通過回調來實現類似功能,解決繼承問題。
策略模式
定義一組功能相似的算法實現,可以互相替換。
職責鏈模式
職責鏈模式是將請求的發送和接收解耦,讓多個接收對象都有機會處理這個請求。
狀態模式
適用於狀態並不多、狀態轉移也比較簡單,但事件觸發執行的動作包含的業務邏輯可能比較複雜的狀態機。
迭代器模式
迭代器模式用來遍歷集合對象。解耦容器代碼和遍歷代碼。
訪問者模式
訪問者模式允許一個或者多個操作應用到一組對象上。
備忘錄模式
備忘錄模式也叫快照模式,具體來說,就是在不違背封裝原則的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態,以便之後恢復對象爲先前的狀態。
命令模式
命令模式將請求封裝爲一個對象,這樣可以使用不同的請求參數化其他對象,並且能夠支持請求的排隊執行、記錄日誌、撤銷等附加功能。
解釋器模式
解釋器模式爲某個語言定義它的語法(或者叫文法)表示,並定義一個解釋器用來處理這個語法
中介模式
中介模式定義了一個單獨的對象,來封裝一組對象之間的交互。將這組對象之間的交互委派給與中介對象交互,來避免對象之間的直接交互。