物件導向-設計模式-行為型
日暮鄉關何處是?煙波江上使人愁。
簡介:物件導向-設計模式-行為型。
一、概述
何謂設計模式:
設計模式(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 }
日暮鄉關何處是?
煙波江上使人愁