1. 責任鏈
使多個對象都有機會處理請求,從而避免請求的發(fā)送者和接收者之間的耦合關(guān)系。將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有一個對象處理它為止。
責任鏈模式(Chain of Responsibility)是一種處理請求的模式,它讓多個處理器都有機會處理該請求,直到其中某個處理成功為止。責任鏈模式把多個處理器串成鏈,然后讓請求在鏈上傳遞:
┌─────────┐
│ Request │
└─────────┘
│
┌ ─ ─ ─ ─ ┼ ─ ─ ─ ─ ┐
▼
│ ┌─────────────┐ │
│ ProcessorA │
│ └─────────────┘ │
│
│ ▼ │
┌─────────────┐
│ │ ProcessorB │ │
└─────────────┘
│ │ │
▼
│ ┌─────────────┐ │
│ ProcessorC │
│ └─────────────┘ │
│
└ ─ ─ ─ ─ ┼ ─ ─ ─ ─ ┘
│
▼
財務審批
假設某個員工需要報銷一筆費用,審核者可以分為:
Manager:只能審核1000元以下的報銷;Director:只能審核10000元以下的報銷;CEO:可以審核任意額度。首先,要抽象出請求對象,它將在責任鏈上傳遞:
public class Request {
private String name;
private BigDecimal amount;
public Request(String name, BigDecimal amount) {
this.name = name;
this.amount = amount;
}
public String getName() {
return name;
}
public BigDecimal getAmount() {
return amount;
}
}
- 其次,我們要抽象出處理器:
public interface Handler {
// 返回Boolean.TRUE = 成功
// 返回Boolean.FALSE = 拒絕
// 返回null = 交下一個處理
Boolean process(Request request);
}
- 然后,依次編寫
ManagerHandler、DirectorHandler和CEOHandler。以ManagerHandler為例:
public class ManagerHandler implements Handler {
public Boolean process(Request request) {
// 如果超過1000元,處理不了,交下一個處理:
if (request.getAmount().compareTo(BigDecimal.valueOf(1000)) > 0) {
return null;
}
return !request.getName().equalsIgnoreCase("bob");
}
}
- 有了不同的
Handler后,我們還要把這些Handler組合起來,變成一個鏈,并通過一個統(tǒng)一入口處理:
public class HandlerChain {
// 持有所有Handler:
private List<Handler> handlers = new ArrayList<>();
public void addHandler(Handler handler) {
this.handlers.add(handler);
}
public boolean process(Request request) {
// 依次調(diào)用每個Handler:
for (Handler handler : handlers) {
Boolean r = handler.process(request);
if (r != null) {
// 如果返回TRUE或FALSE,處理結(jié)束:
System.out.println(request + " " + (r ? "Approved by " : "Denied by ") + handler.getClass().getSimpleName());
return r;
}
}
throw new RuntimeException("Could not handle request: " + request);
}
}
- 客戶端組裝出責任鏈,然后用責任鏈來處理請求:
// 構(gòu)造責任鏈:
HandlerChain chain = new HandlerChain();
chain.addHandler(new ManagerHandler());
chain.addHandler(new DirectorHandler());
chain.addHandler(new CEOHandler());
// 處理請求:
chain.process(new Request("Bob", new BigDecimal("123.45")));
chain.process(new Request("Alice", new BigDecimal("1234.56")));
chain.process(new Request("Bill", new BigDecimal("12345.67")));
chain.process(new Request("John", new BigDecimal("123456.78")));
責任鏈模式有很多變種。
- 有些責任鏈的實現(xiàn)方式是通過某個
Handler手動調(diào)用下一個Handler來傳遞Request- 還有一些責任鏈模式,每個
Handler都有機會處理Request,通常這種責任鏈被稱為攔截器(Interceptor)或者過濾器(Filter)
2. 命令
將一個請求封裝為一個對象,從而使你可用不同的請求對客戶進行參數(shù)化,對請求排隊或記錄請求日志,以及支持可撤銷的操作。
命令模式(Command)是指,把請求封裝成一個命令,然后執(zhí)行該命令。
public class TextEditor {
private StringBuilder buffer = new StringBuilder();
public void copy() {
...
}
public void paste() {
String text = getFromClipBoard();
add(text);
}
public void add(String s) {
buffer.append(s);
}
public void delete() {
if (buffer.length() > 0) {
buffer.deleteCharAt(buffer.length() - 1);
}
}
public String getState() {
return buffer.toString();
}
}
public interface Command {
void execute();
}
public class CopyCommand implements Command {
// 持有執(zhí)行者對象:
private TextEditor receiver;
public CopyCommand(TextEditor receiver) {
this.receiver = receiver;
}
public void execute() {
receiver.copy();
}
}
public class PasteCommand implements Command {
private TextEditor receiver;
public PasteCommand(TextEditor receiver) {
this.receiver = receiver;
}
public void execute() {
receiver.paste();
}
}
TextEditor editor = new TextEditor();
editor.add("Command pattern in text editor.\n");
// 執(zhí)行一個CopyCommand:
Command copy = new CopyCommand(editor);
copy.execute();
editor.add("----\n");
// 執(zhí)行一個PasteCommand:
Command paste = new PasteCommand(editor);
paste.execute();
System.out.println(editor.getState());
┌──────┐ ┌───────┐
│Client│─ ─ ─>│Command│
└──────┘ └───────┘
│ ┌──────────────┐
├─>│ CopyCommand │
│ ├──────────────┤
│ │editor.copy() │─ ┐
│ └──────────────┘
│ │ ┌────────────┐
│ ┌──────────────┐ ─>│ TextEditor │
└─>│ PasteCommand │ │ └────────────┘
├──────────────┤
│editor.paste()│─ ┘
└──────────────┘
命令模式雖然增加了系統(tǒng)的復雜度,但是當
TextEditor比較復雜時,就需要用到命令模式。
3.解釋器
給定一個語言,定義它的文法的一種表示,并定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。
解釋器模式(Interpreter)是一種針對特定問題設計的一種解決方案。例如,匹配字符串的時候,由于匹配條件非常靈活,使得通過代碼來實現(xiàn)非常不靈活。
- 以
+開頭的數(shù)字表示的區(qū)號和電話號碼,如+861012345678; - 以英文開頭,后接英文和數(shù)字,并以
.分隔的域名,如www.liaoxuefeng.com; - 以
/開頭的文件路徑,如/path/to/file.txt;
正則表達式就是一個字符串,但要把正則表達式解析為語法樹,然后再匹配指定的字符串,就需要一個解釋器。
實現(xiàn)一個完整的正則表達式的解釋器非常復雜,但是使用解釋器模式卻很簡單:
String s = "+861012345678";
System.out.println(s.matches("^\\+\\d+$"));
4. 迭代器
提供一種方法順序訪問一個聚合對象中的各個元素,而又不需要暴露該對象的內(nèi)部表示。
實現(xiàn)Iterator模式的關(guān)鍵是返回一個Iterator對象,該對象知道集合的內(nèi)部結(jié)構(gòu),因為它可以實現(xiàn)倒序遍歷。我們使用Java的內(nèi)部類實現(xiàn)這個Iterator:
public class ReverseArrayCollection<T> implements Iterable<T> {
private T[] array;
public ReverseArrayCollection(T... objs) {
this.array = Arrays.copyOfRange(objs, 0, objs.length);
}
public Iterator<T> iterator() {
return new ReverseIterator();
}
class ReverseIterator implements Iterator<T> {
// 索引位置:
int index;
public ReverseIterator() {
// 創(chuàng)建Iterator時,索引在數(shù)組末尾:
this.index = ReverseArrayCollection.this.array.length;
}
public boolean hasNext() {
// 如果索引大于0,那么可以移動到下一個元素(倒序往前移動):
return index > 0;
}
public T next() {
// 將索引移動到下一個元素并返回(倒序往前移動):
index--;
return array[index];
}
}
}
使用內(nèi)部類的好處是內(nèi)部類隱含地持有一個它所在對象的this引用,可以通過ReverseArrayCollection.this引用到它所在的集合。
迭代器模式(Iterator)實際上在Java的集合類中已經(jīng)廣泛使用了。
我們以List為例,要遍歷ArrayList,即使我們知道它的內(nèi)部存儲了一個Object[]數(shù)組,也不應該直接使用數(shù)組索引去遍歷,因為這樣需要了解集合內(nèi)部的存儲結(jié)構(gòu)。如果使用Iterator遍歷,那么,ArrayList和LinkedList都可以以一種統(tǒng)一的接口來遍歷:
List<String> list = ...
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
}
5. 備忘錄
在不破壞封裝性的前提下,捕獲一個對象的內(nèi)部狀態(tài),并在該對象之外保存這個狀態(tài)。
備忘錄模式(Memento),主要用于捕獲一個對象的內(nèi)部狀態(tài),以便在將來的某個時候恢復此狀態(tài)。
其實我們使用的幾乎所有軟件都用到了備忘錄模式。最簡單的備忘錄模式就是保存到文件,打開文件。對于文本編輯器來說,保存就是把TextEditor類的字符串存儲到文件,打開就是恢復TextEditor類的狀態(tài)。
public class TextEditor {
private StringBuilder buffer = new StringBuilder();
public void add(char ch) {
buffer.append(ch);
}
public void add(String s) {
buffer.append(s);
}
public void delete() {
if (buffer.length() > 0) {
buffer.deleteCharAt(buffer.length() - 1);
}
}
}
public class TextEditor {
...
// 獲取狀態(tài):
public String getState() {
return buffer.toString();
}
// 恢復狀態(tài):
public void setState(String state) {
this.buffer.delete(0, this.buffer.length());
this.buffer.append(state);
}
}
6. 觀察者
定義對象間的一種一對多的依賴關(guān)系,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新。
觀察者模式(Observer)又稱發(fā)布-訂閱模式(Publish-Subscribe:Pub/Sub)。它是一種通知機制,讓發(fā)送通知的一方(被觀察方)和接收通知的一方(觀察者)能彼此分離,互不影響。
public class Store {
private List<ProductObserver> observers = new ArrayList<>();
private Map<String, Product> products = new HashMap<>();
// 注冊觀察者:
public void addObserver(ProductObserver observer) {
this.observers.add(observer);
}
// 取消注冊:
public void removeObserver(ProductObserver observer) {
this.observers.remove(observer);
}
public void addNewProduct(String name, double price) {
Product p = new Product(name, price);
products.put(p.getName(), p);
// 通知觀察者:
observers.forEach(o -> o.onPublished(p));
}
public void setProductPrice(String name, double price) {
Product p = products.get(name);
p.setPrice(price);
// 通知觀察者:
observers.forEach(o -> o.onPriceChanged(p));
}
}
// observer:
Admin a = new Admin();
Customer c = new Customer();
// store:
Store store = new Store();
// 注冊觀察者:
store.addObserver(a);
store.addObserver(c);
7. 策略
定義一系列的算法,把它們一個個封裝起來,并且使它們可相互替換。本模式使得算法可獨立于使用它的客戶而變化。
策略模式:Strategy,是指,定義一組算法,并把其封裝到一個對象中。然后在運行時,可以靈活的使用其中的一個算法。
示例
// 打折策略接口
public interface DiscountStrategy {
// 計算折扣額度:
BigDecimal getDiscount(BigDecimal total);
}
// 普通用戶策略
public class UserDiscountStrategy implements DiscountStrategy {
public BigDecimal getDiscount(BigDecimal total) {
// 普通會員打九折:
return total.multiply(new BigDecimal("0.1")).setScale(2, RoundingMode.DOWN);
}
}
//滿減策略
public class OverDiscountStrategy implements DiscountStrategy {
public BigDecimal getDiscount(BigDecimal total) {
// 滿100減20優(yōu)惠:
return total.compareTo(BigDecimal.valueOf(100)) >= 0 ? BigDecimal.valueOf(20) : BigDecimal.ZERO;
}
}
public class DiscountContext {
// 持有某個策略:
private DiscountStrategy strategy = new UserDiscountStrategy();
// 允許客戶端設置新策略:
public void setStrategy(DiscountStrategy strategy) {
this.strategy = strategy;
}
public BigDecimal calculatePrice(BigDecimal total) {
return total.subtract(this.strategy.getDiscount(total)).setScale(2);
}
}
DiscountContext ctx = new DiscountContext();
// 默認使用普通會員折扣:
BigDecimal pay1 = ctx.calculatePrice(BigDecimal.valueOf(105));
System.out.println(pay1);
// 使用滿減折扣:
ctx.setStrategy(new OverDiscountStrategy());
BigDecimal pay2 = ctx.calculatePrice(BigDecimal.valueOf(105));
System.out.println(pay2);
// 使用Prime會員折扣:
ctx.setStrategy(new PrimeDiscountStrategy());
BigDecimal pay3 = ctx.calculatePrice(BigDecimal.valueOf(105));
System.out.println(pay3);
策略模式在Java標準庫中應用非常廣泛,我們以排序為例,看看如何通過Arrays.sort()實現(xiàn)忽略大小寫排序:
String[] names = {"ABC", "XYZ", "zoo"};
// 忽略大小寫排序
Arrays.sort(names,String::compareToIgnoreCase);
//倒序排序
Arrays.sort(names,(s1, s2) -> -s1.compareTo(s2));
Arrays.sort(T[] a, Comparator<? super T> c)這個排序方法,它在內(nèi)部實現(xiàn)了TimSort排序,但是,排序算法在比較兩個元素大小的時候,需要借助我們傳入的Comparator對象,才能完成比較。
8. 模板方法
定義一個操作中的算法的骨架,而將一些步驟延遲到子類中,使得子類可以不改變一個算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。
模板方法的核心思想是:父類定義骨架,子類實現(xiàn)某些細節(jié)。
模板方法(Template Method)是一個比較簡單的模式。它的主要思想是,定義一個操作的一系列步驟,對于某些暫時確定不下來的步驟,就留給子類去實現(xiàn)好了,這樣不同的子類就可以定義出不同的步驟。
示例:讀取配置
// 抽象類
public abstract class AbstractSetting {
public final String getSetting(String key) {
String value = lookupCache(key);
if (value == null) {
value = readFromDatabase(key);
putIntoCache(key, value);
}
return value;
}
protected abstract String lookupCache(String key);
protected abstract void putIntoCache(String key, String value);
}
// Map做緩存
public class LocalSetting extends AbstractSetting {
private Map<String, String> cache = new HashMap<>();
protected String lookupCache(String key) {
return cache.get(key);
}
protected void putIntoCache(String key, String value) {
cache.put(key, value);
}
}
// Redis做緩存
public class RedisSetting extends AbstractSetting {
private RedisClient client = RedisClient.create("redis://localhost:6379");
protected String lookupCache(String key) {
try (StatefulRedisConnection<String, String> connection = client.connect()) {
RedisCommands<String, String> commands = connection.sync();
return commands.get(key);
}
}
protected void putIntoCache(String key, String value) {
try (StatefulRedisConnection<String, String> connection = client.connect()) {
RedisCommands<String, String> commands = connection.sync();
commands.set(key, value);
}
}
}