面向對象-設計模式-行為型
日暮鄉關何處是?煙波江上使人愁。
簡介:面向對象-設計模式-行為型。
一、概述
何謂設計模式:
設計模式(Design Pattern)是一套被反復使用、多數人知曉的、經過分類的、代碼設計經驗的總結。
設計模式的好處&學習目的:
1、為了代碼可重用行、讓代碼更易被他人理解、保證代碼的可靠性、使代碼編寫真正實現工程化;
2、設計模式便於我們維護項目,增強系統的健壯性和可擴展性;
3、設計模式還可以鍛煉碼農的設計思維、升華代碼質量等。
二、行為型
責任鏈模式、命令模式、解釋器模式、迭代器模式、中介者模式、備忘錄模式、觀察者模式、狀態模式、策略模式、模板方法模式、訪問者模式、空對象模式。
1. 責任鏈(Chain Of Responsibility)
Intent
使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關系。將這些對象連成一條鏈,並沿着這條鏈發送該請求,直到有一個對象處理它為止。
Class Diagram
- Handler:定義處理請求的接口,並且實現后繼鏈(successor)
Implementation

1 public abstract class Handler { 2
3 protected Handler successor; 4
5
6 public Handler(Handler successor) { 7 this.successor = successor; 8 } 9
10
11 protected abstract void handleRequest(Request request); 12 }

1 public class ConcreteHandler1 extends Handler { 2
3 public ConcreteHandler1(Handler successor) { 4 super(successor); 5 } 6
7
8 @Override 9 protected void handleRequest(Request request) { 10 if (request.getType() == RequestType.TYPE1) { 11 System.out.println(request.getName() + " is handle by ConcreteHandler1"); 12 return; 13 } 14 if (successor != null) { 15 successor.handleRequest(request); 16 } 17 } 18 }

1 public class ConcreteHandler2 extends Handler { 2
3 public ConcreteHandler2(Handler successor) { 4 super(successor); 5 } 6
7
8 @Override 9 protected void handleRequest(Request request) { 10 if (request.getType() == RequestType.TYPE2) { 11 System.out.println(request.getName() + " is handle by ConcreteHandler2"); 12 return; 13 } 14 if (successor != null) { 15 successor.handleRequest(request); 16 } 17 } 18 }

1 public class Request { 2
3 private RequestType type; 4 private String name; 5
6
7 public Request(RequestType type, String name) { 8 this.type = type; 9 this.name = name; 10 } 11
12
13 public RequestType getType() { 14 return type; 15 } 16
17
18 public String getName() { 19 return name; 20 } 21 }
1 public enum RequestType { 2 TYPE1, TYPE2 3 }

1 public class Client { 2
3 public static void main(String[] args) { 4
5 Handler handler1 = new ConcreteHandler1(null); 6 Handler handler2 = new ConcreteHandler2(handler1); 7
8 Request request1 = new Request(RequestType.TYPE1, "request1"); 9 handler2.handleRequest(request1); 10
11 Request request2 = new Request(RequestType.TYPE2, "request2"); 12 handler2.handleRequest(request2); 13 } 14 }
1 輸出: 2 request1 is handle by ConcreteHandler1 3 request2 is handle by ConcreteHandler2
2. 命令(Command)
Intent
將命令封裝成對象中,具有以下作用:
- 使用命令來參數化其它對象
- 將命令放入隊列中進行排隊
- 將命令的操作記錄到日志中
- 支持可撤銷的操作
Class Diagram
- Command:命令
- Receiver:命令接收者,也就是命令真正的執行者
- Invoker:通過它來調用命令
- Client:可以設置命令與命令的接收者
Implementation
設計一個遙控器,可以控制電燈開關。
1 public interface Command { 2 void execute(); 3 }

1 public class LightOnCommand implements Command { 2 Light light; 3
4 public LightOnCommand(Light light) { 5 this.light = light; 6 } 7
8 @Override 9 public void execute() { 10 light.on(); 11 } 12 }

1 public class LightOffCommand implements Command { 2 Light light; 3
4 public LightOffCommand(Light light) { 5 this.light = light; 6 } 7
8 @Override 9 public void execute() { 10 light.off(); 11 } 12 }

1 public class Light { 2
3 public void on() { 4 System.out.println("Light is on!"); 5 } 6
7 public void off() { 8 System.out.println("Light is off!"); 9 } 10 }

1 /**
2 * 遙控器 3 */
4 public class Invoker { 5 private Command[] onCommands; 6 private Command[] offCommands; 7 private final int slotNum = 7; 8
9 public Invoker() { 10 this.onCommands = new Command[slotNum]; 11 this.offCommands = new Command[slotNum]; 12 } 13
14 public void setOnCommand(Command command, int slot) { 15 onCommands[slot] = command; 16 } 17
18 public void setOffCommand(Command command, int slot) { 19 offCommands[slot] = command; 20 } 21
22 public void onButtonWasPushed(int slot) { 23 onCommands[slot].execute(); 24 } 25
26 public void offButtonWasPushed(int slot) { 27 offCommands[slot].execute(); 28 } 29 }
1 public class Client { 2 public static void main(String[] args) { 3 Invoker invoker = new Invoker(); 4 Light light = new Light(); 5 Command lightOnCommand = new LightOnCommand(light); 6 Command lightOffCommand = new LightOffCommand(light); 7 invoker.setOnCommand(lightOnCommand, 0); 8 invoker.setOffCommand(lightOffCommand, 0); 9 invoker.onButtonWasPushed(0); 10 invoker.offButtonWasPushed(0); 11 } 12 }
3. 解釋器(Interpreter)
Intent
為語言創建解釋器,通常由語言的語法和語法分析來定義。
Class Diagram
- TerminalExpression:終結符表達式,每個終結符都需要一個 TerminalExpression。
- Context:上下文,包含解釋器之外的一些全局信息。
Implementation
以下是一個規則檢驗器實現,具有 and 和 or 規則,通過規則可以構建一顆解析樹,用來檢驗一個文本是否滿足解析樹定義的規則。
例如一顆解析樹為 D And (A Or (B C)),文本 "D A" 滿足該解析樹定義的規則。
這里的 Context 指的是 String。
1 public abstract class Expression { 2 public abstract boolean interpret(String str); 3 }

1 public class TerminalExpression extends Expression { 2
3 private String literal = null; 4
5 public TerminalExpression(String str) { 6 literal = str; 7 } 8
9 public boolean interpret(String str) { 10 StringTokenizer st = new StringTokenizer(str); 11 while (st.hasMoreTokens()) { 12 String test = st.nextToken(); 13 if (test.equals(literal)) { 14 return true; 15 } 16 } 17 return false; 18 } 19 }

1 public class AndExpression extends Expression { 2
3 private Expression expression1 = null; 4 private Expression expression2 = null; 5
6 public AndExpression(Expression expression1, Expression expression2) { 7 this.expression1 = expression1; 8 this.expression2 = expression2; 9 } 10
11 public boolean interpret(String str) { 12 return expression1.interpret(str) && expression2.interpret(str); 13 } 14 }

1 public class OrExpression extends Expression { 2 private Expression expression1 = null; 3 private Expression expression2 = null; 4
5 public OrExpression(Expression expression1, Expression expression2) { 6 this.expression1 = expression1; 7 this.expression2 = expression2; 8 } 9
10 public boolean interpret(String str) { 11 return expression1.interpret(str) || expression2.interpret(str); 12 } 13 }

1 public class Client { 2
3 /**
4 * 構建解析樹 5 */
6 public static Expression buildInterpreterTree() { 7 // Literal
8 Expression terminal1 = new TerminalExpression("A"); 9 Expression terminal2 = new TerminalExpression("B"); 10 Expression terminal3 = new TerminalExpression("C"); 11 Expression terminal4 = new TerminalExpression("D"); 12 // B C
13 Expression alternation1 = new OrExpression(terminal2, terminal3); 14 // A Or (B C)
15 Expression alternation2 = new OrExpression(terminal1, alternation1); 16 // D And (A Or (B C))
17 return new AndExpression(terminal4, alternation2); 18 } 19
20 public static void main(String[] args) { 21 Expression define = buildInterpreterTree(); 22 String context1 = "D A"; 23 String context2 = "A B"; 24 System.out.println(define.interpret(context1)); // true
25 System.out.println(define.interpret(context2)); // false
26 } 27 }
4. 迭代器(Iterator)
Intent
提供一種順序訪問聚合對象元素的方法,並且不暴露聚合對象的內部表示。
Class Diagram
- Aggregate 是聚合類,其中 createIterator() 方法可以產生一個 Iterator;
- Iterator 主要定義了 hasNext() 和 next() 方法;
- Client 組合了 Aggregate,為了迭代遍歷 Aggregate,也需要組合 Iterator。
Implementation
1 public interface Aggregate { 2 Iterator createIterator(); 3 }

1 public class ConcreteAggregate implements Aggregate { 2
3 private Integer[] items; 4
5 public ConcreteAggregate() { 6 items = new Integer[10]; 7 for (int i = 0; i < items.length; i++) { 8 items[i] = i; 9 } 10 } 11
12 @Override 13 public Iterator createIterator() { 14 return new ConcreteIterator<Integer>(items); 15 } 16 }
1 public interface Iterator<Item> { 2 Item next(); 3 boolean hasNext(); 4 }

1 public class ConcreteIterator<Item> implements Iterator { 2
3 private Item[] items; 4 private int position = 0; 5
6 public ConcreteIterator(Item[] items) { 7 this.items = items; 8 } 9
10 @Override 11 public Object next() { 12 return items[position++]; 13 } 14
15 @Override 16 public boolean hasNext() { 17 return position < items.length; 18 } 19 }
1 public class Client { 2 public static void main(String[] args) { 3 Aggregate aggregate = new ConcreteAggregate(); 4 Iterator<Integer> iterator = aggregate.createIterator(); 5 while (iterator.hasNext()) { 6 System.out.println(iterator.next()); 7 } 8 } 9 }
5. 中介者(Mediator)
Intent
集中相關對象之間復雜的溝通和控制方式。
Class Diagram
- Mediator:中介者,定義一個接口用於與各同事(Colleague)對象通信。
- Colleague:同事,相關對象。
Implementation
Alarm(鬧鍾)、CoffeePot(咖啡壺)、Calendar(日歷)、Sprinkler(噴頭)是一組相關的對象,在某個對象的事件產生時需要去操作其它對象,形成了下面這種依賴結構:
使用中介者模式可以將復雜的依賴結構變成星形結構:
1 public abstract class Colleague { 2 public abstract void onEvent(Mediator mediator); 3 }

1 public class Alarm extends Colleague { 2
3 @Override 4 public void onEvent(Mediator mediator) { 5 mediator.doEvent("alarm"); 6 } 7
8 public void doAlarm() { 9 System.out.println("doAlarm()"); 10 } 11 }

1 public class CoffeePot extends Colleague { 2 @Override 3 public void onEvent(Mediator mediator) { 4 mediator.doEvent("coffeePot"); 5 } 6
7 public void doCoffeePot() { 8 System.out.println("doCoffeePot()"); 9 } 10 }

1 public class Calender extends Colleague { 2 @Override 3 public void onEvent(Mediator mediator) { 4 mediator.doEvent("calender"); 5 } 6
7 public void doCalender() { 8 System.out.println("doCalender()"); 9 } 10 }

1 public class Sprinkler extends Colleague { 2 @Override 3 public void onEvent(Mediator mediator) { 4 mediator.doEvent("sprinkler"); 5 } 6
7 public void doSprinkler() { 8 System.out.println("doSprinkler()"); 9 } 10 }
1 public abstract class Mediator { 2 public abstract void doEvent(String eventType); 3 }

1 public class ConcreteMediator extends Mediator { 2 private Alarm alarm; 3 private CoffeePot coffeePot; 4 private Calender calender; 5 private Sprinkler sprinkler; 6
7 public ConcreteMediator(Alarm alarm, CoffeePot coffeePot, Calender calender, Sprinkler sprinkler) { 8 this.alarm = alarm; 9 this.coffeePot = coffeePot; 10 this.calender = calender; 11 this.sprinkler = sprinkler; 12 } 13
14 @Override 15 public void doEvent(String eventType) { 16 switch (eventType) { 17 case "alarm": 18 doAlarmEvent(); 19 break; 20 case "coffeePot": 21 doCoffeePotEvent(); 22 break; 23 case "calender": 24 doCalenderEvent(); 25 break; 26 default: 27 doSprinklerEvent(); 28 } 29 } 30
31 public void doAlarmEvent() { 32 alarm.doAlarm(); 33 coffeePot.doCoffeePot(); 34 calender.doCalender(); 35 sprinkler.doSprinkler(); 36 } 37
38 public void doCoffeePotEvent() { 39 // ...
40 } 41
42 public void doCalenderEvent() { 43 // ...
44 } 45
46 public void doSprinklerEvent() { 47 // ...
48 } 49 }
1 public class Client { 2 public static void main(String[] args) { 3 Alarm alarm = new Alarm(); 4 CoffeePot coffeePot = new CoffeePot(); 5 Calender calender = new Calender(); 6 Sprinkler sprinkler = new Sprinkler(); 7 Mediator mediator = new ConcreteMediator(alarm, coffeePot, calender, sprinkler); 8 // 鬧鍾事件到達,調用中介者就可以操作相關對象
9 alarm.onEvent(mediator); 10 } 11 }
1 輸出: 2 doAlarm() 3 doCoffeePot() 4 doCalender() 5 doSprinkler()
6. 備忘錄(Memento)
Intent
在不違反封裝的情況下獲得對象的內部狀態,從而在需要時可以將對象恢復到最初狀態。
Class Diagram
- Originator:原始對象
- Caretaker:負責保存好備忘錄
- Memento:備忘錄,存儲原始對象的狀態。備忘錄實際上有兩個接口,一個是提供給 Caretaker 的窄接口:它只能將備忘錄傳遞給其它對象;一個是提供給 Originator 的寬接口,允許它訪問到先前狀態所需的所有數據。理想情況是只允許 Originator 訪問本備忘錄的內部狀態。
Implementation
以下實現了一個簡單計算器程序,可以輸入兩個值,然后計算這兩個值的和。備忘錄模式允許將這兩個值存儲起來,然后在某個時刻用存儲的狀態進行恢復。

1 /**
2 * Originator Interface 3 */
4 public interface Calculator { 5
6 // Create Memento
7 PreviousCalculationToCareTaker backupLastCalculation(); 8
9 // setMemento
10 void restorePreviousCalculation(PreviousCalculationToCareTaker memento); 11
12 int getCalculationResult(); 13
14 void setFirstNumber(int firstNumber); 15
16 void setSecondNumber(int secondNumber); 17 }

1 /**
2 * Originator Implementation 3 */
4 public class CalculatorImp implements Calculator { 5
6 private int firstNumber; 7 private int secondNumber; 8
9 @Override 10 public PreviousCalculationToCareTaker backupLastCalculation() { 11 // create a memento object used for restoring two numbers
12 return new PreviousCalculationImp(firstNumber, secondNumber); 13 } 14
15 @Override 16 public void restorePreviousCalculation(PreviousCalculationToCareTaker memento) { 17 this.firstNumber = ((PreviousCalculationToOriginator) memento).getFirstNumber(); 18 this.secondNumber = ((PreviousCalculationToOriginator) memento).getSecondNumber(); 19 } 20
21 @Override 22 public int getCalculationResult() { 23 // result is adding two numbers
24 return firstNumber + secondNumber; 25 } 26
27 @Override 28 public void setFirstNumber(int firstNumber) { 29 this.firstNumber = firstNumber; 30 } 31
32 @Override 33 public void setSecondNumber(int secondNumber) { 34 this.secondNumber = secondNumber; 35 } 36 }
1 /**
2 * Memento Interface to Originator 3 * 4 * This interface allows the originator to restore its state 5 */
6 public interface PreviousCalculationToOriginator { 7 int getFirstNumber(); 8 int getSecondNumber(); 9 }
1 /**
2 * Memento interface to CalculatorOperator (Caretaker) 3 */
4 public interface PreviousCalculationToCareTaker { 5 // no operations permitted for the caretaker
6 }

1 /**
2 * Memento Object Implementation 3 * <p> 4 * Note that this object implements both interfaces to Originator and CareTaker 5 */
6 public class PreviousCalculationImp implements PreviousCalculationToCareTaker, 7 PreviousCalculationToOriginator { 8
9 private int firstNumber; 10 private int secondNumber; 11
12 public PreviousCalculationImp(int firstNumber, int secondNumber) { 13 this.firstNumber = firstNumber; 14 this.secondNumber = secondNumber; 15 } 16
17 @Override 18 public int getFirstNumber() { 19 return firstNumber; 20 } 21
22 @Override 23 public int getSecondNumber() { 24 return secondNumber; 25 } 26 }

1 /**
2 * CareTaker object 3 */
4 public class Client { 5
6 public static void main(String[] args) { 7 // program starts
8 Calculator calculator = new CalculatorImp(); 9
10 // assume user enters two numbers
11 calculator.setFirstNumber(10); 12 calculator.setSecondNumber(100); 13
14 // find result
15 System.out.println(calculator.getCalculationResult()); 16
17 // Store result of this calculation in case of error
18 PreviousCalculationToCareTaker memento = calculator.backupLastCalculation(); 19
20 // user enters a number
21 calculator.setFirstNumber(17); 22
23 // user enters a wrong second number and calculates result
24 calculator.setSecondNumber(-290); 25
26 // calculate result
27 System.out.println(calculator.getCalculationResult()); 28
29 // user hits CTRL + Z to undo last operation and see last result
30 calculator.restorePreviousCalculation(memento); 31
32 // result restored
33 System.out.println(calculator.getCalculationResult()); 34 } 35 }
1 輸出: 2 110
3 -273
4 110
7. 觀察者(Observer)
Intent
定義對象之間的一對多依賴,當一個對象狀態改變時,它的所有依賴都會收到通知並且自動更新狀態。
主題(Subject)是被觀察的對象,而其所有依賴者(Observer)稱為觀察者。
Class Diagram
主題(Subject)具有注冊和移除觀察者、並通知所有觀察者的功能,主題是通過維護一張觀察者列表來實現這些操作的。
觀察者(Observer)的注冊功能需要調用主題的 registerObserver() 方法。
Implementation
天氣數據布告板會在天氣信息發生改變時更新其內容,布告板有多個,並且在將來會繼續增加。
1 public interface Subject { 2 void registerObserver(Observer o); 3
4 void removeObserver(Observer o); 5
6 void notifyObserver(); 7 }

1 public class WeatherData implements Subject { 2 private List<Observer> observers; 3 private float temperature; 4 private float humidity; 5 private float pressure; 6
7 public WeatherData() { 8 observers = new ArrayList<>(); 9 } 10
11 public void setMeasurements(float temperature, float humidity, float pressure) { 12 this.temperature = temperature; 13 this.humidity = humidity; 14 this.pressure = pressure; 15 notifyObserver(); 16 } 17
18 @Override 19 public void registerObserver(Observer o) { 20 observers.add(o); 21 } 22
23 @Override 24 public void removeObserver(Observer o) { 25 int i = observers.indexOf(o); 26 if (i >= 0) { 27 observers.remove(i); 28 } 29 } 30
31 @Override 32 public void notifyObserver() { 33 for (Observer o : observers) { 34 o.update(temperature, humidity, pressure); 35 } 36 } 37 }
1 public interface Observer { 2 void update(float temp, float humidity, float pressure); 3 }

1 public class StatisticsDisplay implements Observer { 2
3 public StatisticsDisplay(Subject weatherData) { 4 weatherData.registerObserver(this); 5 } 6
7 @Override 8 public void update(float temp, float humidity, float pressure) { 9 System.out.println("StatisticsDisplay.update: " + temp + " " + humidity + " " + pressure); 10 } 11 }

1 public class CurrentConditionsDisplay implements Observer { 2
3 public CurrentConditionsDisplay(Subject weatherData) { 4 weatherData.registerObserver(this); 5 } 6
7 @Override 8 public void update(float temp, float humidity, float pressure) { 9 System.out.println("CurrentConditionsDisplay.update: " + temp + " " + humidity + " " + pressure); 10 } 11 }
1 public class WeatherStation { 2 public static void main(String[] args) { 3 WeatherData weatherData = new WeatherData(); 4 CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData); 5 StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); 6
7 weatherData.setMeasurements(0, 0, 0); 8 weatherData.setMeasurements(1, 1, 1); 9 } 10 }
1 輸出: 2 CurrentConditionsDisplay.update: 0.0 0.0 0.0
3 StatisticsDisplay.update: 0.0 0.0 0.0
4 CurrentConditionsDisplay.update: 1.0 1.0 1.0
5 StatisticsDisplay.update: 1.0 1.0 1.0
8. 狀態(State)
Intent
允許對象在內部狀態改變時改變它的行為,對象看起來好像修改了它所屬的類。
Class Diagram
Implementation
糖果銷售機有多種狀態,每種狀態下銷售機有不同的行為,狀態可以發生轉移,使得銷售機的行為也發生改變。
1 public interface State { 2 /**
3 * 投入 25 分錢 4 */
5 void insertQuarter(); 6
7 /**
8 * 退回 25 分錢 9 */
10 void ejectQuarter(); 11
12 /**
13 * 轉動曲柄 14 */
15 void turnCrank(); 16
17 /**
18 * 發放糖果 19 */
20 void dispense(); 21 }

1 public class HasQuarterState implements State { 2
3 private GumballMachine gumballMachine; 4
5 public HasQuarterState(GumballMachine gumballMachine) { 6 this.gumballMachine = gumballMachine; 7 } 8
9 @Override 10 public void insertQuarter() { 11 System.out.println("You can't insert another quarter"); 12 } 13
14 @Override 15 public void ejectQuarter() { 16 System.out.println("Quarter returned"); 17 gumballMachine.setState(gumballMachine.getNoQuarterState()); 18 } 19
20 @Override 21 public void turnCrank() { 22 System.out.println("You turned..."); 23 gumballMachine.setState(gumballMachine.getSoldState()); 24 } 25
26 @Override 27 public void dispense() { 28 System.out.println("No gumball dispensed"); 29 } 30 }

1 public class NoQuarterState implements State { 2
3 GumballMachine gumballMachine; 4
5 public NoQuarterState(GumballMachine gumballMachine) { 6 this.gumballMachine = gumballMachine; 7 } 8
9 @Override 10 public void insertQuarter() { 11 System.out.println("You insert a quarter"); 12 gumballMachine.setState(gumballMachine.getHasQuarterState()); 13 } 14
15 @Override 16 public void ejectQuarter() { 17 System.out.println("You haven't insert a quarter"); 18 } 19
20 @Override 21 public void turnCrank() { 22 System.out.println("You turned, but there's no quarter"); 23 } 24
25 @Override 26 public void dispense() { 27 System.out.println("You need to pay first"); 28 } 29 }

1 public class SoldOutState implements State { 2
3 GumballMachine gumballMachine; 4
5 public SoldOutState(GumballMachine gumballMachine) { 6 this.gumballMachine = gumballMachine; 7 } 8
9 @Override 10 public void insertQuarter() { 11 System.out.println("You can't insert a quarter, the machine is sold out"); 12 } 13
14 @Override 15 public void ejectQuarter() { 16 System.out.println("You can't eject, you haven't inserted a quarter yet"); 17 } 18
19 @Override 20 public void turnCrank() { 21 System.out.println("You turned, but there are no gumballs"); 22 } 23
24 @Override 25 public void dispense() { 26 System.out.println("No gumball dispensed"); 27 } 28 }

1 public class SoldState implements State { 2
3 GumballMachine gumballMachine; 4
5 public SoldState(GumballMachine gumballMachine) { 6 this.gumballMachine = gumballMachine; 7 } 8
9 @Override 10 public void insertQuarter() { 11 System.out.println("Please wait, we're already giving you a gumball"); 12 } 13
14 @Override 15 public void ejectQuarter() { 16 System.out.println("Sorry, you already turned the crank"); 17 } 18
19 @Override 20 public void turnCrank() { 21 System.out.println("Turning twice doesn't get you another gumball!"); 22 } 23
24 @Override 25 public void dispense() { 26 gumballMachine.releaseBall(); 27 if (gumballMachine.getCount() > 0) { 28 gumballMachine.setState(gumballMachine.getNoQuarterState()); 29 } else { 30 System.out.println("Oops, out of gumballs"); 31 gumballMachine.setState(gumballMachine.getSoldOutState()); 32 } 33 } 34 }

1 public class GumballMachine { 2
3 private State soldOutState; 4 private State noQuarterState; 5 private State hasQuarterState; 6 private State soldState; 7
8 private State state; 9 private int count = 0; 10
11 public GumballMachine(int numberGumballs) { 12 count = numberGumballs; 13 soldOutState = new SoldOutState(this); 14 noQuarterState = new NoQuarterState(this); 15 hasQuarterState = new HasQuarterState(this); 16 soldState = new SoldState(this); 17
18 if (numberGumballs > 0) { 19 state = noQuarterState; 20 } else { 21 state = soldOutState; 22 } 23 } 24
25 public void insertQuarter() { 26 state.insertQuarter(); 27 } 28
29 public void ejectQuarter() { 30 state.ejectQuarter(); 31 } 32
33 public void turnCrank() { 34 state.turnCrank(); 35 state.dispense(); 36 } 37
38 public void setState(State state) { 39 this.state = state; 40 } 41
42 public void releaseBall() { 43 System.out.println("A gumball comes rolling out the slot..."); 44 if (count != 0) { 45 count -= 1; 46 } 47 } 48
49 public State getSoldOutState() { 50 return soldOutState; 51 } 52
53 public State getNoQuarterState() { 54 return noQuarterState; 55 } 56
57 public State getHasQuarterState() { 58 return hasQuarterState; 59 } 60
61 public State getSoldState() { 62 return soldState; 63 } 64
65 public int getCount() { 66 return count; 67 } 68 }

1 public class Client { 2
3 public static void main(String[] args) { 4 GumballMachine gumballMachine = new GumballMachine(5); 5
6 gumballMachine.insertQuarter(); 7 gumballMachine.turnCrank(); 8
9 gumballMachine.insertQuarter(); 10 gumballMachine.ejectQuarter(); 11 gumballMachine.turnCrank(); 12
13 gumballMachine.insertQuarter(); 14 gumballMachine.turnCrank(); 15 gumballMachine.insertQuarter(); 16 gumballMachine.turnCrank(); 17 gumballMachine.ejectQuarter(); 18
19 gumballMachine.insertQuarter(); 20 gumballMachine.insertQuarter(); 21 gumballMachine.turnCrank(); 22 gumballMachine.insertQuarter(); 23 gumballMachine.turnCrank(); 24 gumballMachine.insertQuarter(); 25 gumballMachine.turnCrank(); 26 } 27 }
1 輸出: 2 You insert a quarter 3 You turned... 4 A gumball comes rolling out the slot... 5 You insert a quarter 6 Quarter returned 7 You turned, but there's no quarter
8 You need to pay first 9 You insert a quarter 10 You turned... 11 A gumball comes rolling out the slot... 12 You insert a quarter 13 You turned... 14 A gumball comes rolling out the slot... 15 You haven't insert a quarter
16 You insert a quarter 17 You can't insert another quarter
18 You turned... 19 A gumball comes rolling out the slot... 20 You insert a quarter 21 You turned... 22 A gumball comes rolling out the slot... 23 Oops, out of gumballs 24 You can't insert a quarter, the machine is sold out
25 You turned, but there are no gumballs 26 No gumball dispensed
9. 策略(Strategy)
Intent
定義一系列算法,封裝每個算法,並使它們可以互換。
策略模式可以讓算法獨立於使用它的客戶端。
Class Diagram
- Strategy 接口定義了一個算法族,它們都實現了 behavior() 方法。
- Context 是使用到該算法族的類,其中的 doSomething() 方法會調用 behavior(),setStrategy(Strategy) 方法可以動態地改變 strategy 對象,也就是說能動態地改變 Context 所使用的算法。
與狀態模式的比較
狀態模式的類圖和策略模式類似,並且都是能夠動態改變對象的行為。但是狀態模式是通過狀態轉移來改變 Context 所組合的 State 對象,而策略模式是通過 Context 本身的決策來改變組合的 Strategy 對象。所謂的狀態轉移,是指 Context 在運行過程中由於一些條件發生改變而使得 State 對象發生改變,注意必須要是在運行過程中。
狀態模式主要是用來解決狀態轉移的問題,當狀態發生轉移了,那么 Context 對象就會改變它的行為;而策略模式主要是用來封裝一組可以互相替代的算法族,並且可以根據需要動態地去替換 Context 使用的算法。
Implementation
設計一個鴨子,它可以動態地改變叫聲。這里的算法族是鴨子的叫聲行為。
1 public interface QuackBehavior { 2 void quack(); 3 }
1 public class Quack implements QuackBehavior { 2 @Override 3 public void quack() { 4 System.out.println("quack!"); 5 } 6 }
1 public class Squeak implements QuackBehavior{ 2 @Override 3 public void quack() { 4 System.out.println("squeak!"); 5 } 6 }

1 public class Duck { 2
3 private QuackBehavior quackBehavior; 4
5 public void performQuack() { 6 if (quackBehavior != null) { 7 quackBehavior.quack(); 8 } 9 } 10
11 public void setQuackBehavior(QuackBehavior quackBehavior) { 12 this.quackBehavior = quackBehavior; 13 } 14 }
1 public class Client { 2
3 public static void main(String[] args) { 4 Duck duck = new Duck(); 5 duck.setQuackBehavior(new Squeak()); 6 duck.performQuack(); 7 duck.setQuackBehavior(new Quack()); 8 duck.performQuack(); 9 } 10 }
1 輸出: 2 squeak!
3 quack!
10. 模板方法(Template Method)
Intent
定義算法框架,並將一些步驟的實現延遲到子類。
通過模板方法,子類可以重新定義算法的某些步驟,而不用改變算法的結構。
Class Diagram
Implementation
沖咖啡和沖茶都有類似的流程,但是某些步驟會有點不一樣,要求復用那些相同步驟的代碼。
1 public abstract class CaffeineBeverage { 2
3 final void prepareRecipe() { 4 boilWater(); 5 brew(); 6 pourInCup(); 7 addCondiments(); 8 } 9
10 abstract void brew(); 11
12 abstract void addCondiments(); 13
14 void boilWater() { 15 System.out.println("boilWater"); 16 } 17
18 void pourInCup() { 19 System.out.println("pourInCup"); 20 } 21 }
1 public class Coffee extends CaffeineBeverage { 2 @Override 3 void brew() { 4 System.out.println("Coffee.brew"); 5 } 6
7 @Override 8 void addCondiments() { 9 System.out.println("Coffee.addCondiments"); 10 } 11 }
1 public class Tea extends CaffeineBeverage { 2 @Override 3 void brew() { 4 System.out.println("Tea.brew"); 5 } 6
7 @Override 8 void addCondiments() { 9 System.out.println("Tea.addCondiments"); 10 } 11 }
1 public class Client { 2 public static void main(String[] args) { 3 CaffeineBeverage caffeineBeverage = new Coffee(); 4 caffeineBeverage.prepareRecipe(); 5 System.out.println("-----------"); 6 caffeineBeverage = new Tea(); 7 caffeineBeverage.prepareRecipe(); 8 } 9 }
1 輸出: 2 boilWater 3 Coffee.brew 4 pourInCup 5 Coffee.addCondiments 6 -----------
7 boilWater 8 Tea.brew 9 pourInCup 10 Tea.addCondiments
11. 訪問者(Visitor)
Intent
為一個對象結構(比如組合結構)增加新能力。
Class Diagram
- Visitor:訪問者,為每一個 ConcreteElement 聲明一個 visit 操作
- ConcreteVisitor:具體訪問者,存儲遍歷過程中的累計結果
- ObjectStructure:對象結構,可以是組合結構,或者是一個集合。
Implementation
1 public interface Element { 2 void accept(Visitor visitor); 3 }

1 class CustomerGroup { 2
3 private List<Customer> customers = new ArrayList<>(); 4
5 void accept(Visitor visitor) { 6 for (Customer customer : customers) { 7 customer.accept(visitor); 8 } 9 } 10
11 void addCustomer(Customer customer) { 12 customers.add(customer); 13 } 14 }

1 public class Customer implements Element { 2
3 private String name; 4 private List<Order> orders = new ArrayList<>(); 5
6 Customer(String name) { 7 this.name = name; 8 } 9
10 String getName() { 11 return name; 12 } 13
14 void addOrder(Order order) { 15 orders.add(order); 16 } 17
18 public void accept(Visitor visitor) { 19 visitor.visit(this); 20 for (Order order : orders) { 21 order.accept(visitor); 22 } 23 } 24 }

1 public class Order implements Element { 2
3 private String name; 4 private List<Item> items = new ArrayList(); 5
6 Order(String name) { 7 this.name = name; 8 } 9
10 Order(String name, String itemName) { 11 this.name = name; 12 this.addItem(new Item(itemName)); 13 } 14
15 String getName() { 16 return name; 17 } 18
19 void addItem(Item item) { 20 items.add(item); 21 } 22
23 public void accept(Visitor visitor) { 24 visitor.visit(this); 25
26 for (Item item : items) { 27 item.accept(visitor); 28 } 29 } 30 }

1 public class Item implements Element { 2
3 private String name; 4
5 Item(String name) { 6 this.name = name; 7 } 8
9 String getName() { 10 return name; 11 } 12
13 public void accept(Visitor visitor) { 14 visitor.visit(this); 15 } 16 }
1 public interface Visitor { 2 void visit(Customer customer); 3
4 void visit(Order order); 5
6 void visit(Item item); 7 }

1 public class GeneralReport implements Visitor { 2
3 private int customersNo; 4 private int ordersNo; 5 private int itemsNo; 6
7 public void visit(Customer customer) { 8 System.out.println(customer.getName()); 9 customersNo++; 10 } 11
12 public void visit(Order order) { 13 System.out.println(order.getName()); 14 ordersNo++; 15 } 16
17 public void visit(Item item) { 18 System.out.println(item.getName()); 19 itemsNo++; 20 } 21
22 public void displayResults() { 23 System.out.println("Number of customers: " + customersNo); 24 System.out.println("Number of orders: " + ordersNo); 25 System.out.println("Number of items: " + itemsNo); 26 } 27 }

1 public class Client { 2 public static void main(String[] args) { 3 Customer customer1 = new Customer("customer1"); 4 customer1.addOrder(new Order("order1", "item1")); 5 customer1.addOrder(new Order("order2", "item1")); 6 customer1.addOrder(new Order("order3", "item1")); 7
8 Order order = new Order("order_a"); 9 order.addItem(new Item("item_a1")); 10 order.addItem(new Item("item_a2")); 11 order.addItem(new Item("item_a3")); 12 Customer customer2 = new Customer("customer2"); 13 customer2.addOrder(order); 14
15 CustomerGroup customers = new CustomerGroup(); 16 customers.addCustomer(customer1); 17 customers.addCustomer(customer2); 18
19 GeneralReport visitor = new GeneralReport(); 20 customers.accept(visitor); 21 visitor.displayResults(); 22 } 23 }
1 輸出: 2 customer1 3 order1 4 item1 5 order2 6 item1 7 order3 8 item1 9 customer2 10 order_a 11 item_a1 12 item_a2 13 item_a3 14 Number of customers: 2
15 Number of orders: 4
16 Number of items: 6
12. 空對象(Null)
Intent
使用什么都不做的空對象來代替 NULL。
一個方法返回 NULL,意味着方法的調用端需要去檢查返回值是否是 NULL,這么做會導致非常多的冗余的檢查代碼。並且如果某一個調用端忘記了做這個檢查返回值,而直接使用返回的對象,那么就有可能拋出空指針異常。
Class Diagram
Implementation
1 public abstract class AbstractOperation { 2 abstract void request(); 3 }
1 public class RealOperation extends AbstractOperation { 2 @Override 3 void request() { 4 System.out.println("do something"); 5 } 6 }
1 public class NullOperation extends AbstractOperation{ 2 @Override 3 void request() { 4 // do nothing
5 } 6 }

1 public class Client { 2 public static void main(String[] args) { 3 AbstractOperation abstractOperation = func(-1); 4 abstractOperation.request(); 5 } 6
7 public static AbstractOperation func(int para) { 8 if (para < 0) { 9 return new NullOperation(); 10 } 11 return new RealOperation(); 12 } 13 }
日暮鄉關何處是?
煙波江上使人愁