1. 單例(Singleton)
Intent
確保一個類只有一個實例,並提供該實例的全局訪問點。
Class Diagram
使用一個私有構造函數、一個私有靜態變量以及一個公有靜態函數來實現。
私有構造函數保證了不能通過構造函數來創建對象實例,只能通過公有靜態函數返回唯一的私有靜態變量。
Implementation
Ⅰ 懶漢式-線程不安全
以下實現中,私有靜態變量 uniqueInstance 被延遲實例化,這樣做的好處是,如果沒有用到該類,那麼就不會實例化 uniqueInstance,從而節約資源。
這個實現在多線程環境下是不安全的,如果多個線程能夠同時進入 if (uniqueInstance == null)
,並且此時 uniqueInstance 爲 null,那麼會有多個線程執行 uniqueInstance = new Singleton();
語句,這將導致實例化多次 uniqueInstance。
public class Singleton { private static Singleton uniqueInstance; private Singleton() { } public static Singleton getUniqueInstance() { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; } }
Ⅱ 餓漢式-線程安全
線程不安全問題主要是由於 uniqueInstance 被實例化多次,採取直接實例化 uniqueInstance 的方式就不會產生線程不安全問題。
但是直接實例化的方式也丟失了延遲實例化帶來的節約資源的好處。
private static Singleton uniqueInstance = new Singleton();
Ⅲ 懶漢式-線程安全
只需要對 getUniqueInstance() 方法加鎖,那麼在一個時間點只能有一個線程能夠進入該方法,從而避免了實例化多次 uniqueInstance。
但是當一個線程進入該方法之後,其它試圖進入該方法的線程都必須等待,即使 uniqueInstance 已經被實例化了。這會讓線程阻塞時間過程,因此該方法有性能問題,不推薦使用。
public static synchronized Singleton getUniqueInstance() { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; }
Ⅳ 雙重校驗鎖-線程安全
uniqueInstance 只需要被實例化一次,之後就可以直接使用了。加鎖操作只需要對實例化那部分的代碼進行,只有當 uniqueInstance 沒有被實例化時,才需要進行加鎖。
雙重校驗鎖先判斷 uniqueInstance 是否已經被實例化,如果沒有被實例化,那麼纔對實例化語句進行加鎖。
public class Singleton { private volatile static Singleton uniqueInstance; private Singleton() { } public static Singleton getUniqueInstance() { if (uniqueInstance == null) { synchronized (Singleton.class) { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; } }
考慮下面的實現,也就是隻使用了一個 if 語句。在 uniqueInstance == null 的情況下,如果兩個線程都執行了 if 語句,那麼兩個線程都會進入 if 語句塊內。雖然在 if 語句塊內有加鎖操作,但是兩個線程都會執行 uniqueInstance = new Singleton();
這條語句,只是先後的問題,那麼就會進行兩次實例化。因此必須使用雙重校驗鎖,也就是需要使用兩個 if 語句。
if (uniqueInstance == null) { synchronized (Singleton.class) { uniqueInstance = new Singleton(); } }
uniqueInstance 採用 volatile 關鍵字修飾也是很有必要的。uniqueInstance = new Singleton();
這段代碼其實是分爲三步執行。
- 爲 uniqueInstance 分配內存空間
- 初始化 uniqueInstance
- 將 uniqueInstance 指向分配的內存地址
但是由於 JVM 具有指令重排的特性,執行順序有可能變成 1>3>2。指令重排在單線程環境下不會出先問題,但是在多線程環境下會導致一個線程獲得還沒有初始化的實例。例如,線程 T1 執行了 1 和 3,此時 T2 調用 getUniqueInstance() 後發現 uniqueInstance 不爲空,因此返回 uniqueInstance,但此時 uniqueInstance 還未被初始化。
使用 volatile 可以禁止 JVM 的指令重排,保證在多線程環境下也能正常運行。
Ⅴ 靜態內部類實現
當 Singleton 類加載時,靜態內部類 SingletonHolder 沒有被加載進內存。只有當調用 getUniqueInstance()
方法從而觸發 SingletonHolder.INSTANCE
時 SingletonHolder 纔會被加載,此時初始化 INSTANCE 實例,並且 JVM 能確保 INSTANCE 只被實例化一次。
這種方式不僅具有延遲初始化的好處,而且由 JVM 提供了對線程安全的支持。
public class Singleton { private Singleton() { } private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getUniqueInstance() { return SingletonHolder.INSTANCE; } }
Ⅵ 枚舉實現
public enum Singleton { INSTANCE; private String objName; public String getObjName() { return objName; } public void setObjName(String objName) { this.objName = objName; } public static void main(String[] args) { // 單例測試 Singleton firstSingleton = Singleton.INSTANCE; firstSingleton.setObjName("firstName"); System.out.println(firstSingleton.getObjName()); Singleton secondSingleton = Singleton.INSTANCE; secondSingleton.setObjName("secondName"); System.out.println(firstSingleton.getObjName()); System.out.println(secondSingleton.getObjName()); // 反射獲取實例測試 try { Singleton[] enumConstants = Singleton.class.getEnumConstants(); for (Singleton enumConstant : enumConstants) { System.out.println(enumConstant.getObjName()); } } catch (Exception e) { e.printStackTrace(); } } }
該實現在多次序列化再進行反序列化之後,不會得到多個實例。而其它實現,爲了保證不會出現反序列化之後出現多個實例,需要使用 transient 修飾所有字段,並且實現序列化和反序列化的方法。
該實現可以防止反射攻擊。在其它實現中,通過 setAccessible() 方法可以將私有構造函數的訪問級別設置爲 public,然後調用構造函數從而實例化對象,如果要防止這種攻擊,需要在構造函數中添加防止實例化第二個對象的代碼。但是該實現是由 JVM 保證只會實例化一次,因此不會出現上述的反射攻擊。
Examples
- Logger Classes
- Configuration Classes
- Accesing resources in shared mode
- Factories implemented as Singletons
JDK
2. 簡單工廠(Simple Factory)
Intent
在創建一個對象時不向客戶暴露內部細節,並提供一個創建對象的通用接口。
Class Diagram
簡單工廠不是設計模式,更像是一種編程習慣。它把實例化的操作單獨放到一個類中,這個類就成爲簡單工廠類,讓簡單工廠類來決定應該用哪個具體子類來實例化。
這樣做能把客戶類和具體子類的實現解耦,客戶類不再需要知道有哪些子類以及應當實例化哪個子類。客戶類往往有多個,如果不使用簡單工廠,那麼所有的客戶類都要知道所有子類的細節。而且一旦子類發生改變,例如增加子類,那麼所有的客戶類都要進行修改。
Implementation
public interface Product { }
public class ConcreteProduct implements Product { }
public class ConcreteProduct1 implements Product { }
public class ConcreteProduct2 implements Product { }
以下的 Client 類包含了實例化的代碼,這是一種錯誤的實現。如果在客戶類中存在這種實例化代碼,就需要考慮將代碼放到簡單工廠中。
public class Client { public static void main(String[] args) { int type = 1; Product product; if (type == 1) { product = new ConcreteProduct1(); } else if (type == 2) { product = new ConcreteProduct2(); } else { product = new ConcreteProduct(); } // do something with the product } }
以下的 SimpleFactory 是簡單工廠實現,它被所有需要進行實例化的客戶類調用。
public class SimpleFactory { public Product createProduct(int type) { if (type == 1) { return new ConcreteProduct1(); } else if (type == 2) { return new ConcreteProduct2(); } return new ConcreteProduct(); } }
public class Client { public static void main(String[] args) { SimpleFactory simpleFactory = new SimpleFactory(); Product product = simpleFactory.createProduct(1); // do something with the product } }
3. 工廠方法(Factory Method)
Intent
定義了一個創建對象的接口,但由子類決定要實例化哪個類。工廠方法把實例化操作推遲到子類。
Class Diagram
在簡單工廠中,創建對象的是另一個類,而在工廠方法中,是由子類來創建對象。
下圖中,Factory 有一個 doSomething() 方法,這個方法需要用到一個產品對象,這個產品對象由 factoryMethod() 方法創建。該方法是抽象的,需要由子類去實現。
Implementation
public abstract class Factory { abstract public Product factoryMethod(); public void doSomething() { Product product = factoryMethod(); // do something with the product } }
public class ConcreteFactory extends Factory { public Product factoryMethod() { return new ConcreteProduct(); } }
public class ConcreteFactory1 extends Factory { public Product factoryMethod() { return new ConcreteProduct1(); } }
public class ConcreteFactory2 extends Factory { public Product factoryMethod() { return new ConcreteProduct2(); } }
JDK
- java.util.Calendar
- java.util.ResourceBundle
- java.text.NumberFormat
- java.nio.charset.Charset
- java.net.URLStreamHandlerFactory
- java.util.EnumSet
- javax.xml.bind.JAXBContext
4. 抽象工廠(Abstract Factory)
Intent
提供一個接口,用於創建 相關的對象家族 。
Class Diagram
抽象工廠模式創建的是對象家族,也就是很多對象而不是一個對象,並且這些對象是相關的,也就是說必須一起創建出來。而工廠方法模式只是用於創建一個對象,這和抽象工廠模式有很大不同。
抽象工廠模式用到了工廠方法模式來創建單一對象,AbstractFactory 中的 createProductA() 和 createProductB() 方法都是讓子類來實現,這兩個方法單獨來看就是在創建一個對象,這符合工廠方法模式的定義。
至於創建對象的家族這一概念是在 Client 體現,Client 要通過 AbstractFactory 同時調用兩個方法來創建出兩個對象,在這裏這兩個對象就有很大的相關性,Client 需要同時創建出這兩個對象。
從高層次來看,抽象工廠使用了組合,即 Cilent 組合了 AbstractFactory,而工廠方法模式使用了繼承。
Implementation
public class AbstractProductA { }
public class AbstractProductB { }
public class ProductA1 extends AbstractProductA { }
public class ProductA2 extends AbstractProductA { }
public class ProductB1 extends AbstractProductB { }
public class ProductB2 extends AbstractProductB { }
public abstract class AbstractFactory { abstract AbstractProductA createProductA(); abstract AbstractProductB createProductB(); }
public class ConcreteFactory1 extends AbstractFactory { AbstractProductA createProductA() { return new ProductA1(); } AbstractProductB createProductB() { return new ProductB1(); } }
public class ConcreteFactory2 extends AbstractFactory { AbstractProductA createProductA() { return new ProductA2(); } AbstractProductB createProductB() { return new ProductB2(); } }
public class Client { public static void main(String[] args) { AbstractFactory abstractFactory = new ConcreteFactory1(); AbstractProductA productA = abstractFactory.createProductA(); AbstractProductB productB = abstractFactory.createProductB(); // do something with productA and productB } }
JDK
- javax.xml.parsers.DocumentBuilderFactory
- javax.xml.transform.TransformerFactory
- javax.xml.xpath.XPathFactory
5. 生成器(Builder)
Intent
封裝一個對象的構造過程,並允許按步驟構造。
Class Diagram
Implementation
以下是一個簡易的 StringBuilder 實現,參考了 JDK 1.8 源碼。
public class AbstractStringBuilder { protected char[] value; protected int count; public AbstractStringBuilder(int capacity) { count = 0; value = new char[capacity]; } public AbstractStringBuilder append(char c) { ensureCapacityInternal(count + 1); value[count++] = c; return this; } private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) expandCapacity(minimumCapacity); } void expandCapacity(int minimumCapacity) { int newCapacity = value.length * 2 + 2; if (newCapacity - minimumCapacity < 0) newCapacity = minimumCapacity; if (newCapacity < 0) { if (minimumCapacity < 0) // overflow throw new OutOfMemoryError(); newCapacity = Integer.MAX_VALUE; } value = Arrays.copyOf(value, newCapacity); } }
public class StringBuilder extends AbstractStringBuilder { public StringBuilder() { super(16); } @Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count); } }
public class Client { public static void main(String[] args) { StringBuilder sb = new StringBuilder(); final int count = 26; for (int i = 0; i < count; i++) { sb.append((char) ('a' + i)); } System.out.println(sb.toString()); } }
abcdefghijklmnopqrstuvwxyz
JDK
- java.lang.StringBuilder
- java.nio.ByteBuffer
- java.lang.StringBuffer
- java.lang.Appendable
- Apache Camel builders
6. 原型模式(Prototype)
Intent
使用原型實例指定要創建對象的類型,通過複製這個原型來創建新對象。
Class Diagram
Implementation
public abstract class Prototype { abstract Prototype myClone(); }
public class ConcretePrototype extends Prototype { private String filed; public ConcretePrototype(String filed) { this.filed = filed; } @Override Prototype myClone() { return new ConcretePrototype(filed); } @Override public String toString() { return filed; } }
public class Client { public static void main(String[] args) { Prototype prototype = new ConcretePrototype("abc"); Prototype clone = prototype.myClone(); System.out.println(clone.toString()); } }
abc
JDK
三、行爲型
1. 責任鏈(Chain Of Responsibility)
Intent
使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係。將這些對象連成一條鏈,並沿着這條鏈發送該請求,直到有一個對象處理它爲止。
Class Diagram
- Handler:定義處理請求的接口,並且實現後繼鏈(successor)
Implementation
public abstract class Handler { protected Handler successor; public Handler(Handler successor) { this.successor = successor; } protected abstract void handleRequest(Request request); }
public class ConcreteHandler1 extends Handler { public ConcreteHandler1(Handler successor) { super(successor); } @Override protected void handleRequest(Request request) { if (request.getType() == RequestType.TYPE1) { System.out.println(request.getName() + " is handle by ConcreteHandler1"); return; } if (successor != null) { successor.handleRequest(request); } } }
public class ConcreteHandler2 extends Handler { public ConcreteHandler2(Handler successor) { super(successor); } @Override protected void handleRequest(Request request) { if (request.getType() == RequestType.TYPE2) { System.out.println(request.getName() + " is handle by ConcreteHandler2"); return; } if (successor != null) { successor.handleRequest(request); } } }
public class Request { private RequestType type; private String name; public Request(RequestType type, String name) { this.type = type; this.name = name; } public RequestType getType() { return type; } public String getName() { return name; } }
public enum RequestType { TYPE1, TYPE2 }
public class Client { public static void main(String[] args) { Handler handler1 = new ConcreteHandler1(null); Handler handler2 = new ConcreteHandler2(handler1); Request request1 = new Request(RequestType.TYPE1, "request1"); handler2.handleRequest(request1); Request request2 = new Request(RequestType.TYPE2, "request2"); handler2.handleRequest(request2); } }
request1 is handle by ConcreteHandler1 request2 is handle by ConcreteHandler2
JDK
2. 命令(Command)
Intent
將命令封裝成對象中,具有以下作用:
- 使用命令來參數化其它對象
- 將命令放入隊列中進行排隊
- 將命令的操作記錄到日誌中
- 支持可撤銷的操作
Class Diagram
- Command:命令
- Receiver:命令接收者,也就是命令真正的執行者
- Invoker:通過它來調用命令
- Client:可以設置命令與命令的接收者
Implementation
設計一個遙控器,可以控制電燈開關。
public interface Command { void execute(); }
public class LightOnCommand implements Command { Light light; public LightOnCommand(Light light) { this.light = light; } @Override public void execute() { light.on(); } }
public class LightOffCommand implements Command { Light light; public LightOffCommand(Light light) { this.light = light; } @Override public void execute() { light.off(); } }
public class Light { public void on() { System.out.println("Light is on!"); } public void off() { System.out.println("Light is off!"); } }
/** * 遙控器 */ public class Invoker { private Command[] onCommands; private Command[] offCommands; private final int slotNum = 7; public Invoker() { this.onCommands = new Command[slotNum]; this.offCommands = new Command[slotNum]; } public void setOnCommand(Command command, int slot) { onCommands[slot] = command; } public void setOffCommand(Command command, int slot) { offCommands[slot] = command; } public void onButtonWasPushed(int slot) { onCommands[slot].execute(); } public void offButtonWasPushed(int slot) { offCommands[slot].execute(); } }
public class Client { public static void main(String[] args) { Invoker invoker = new Invoker(); Light light = new Light(); Command lightOnCommand = new LightOnCommand(light); Command lightOffCommand = new LightOffCommand(light); invoker.setOnCommand(lightOnCommand, 0); invoker.setOffCommand(lightOffCommand, 0); invoker.onButtonWasPushed(0); invoker.offButtonWasPushed(0); } }
JDK
3. 解釋器(Interpreter)
Intent
爲語言創建解釋器,通常由語言的語法和語法分析來定義。
Class Diagram
- TerminalExpression:終結符表達式,每個終結符都需要一個 TerminalExpression。
- Context:上下文,包含解釋器之外的一些全局信息。
Implementation
以下是一個規則檢驗器實現,具有 and 和 or 規則,通過規則可以構建一顆解析樹,用來檢驗一個文本是否滿足解析樹定義的規則。
例如一顆解析樹爲 D And (A Or (B C)),文本 "D A" 滿足該解析樹定義的規則。
這裏的 Context 指的是 String。
public abstract class Expression { public abstract boolean interpret(String str); }
public class TerminalExpression extends Expression { private String literal = null; public TerminalExpression(String str) { literal = str; } public boolean interpret(String str) { StringTokenizer st = new StringTokenizer(str); while (st.hasMoreTokens()) { String test = st.nextToken(); if (test.equals(literal)) { return true; } } return false; } }
public class AndExpression extends Expression { private Expression expression1 = null; private Expression expression2 = null; public AndExpression(Expression expression1, Expression expression2) { this.expression1 = expression1; this.expression2 = expression2; } public boolean interpret(String str) { return expression1.interpret(str) && expression2.interpret(str); } }
public class OrExpression extends Expression { private Expression expression1 = null; private Expression expression2 = null; public OrExpression(Expression expression1, Expression expression2) { this.expression1 = expression1; this.expression2 = expression2; } public boolean interpret(String str) { return expression1.interpret(str) || expression2.interpret(str); } }
public class Client { /** * 構建解析樹 */ public static Expression buildInterpreterTree() { // Literal Expression terminal1 = new TerminalExpression("A"); Expression terminal2 = new TerminalExpression("B"); Expression terminal3 = new TerminalExpression("C"); Expression terminal4 = new TerminalExpression("D"); // B C Expression alternation1 = new OrExpression(terminal2, terminal3); // A Or (B C) Expression alternation2 = new OrExpression(terminal1, alternation1); // D And (A Or (B C)) return new AndExpression(terminal4, alternation2); } public static void main(String[] args) { Expression define = buildInterpreterTree(); String context1 = "D A"; String context2 = "A B"; System.out.println(define.interpret(context1)); System.out.println(define.interpret(context2)); } }
true false
JDK
- java.util.Pattern
- java.text.Normalizer
- All subclasses of java.text.Format
- javax.el.ELResolver
4. 迭代器(Iterator)
Intent
提供一種順序訪問聚合對象元素的方法,並且不暴露聚合對象的內部表示。
Class Diagram
- Aggregate 是聚合類,其中 createIterator() 方法可以產生一個 Iterator;
- Iterator 主要定義了 hasNext() 和 next() 方法。
- Client 組合了 Aggregate,爲了迭代遍歷 Aggregate,也需要組合 Iterator。
Implementation
public interface Aggregate { Iterator createIterator(); }
public class ConcreteAggregate implements Aggregate { private Integer[] items; public ConcreteAggregate() { items = new Integer[10]; for (int i = 0; i < items.length; i++) { items[i] = i; } } @Override public Iterator createIterator() { return new ConcreteIterator<Integer>(items); } }
public interface Iterator<Item> { Item next(); boolean hasNext(); }
public class ConcreteIterator<Item> implements Iterator { private Item[] items; private int position = 0; public ConcreteIterator(Item[] items) { this.items = items; } @Override public Object next() { return items[position++]; } @Override public boolean hasNext() { return position < items.length; } }
public class Client { public static void main(String[] args) { Aggregate aggregate = new ConcreteAggregate(); Iterator<Integer> iterator = aggregate.createIterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } }
JDK
5. 中介者(Mediator)
Intent
集中相關對象之間複雜的溝通和控制方式。
Class Diagram
- Mediator:中介者,定義一個接口用於與各同事(Colleague)對象通信。
- Colleague:同事,相關對象
Implementation
Alarm(鬧鐘)、CoffeePot(咖啡壺)、Calendar(日曆)、Sprinkler(噴頭)是一組相關的對象,在某個對象的事件產生時需要去操作其它對象,形成了下面這種依賴結構:
使用中介者模式可以將複雜的依賴結構變成星形結構:
public abstract class Colleague { public abstract void onEvent(Mediator mediator); }
public class Alarm extends Colleague { @Override public void onEvent(Mediator mediator) { mediator.doEvent("alarm"); } public void doAlarm() { System.out.println("doAlarm()"); } }
public class CoffeePot extends Colleague { @Override public void onEvent(Mediator mediator) { mediator.doEvent("coffeePot"); } public void doCoffeePot() { System.out.println("doCoffeePot()"); } }
public class Calender extends Colleague { @Override public void onEvent(Mediator mediator) { mediator.doEvent("calender"); } public void doCalender() { System.out.println("doCalender()"); } }
public class Sprinkler extends Colleague { @Override public void onEvent(Mediator mediator) { mediator.doEvent("sprinkler"); } public void doSprinkler() { System.out.println("doSprinkler()"); } }
public abstract class Mediator { public abstract void doEvent(String eventType); }
public class ConcreteMediator extends Mediator { private Alarm alarm; private CoffeePot coffeePot; private Calender calender; private Sprinkler sprinkler; public ConcreteMediator(Alarm alarm, CoffeePot coffeePot, Calender calender, Sprinkler sprinkler) { this.alarm = alarm; this.coffeePot = coffeePot; this.calender = calender; this.sprinkler = sprinkler; } @Override public void doEvent(String eventType) { switch (eventType) { case "alarm": doAlarmEvent(); break; case "coffeePot": doCoffeePotEvent(); break; case "calender": doCalenderEvent(); break; default: doSprinklerEvent(); } } public void doAlarmEvent() { alarm.doAlarm(); coffeePot.doCoffeePot(); calender.doCalender(); sprinkler.doSprinkler(); } public void doCoffeePotEvent() { // ... } public void doCalenderEvent() { // ... } public void doSprinklerEvent() { // ... } }
public class Client { public static void main(String[] args) { Alarm alarm = new Alarm(); CoffeePot coffeePot = new CoffeePot(); Calender calender = new Calender(); Sprinkler sprinkler = new Sprinkler(); Mediator mediator = new ConcreteMediator(alarm, coffeePot, calender, sprinkler); // 鬧鐘事件到達,調用中介者就可以操作相關對象 alarm.onEvent(mediator); } }
doAlarm() doCoffeePot() doCalender() doSprinkler()
JDK
- All scheduleXXX() methods of java.util.Timer
- java.util.concurrent.Executor#execute()
- submit() and invokeXXX() methods of java.util.concurrent.ExecutorService
- scheduleXXX() methods of java.util.concurrent.ScheduledExecutorService
- java.lang.reflect.Method#invoke()
6. 備忘錄(Memento)
Intent
在不違反封裝的情況下獲得對象的內部狀態,從而在需要時可以將對象恢復到最初狀態。
Class Diagram
- Originator:原始對象
- Caretaker:負責保存好備忘錄
- Menento:備忘錄,存儲原始對象的的狀態。備忘錄實際上有兩個接口,一個是提供給 Caretaker 的窄接口:它只能將備忘錄傳遞給其它對象;一個是提供給 Originator 的寬接口,允許它訪問到先前狀態所需的所有數據。理想情況是隻允許 Originator 訪問本備忘錄的內部狀態。
Implementation
以下實現了一個簡單計算器程序,可以輸入兩個值,然後計算這兩個值的和。備忘錄模式允許將這兩個值存儲起來,然後在某個時刻用存儲的狀態進行恢復。
實現參考:Memento Pattern - Calculator Example - Java Sourcecode
/** * Originator Interface */ public interface Calculator { // Create Memento PreviousCalculationToCareTaker backupLastCalculation(); // setMemento void restorePreviousCalculation(PreviousCalculationToCareTaker memento); int getCalculationResult(); void setFirstNumber(int firstNumber); void setSecondNumber(int secondNumber); }
/** * Originator Implementation */ public class CalculatorImp implements Calculator { private int firstNumber; private int secondNumber; @Override public PreviousCalculationToCareTaker backupLastCalculation() { // create a memento object used for restoring two numbers return new PreviousCalculationImp(firstNumber, secondNumber); } @Override public void restorePreviousCalculation(PreviousCalculationToCareTaker memento) { this.firstNumber = ((PreviousCalculationToOriginator) memento).getFirstNumber(); this.secondNumber = ((PreviousCalculationToOriginator) memento).getSecondNumber(); } @Override public int getCalculationResult() { // result is adding two numbers return firstNumber + secondNumber; } @Override public void setFirstNumber(int firstNumber) { this.firstNumber = firstNumber; } @Override public void setSecondNumber(int secondNumber) { this.secondNumber = secondNumber; } }
/** * Memento Interface to Originator * * This interface allows the originator to restore its state */ public interface PreviousCalculationToOriginator { int getFirstNumber(); int getSecondNumber(); }
/** * Memento interface to CalculatorOperator (Caretaker) */ public interface PreviousCalculationToCareTaker { // no operations permitted for the caretaker }
/** * Memento Object Implementation * <p> * Note that this object implements both interfaces to Originator and CareTaker */ public class PreviousCalculationImp implements PreviousCalculationToCareTaker, PreviousCalculationToOriginator { private int firstNumber; private int secondNumber; public PreviousCalculationImp(int firstNumber, int secondNumber) { this.firstNumber = firstNumber; this.secondNumber = secondNumber; } @Override public int getFirstNumber() { return firstNumber; } @Override public int getSecondNumber() { return secondNumber; } }
/** * CareTaker object */ public class Client { public static void main(String[] args) { // program starts Calculator calculator = new CalculatorImp(); // assume user enters two numbers calculator.setFirstNumber(10); calculator.setSecondNumber(100); // find result System.out.println(calculator.getCalculationResult()); // Store result of this calculation in case of error PreviousCalculationToCareTaker memento = calculator.backupLastCalculation(); // user enters a number calculator.setFirstNumber(17); // user enters a wrong second number and calculates result calculator.setSecondNumber(-290); // calculate result System.out.println(calculator.getCalculationResult()); // user hits CTRL + Z to undo last operation and see last result calculator.restorePreviousCalculation(memento); // result restored System.out.println(calculator.getCalculationResult()); } }
110 -273 110
JDK
- java.io.Serializable
7. 觀察者(Observer)
Intent
定義對象之間的一對多依賴,當一個對象狀態改變時,它的所有依賴都會收到通知並且自動更新狀態。
主題(Subject)是被觀察的對象,而其所有依賴者(Observer)稱爲觀察者。
Class Diagram
主題(Subject)具有註冊和移除觀察者、並通知所有觀察者的功能,主題是通過維護一張觀察者列表來實現這些操作的。
觀察者(Observer)的註冊功能需要調用主題的 registerObserver() 方法。
Implementation
天氣數據佈告板會在天氣信息發生改變時更新其內容,佈告板有多個,並且在將來會繼續增加。
public interface Subject { void resisterObserver(Observer o); void removeObserver(Observer o); void notifyObserver(); }
public class WeatherData implements Subject { private List<Observer> observers; private float temperature; private float humidity; private float pressure; public WeatherData() { observers = new ArrayList<>(); } public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; notifyObserver(); } @Override public void resisterObserver(Observer o) { observers.add(o); } @Override public void removeObserver(Observer o) { int i = observers.indexOf(o); if (i >= 0) { observers.remove(i); } } @Override public void notifyObserver() { for (Observer o : observers) { o.update(temperature, humidity, pressure); } } }
public interface Observer { void update(float temp, float humidity, float pressure); }
public class StatisticsDisplay implements Observer { public StatisticsDisplay(Subject weatherData) { weatherData.resisterObserver(this); } @Override public void update(float temp, float humidity, float pressure) { System.out.println("StatisticsDisplay.update: " + temp + " " + humidity + " " + pressure); } }
public class CurrentConditionsDisplay implements Observer { public CurrentConditionsDisplay(Subject weatherData) { weatherData.resisterObserver(this); } @Override public void update(float temp, float humidity, float pressure) { System.out.println("CurrentConditionsDisplay.update: " + temp + " " + humidity + " " + pressure); } }
public class WeatherStation { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData); StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); weatherData.setMeasurements(0, 0, 0); weatherData.setMeasurements(1, 1, 1); } }
CurrentConditionsDisplay.update: 0.0 0.0 0.0 StatisticsDisplay.update: 0.0 0.0 0.0 CurrentConditionsDisplay.update: 1.0 1.0 1.0 StatisticsDisplay.update: 1.0 1.0 1.0
JDK
8. 狀態(State)
Intent
允許對象在內部狀態改變時改變它的行爲,對象看起來好像修改了它所屬的類。
Class Diagram
Implementation
糖果銷售機有多種狀態,每種狀態下銷售機有不同的行爲,狀態可以發生轉移,使得銷售機的行爲也發生改變。
public interface State { /** * 投入 25 分錢 */ void insertQuarter(); /** * 退回 25 分錢 */ void ejectQuarter(); /** * 轉動曲柄 */ void turnCrank(); /** * 發放糖果 */ void dispense(); }
public class HasQuarterState implements State { private GumballMachine gumballMachine; public HasQuarterState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; } @Override public void insertQuarter() { System.out.println("You can't insert another quarter"); } @Override public void ejectQuarter() { System.out.println("Quarter returned"); gumballMachine.setState(gumballMachine.getNoQuarterState()); } @Override public void turnCrank() { System.out.println("You turned..."); gumballMachine.setState(gumballMachine.getSoldState()); } @Override public void dispense() { System.out.println("No gumball dispensed"); } }
public class NoQuarterState implements State { GumballMachine gumballMachine; public NoQuarterState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; } @Override public void insertQuarter() { System.out.println("You insert a quarter"); gumballMachine.setState(gumballMachine.getHasQuarterState()); } @Override public void ejectQuarter() { System.out.println("You haven't insert a quarter"); } @Override public void turnCrank() { System.out.println("You turned, but there's no quarter"); } @Override public void dispense() { System.out.println("You need to pay first"); } }
public class SoldOutState implements State { GumballMachine gumballMachine; public SoldOutState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; } @Override public void insertQuarter() { System.out.println("You can't insert a quarter, the machine is sold out"); } @Override public void ejectQuarter() { System.out.println("You can't eject, you haven't inserted a quarter yet"); } @Override public void turnCrank() { System.out.println("You turned, but there are no gumballs"); } @Override public void dispense() { System.out.println("No gumball dispensed"); } }
public class SoldState implements State { GumballMachine gumballMachine; public SoldState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; } @Override public void insertQuarter() { System.out.println("Please wait, we're already giving you a gumball"); } @Override public void ejectQuarter() { System.out.println("Sorry, you already turned the crank"); } @Override public void turnCrank() { System.out.println("Turning twice doesn't get you another gumball!"); } @Override public void dispense() { gumballMachine.releaseBall(); if (gumballMachine.getCount() > 0) { gumballMachine.setState(gumballMachine.getNoQuarterState()); } else { System.out.println("Oops, out of gumballs"); gumballMachine.setState(gumballMachine.getSoldOutState()); } } }
public class GumballMachine { private State soldOutState; private State noQuarterState; private State hasQuarterState; private State soldState; private State state; private int count = 0; public GumballMachine(int numberGumballs) { count = numberGumballs; soldOutState = new SoldOutState(this); noQuarterState = new NoQuarterState(this); hasQuarterState = new HasQuarterState(this); soldState = new SoldState(this); if (numberGumballs > 0) { state = noQuarterState; } else { state = soldOutState; } } public void insertQuarter() { state.insertQuarter(); } public void ejectQuarter() { state.ejectQuarter(); } public void turnCrank() { state.turnCrank(); state.dispense(); } public void setState(State state) { this.state = state; } public void releaseBall() { System.out.println("A gumball comes rolling out the slot..."); if (count != 0) { count -= 1; } } public State getSoldOutState() { return soldOutState; } public State getNoQuarterState() { return noQuarterState; } public State getHasQuarterState() { return hasQuarterState; } public State getSoldState() { return soldState; } public int getCount() { return count; } }
public class Client { public static void main(String[] args) { GumballMachine gumballMachine = new GumballMachine(5); gumballMachine.insertQuarter(); gumballMachine.turnCrank(); gumballMachine.insertQuarter(); gumballMachine.ejectQuarter(); gumballMachine.turnCrank(); gumballMachine.insertQuarter(); gumballMachine.turnCrank(); gumballMachine.insertQuarter(); gumballMachine.turnCrank(); gumballMachine.ejectQuarter(); gumballMachine.insertQuarter(); gumballMachine.insertQuarter(); gumballMachine.turnCrank(); gumballMachine.insertQuarter(); gumballMachine.turnCrank(); gumballMachine.insertQuarter(); gumballMachine.turnCrank(); } }
You insert a quarter You turned... A gumball comes rolling out the slot... You insert a quarter Quarter returned You turned, but there's no quarter You need to pay first You insert a quarter You turned... A gumball comes rolling out the slot... You insert a quarter You turned... A gumball comes rolling out the slot... You haven't insert a quarter You insert a quarter You can't insert another quarter You turned... A gumball comes rolling out the slot... You insert a quarter You turned... A gumball comes rolling out the slot... Oops, out of gumballs You can't insert a quarter, the machine is sold out You turned, but there are no gumballs 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
設計一個鴨子,它可以動態地改變叫聲。這裏的算法族是鴨子的叫聲行爲。
public interface QuackBehavior { void quack(); }
public class Quack implements QuackBehavior { @Override public void quack() { System.out.println("quack!"); } }
public class Squeak implements QuackBehavior{ @Override public void quack() { System.out.println("squeak!"); } }
public class Duck { private QuackBehavior quackBehavior; public void performQuack() { if (quackBehavior != null) { quackBehavior.quack(); } } public void setQuackBehavior(QuackBehavior quackBehavior) { this.quackBehavior = quackBehavior; } }
public class Client { public static void main(String[] args) { Duck duck = new Duck(); duck.setQuackBehavior(new Squeak()); duck.performQuack(); duck.setQuackBehavior(new Quack()); duck.performQuack(); } }
squeak! quack!
JDK
- java.util.Comparator#compare()
- javax.servlet.http.HttpServlet
- javax.servlet.Filter#doFilter()
10. 模板方法(Template Method)
Intent
定義算法框架,並將一些步驟的實現延遲到子類。
通過模板方法,子類可以重新定義算法的某些步驟,而不用改變算法的結構。
Class Diagram
Implementation
衝咖啡和沖茶都有類似的流程,但是某些步驟會有點不一樣,要求複用那些相同步驟的代碼。
public abstract class CaffeineBeverage { final void prepareRecipe() { boilWater(); brew(); pourInCup(); addCondiments(); } abstract void brew(); abstract void addCondiments(); void boilWater() { System.out.println("boilWater"); } void pourInCup() { System.out.println("pourInCup"); } }
public class Coffee extends CaffeineBeverage { @Override void brew() { System.out.println("Coffee.brew"); } @Override void addCondiments() { System.out.println("Coffee.addCondiments"); } }
public class Tea extends CaffeineBeverage { @Override void brew() { System.out.println("Tea.brew"); } @Override void addCondiments() { System.out.println("Tea.addCondiments"); } }
public class Client { public static void main(String[] args) { CaffeineBeverage caffeineBeverage = new Coffee(); caffeineBeverage.prepareRecipe(); System.out.println("-----------"); caffeineBeverage = new Tea(); caffeineBeverage.prepareRecipe(); } }
boilWater Coffee.brew pourInCup Coffee.addCondiments ----------- boilWater Tea.brew pourInCup Tea.addCondiments
JDK
- java.util.Collections#sort()
- java.io.InputStream#skip()
- java.io.InputStream#read()
- java.util.AbstractList#indexOf()
11. 訪問者(Visitor)
Intent
爲一個對象結構(比如組合結構)增加新能力。
Class Diagram
- Visitor:訪問者,爲每一個 ConcreteElement 聲明一個 visit 操作
- ConcreteVisitor:具體訪問者,存儲遍歷過程中的累計結果
- ObjectStructure:對象結構,可以是組合結構,或者是一個集合。
Implementation
public interface Element { void accept(Visitor visitor); }
class CustomerGroup { private List<Customer> customers = new ArrayList<>(); void accept(Visitor visitor) { for (Customer customer : customers) { customer.accept(visitor); } } void addCustomer(Customer customer) { customers.add(customer); } }
public class Customer implements Element { private String name; private List<Order> orders = new ArrayList<>(); Customer(String name) { this.name = name; } String getName() { return name; } void addOrder(Order order) { orders.add(order); } public void accept(Visitor visitor) { visitor.visit(this); for (Order order : orders) { order.accept(visitor); } } }
public class Order implements Element { private String name; private List<Item> items = new ArrayList(); Order(String name) { this.name = name; } Order(String name, String itemName) { this.name = name; this.addItem(new Item(itemName)); } String getName() { return name; } void addItem(Item item) { items.add(item); } public void accept(Visitor visitor) { visitor.visit(this); for (Item item : items) { item.accept(visitor); } } }
public class Item implements Element { private String name; Item(String name) { this.name = name; } String getName() { return name; } public void accept(Visitor visitor) { visitor.visit(this); } }
public interface Visitor { void visit(Customer customer); void visit(Order order); void visit(Item item); }
public class GeneralReport implements Visitor { private int customersNo; private int ordersNo; private int itemsNo; public void visit(Customer customer) { System.out.println(customer.getName()); customersNo++; } public void visit(Order order) { System.out.println(order.getName()); ordersNo++; } public void visit(Item item) { System.out.println(item.getName()); itemsNo++; } public void displayResults() { System.out.println("Number of customers: " + customersNo); System.out.println("Number of orders: " + ordersNo); System.out.println("Number of items: " + itemsNo); } }
public class Client { public static void main(String[] args) { Customer customer1 = new Customer("customer1"); customer1.addOrder(new Order("order1", "item1")); customer1.addOrder(new Order("order2", "item1")); customer1.addOrder(new Order("order3", "item1")); Order order = new Order("order_a"); order.addItem(new Item("item_a1")); order.addItem(new Item("item_a2")); order.addItem(new Item("item_a3")); Customer customer2 = new Customer("customer2"); customer2.addOrder(order); CustomerGroup customers = new CustomerGroup(); customers.addCustomer(customer1); customers.addCustomer(customer2); GeneralReport visitor = new GeneralReport(); customers.accept(visitor); visitor.displayResults(); } }
customer1 order1 item1 order2 item1 order3 item1 customer2 order_a item_a1 item_a2 item_a3 Number of customers: 2 Number of orders: 4 Number of items: 6
JDK
- javax.lang.model.element.Element and javax.lang.model.element.ElementVisitor
- javax.lang.model.type.TypeMirror and javax.lang.model.type.TypeVisitor
12. 空對象(Null)
Intent
使用什麼都不做的空對象來代替 NULL。
一個方法返回 NULL,意味着方法的調用端需要去檢查返回值是否是 NULL,這麼做會導致非常多的冗餘的檢查代碼。並且如果某一個調用端忘記了做這個檢查返回值,而直接使用返回的對象,那麼就有可能拋出空指針異常。
Class Diagram
Implementation
public abstract class AbstractOperation { abstract void request(); }
public class RealOperation extends AbstractOperation { @Override void request() { System.out.println("do something"); } }
public class NullOperation extends AbstractOperation{ @Override void request() { // do nothing } }
public class Client { public static void main(String[] args) { AbstractOperation abstractOperation = func(-1); abstractOperation.request(); } public static AbstractOperation func(int para) { if (para < 0) { return new NullOperation(); } return new RealOperation(); } }
四、結構型
1. 適配器(Adapter)
Intent
把一個類接口轉換成另一個用戶需要的接口。
Class Diagram
Implementation
鴨子(Duck)和火雞(Turkey)擁有不同的叫聲,Duck 的叫聲調用 quack() 方法,而 Turkey 調用 gobble() 方法。
要求將 Turkey 的 gobble() 方法適配成 Duck 的 quack() 方法,從而讓火雞冒充鴨子!
public interface Duck { void quack(); }
public interface Turkey { void gobble(); }
public class WildTurkey implements Turkey { @Override public void gobble() { System.out.println("gobble!"); } }
public class TurkeyAdapter implements Duck { Turkey turkey; public TurkeyAdapter(Turkey turkey) { this.turkey = turkey; } @Override public void quack() { turkey.gobble(); } }
public class Client { public static void main(String[] args) { Turkey turkey = new WildTurkey(); Duck duck = new TurkeyAdapter(turkey); duck.quack(); } }
JDK
- java.util.Arrays#asList()
- java.util.Collections#list()
- java.util.Collections#enumeration()
- javax.xml.bind.annotation.adapters.XMLAdapter
2. 橋接(Bridge)
Intent
將抽象與實現分離開來,使它們可以獨立變化。
Class Diagram
- Abstraction:定義抽象類的接口
- Implementor:定義實現類接口
Implementation
RemoteControl 表示遙控器,指代 Abstraction。
TV 表示電視,指代 Implementor。
橋接模式將遙控器和電視分離開來,從而可以獨立改變遙控器或者電視的實現。
public abstract class TV { public abstract void on(); public abstract void off(); public abstract void tuneChannel(); }
public class Sony extends TV { @Override public void on() { System.out.println("Sony.on()"); } @Override public void off() { System.out.println("Sony.off()"); } @Override public void tuneChannel() { System.out.println("Sony.tuneChannel()"); } }
public class RCA extends TV { @Override public void on() { System.out.println("RCA.on()"); } @Override public void off() { System.out.println("RCA.off()"); } @Override public void tuneChannel() { System.out.println("RCA.tuneChannel()"); } }
public abstract class RemoteControl { protected TV tv; public RemoteControl(TV tv) { this.tv = tv; } public abstract void on(); public abstract void off(); public abstract void tuneChannel(); }
public class ConcreteRemoteControl1 extends RemoteControl { public ConcreteRemoteControl1(TV tv) { super(tv); } @Override public void on() { System.out.println("ConcreteRemoteControl1.on()"); tv.on(); } @Override public void off() { System.out.println("ConcreteRemoteControl1.off()"); tv.off(); } @Override public void tuneChannel() { System.out.println("ConcreteRemoteControl1.tuneChannel()"); tv.tuneChannel(); } }
public class ConcreteRemoteControl2 extends RemoteControl { public ConcreteRemoteControl2(TV tv) { super(tv); } @Override public void on() { System.out.println("ConcreteRemoteControl2.on()"); tv.on(); } @Override public void off() { System.out.println("ConcreteRemoteControl2.off()"); tv.off(); } @Override public void tuneChannel() { System.out.println("ConcreteRemoteControl2.tuneChannel()"); tv.tuneChannel(); } }
public class Client { public static void main(String[] args) { RemoteControl remoteControl1 = new ConcreteRemoteControl1(new RCA()); remoteControl1.on(); remoteControl1.off(); remoteControl1.tuneChannel(); } }
JDK
- AWT (It provides an abstraction layer which maps onto the native OS the windowing support.)
- JDBC
3. 組合(Composite)
Intent
將對象組合成樹形結構來表示“整體/部分”層次關係,允許用戶以相同的方式處理單獨對象和組合對象。
Class Diagram
組件(Component)類是組合類(Composite)和葉子類(Leaf)的父類,可以把組合類看成是樹的中間節點。
組合對象擁有一個或者多個組件對象,因此組合對象的操作可以委託給組件對象去處理,而組件對象可以是另一個組合對象或者葉子對象。
Implementation
public abstract class Component { protected String name; public Component(String name) { this.name = name; } public void print() { print(0); } abstract void print(int level); abstract public void add(Component component); abstract public void remove(Component component); }
public class Composite extends Component { private List<Component> child; public Composite(String name) { super(name); child = new ArrayList<>(); } @Override void print(int level) { for (int i = 0; i < level; i++) { System.out.print("--"); } System.out.println("Composite:" + name); for (Component component : child) { component.print(level + 1); } } @Override public void add(Component component) { child.add(component); } @Override public void remove(Component component) { child.remove(component); } }
public class Leaf extends Component { public Leaf(String name) { super(name); } @Override void print(int level) { for (int i = 0; i < level; i++) { System.out.print("--"); } System.out.println("left:" + name); } @Override public void add(Component component) { throw new UnsupportedOperationException(); // 犧牲透明性換取單一職責原則,這樣就不用考慮是葉子節點還是組合節點 } @Override public void remove(Component component) { throw new UnsupportedOperationException(); } }
public class Client { public static void main(String[] args) { Composite root = new Composite("root"); Component node1 = new Leaf("1"); Component node2 = new Composite("2"); Component node3 = new Leaf("3"); root.add(node1); root.add(node2); root.add(node3); Component node21 = new Leaf("21"); Component node22 = new Composite("22"); node2.add(node21); node2.add(node22); Component node221 = new Leaf("221"); node22.add(node221); root.print(); } }
Composite:root --left:1 --Composite:2 ----left:21 ----Composite:22 ------left:221 --left:3
JDK
- javax.swing.JComponent#add(Component)
- java.awt.Container#add(Component)
- java.util.Map#putAll(Map)
- java.util.List#addAll(Collection)
- java.util.Set#addAll(Collection)
4. 裝飾(Decorator)
Intent
爲對象動態添加功能。
Class Diagram
裝飾者(Decorator)和具體組件(ConcreteComponent)都繼承自組件(Component),具體組件的方法實現不需要依賴於其它對象,而裝飾者組合了一個組件,這樣它可以裝飾其它裝飾者或者具體組件。所謂裝飾,就是把這個裝飾者套在被裝飾者之上,從而動態擴展被裝飾者的功能。裝飾者的方法有一部分是自己的,這屬於它的功能,然後調用被裝飾者的方法實現,從而也保留了被裝飾者的功能。可以看到,具體組件應當是裝飾層次的最低層,因爲只有具體組件的方法實現不需要依賴於其它對象。
Implementation
設計不同種類的飲料,飲料可以添加配料,比如可以添加牛奶,並且支持動態添加新配料。每增加一種配料,該飲料的價格就會增加,要求計算一種飲料的價格。
下圖表示在 DarkRoast 飲料上新增新添加 Mocha 配料,之後又添加了 Whip 配料。DarkRoast 被 Mocha 包裹,Mocha 又被 Whip 包裹。它們都繼承自相同父類,都有 cost() 方法,外層類的 cost() 方法調用了內層類的 cost() 方法。
public interface Beverage { double cost(); }
public class DarkRoast implements Beverage { @Override public double cost() { return 1; } }
public class HouseBlend implements Beverage { @Override public double cost() { return 1; } }
public abstract class CondimentDecorator implements Beverage { protected Beverage beverage; }
public class Milk extends CondimentDecorator { public Milk(Beverage beverage) { this.beverage = beverage; } @Override public double cost() { return 1 + beverage.cost(); } }
public class Mocha extends CondimentDecorator { public Mocha(Beverage beverage) { this.beverage = beverage; } @Override public double cost() { return 1 + beverage.cost(); } }
public class Client { public static void main(String[] args) { Beverage beverage = new HouseBlend(); beverage = new Mocha(beverage); beverage = new Milk(beverage); System.out.println(beverage.cost()); } }
3.0
設計原則
類應該對擴展開放,對修改關閉:也就是添加新功能時不需要修改代碼。飲料可以動態添加新的配料,而不需要去修改飲料的代碼。
不可能把所有的類設計成都滿足這一原則,應當把該原則應用於最有可能發生改變的地方。
JDK
- java.io.BufferedInputStream(InputStream)
- java.io.DataInputStream(InputStream)
- java.io.BufferedOutputStream(OutputStream)
- java.util.zip.ZipOutputStream(OutputStream)
- java.util.Collections#checkedList|Map|Set|SortedSet|SortedMap
5. 外觀(Facade)
Intent
提供了一個統一的接口,用來訪問子系統中的一羣接口,從而讓子系統更容易使用。
Class Diagram
Implementation
觀看電影需要操作很多電器,使用外觀模式實現一鍵看電影功能。
public class SubSystem { public void turnOnTV() { System.out.println("turnOnTV()"); } public void setCD(String cd) { System.out.println("setCD( " + cd + " )"); } public void starWatching(){ System.out.println("starWatching()"); } }
public class Facade { private SubSystem subSystem = new SubSystem(); public void watchMovie() { subSystem.turnOnTV(); subSystem.setCD("a movie"); subSystem.starWatching(); } }
public class Client { public static void main(String[] args) { Facade facade = new Facade(); facade.watchMovie(); } }
設計原則
最少知識原則:只和你的密友談話。也就是說客戶對象所需要交互的對象應當儘可能少。
6. 享元(Flyweight)
Intent
利用共享的方式來支持大量細粒度的對象,這些對象一部分內部狀態是相同的。
Class Diagram
- Flyweight:享元對象
- IntrinsicState:內部狀態,享元對象共享內部狀態
- ExtrinsicState:外部狀態,每個享元對象的外部狀態不同
Implementation
public interface Flyweight { void doOperation(String extrinsicState); }
public class ConcreteFlyweight implements Flyweight { private String intrinsicState; public ConcreteFlyweight(String intrinsicState) { this.intrinsicState = intrinsicState; } @Override public void doOperation(String extrinsicState) { System.out.println("Object address: " + System.identityHashCode(this)); System.out.println("IntrinsicState: " + intrinsicState); System.out.println("ExtrinsicState: " + extrinsicState); } }
public class FlyweightFactory { private HashMap<String, Flyweight> flyweights = new HashMap<>(); Flyweight getFlyweight(String intrinsicState) { if (!flyweights.containsKey(intrinsicState)) { Flyweight flyweight = new ConcreteFlyweight(intrinsicState); flyweights.put(intrinsicState, flyweight); } return flyweights.get(intrinsicState); } }
public class Client { public static void main(String[] args) { FlyweightFactory factory = new FlyweightFactory(); Flyweight flyweight1 = factory.getFlyweight("aa"); Flyweight flyweight2 = factory.getFlyweight("aa"); flyweight1.doOperation("x"); flyweight2.doOperation("y"); } }
Object address: 1163157884 IntrinsicState: aa ExtrinsicState: x Object address: 1163157884 IntrinsicState: aa ExtrinsicState: y
JDK
Java 利用緩存來加速大量小對象的訪問時間。
- java.lang.Integer#valueOf(int)
- java.lang.Boolean#valueOf(boolean)
- java.lang.Byte#valueOf(byte)
- java.lang.Character#valueOf(char)
7. 代理(Proxy)
Intent
控制對其它對象的訪問。
Class Diagram
代理有以下四類:
- 遠程代理(Remote Proxy):控制對遠程對象(不同地址空間)的訪問,它負責將請求及其參數進行編碼,並向不同地址空間中的對象發送已經編碼的請求。
- 虛擬代理(Virtual Proxy):根據需要創建開銷很大的對象,它可以緩存實體的附加信息,以便延遲對它的訪問,例如在網站加載一個很大圖片時,不能馬上完成,可以用虛擬代理緩存圖片的大小信息,然後生成一張臨時圖片代替原始圖片。
- 保護代理(Protection Proxy):按權限控制對象的訪問,它負責檢查調用者是否具有實現一個請求所必須的訪問權限。
- 智能代理(Smart Reference):取代了簡單的指針,它在訪問對象時執行一些附加操作:記錄對象的引用次數;當第一次引用一個對象時,將它裝入內存;在訪問一個實際對象前,檢查是否已經鎖定了它,以確保其它對象不能改變它。
Implementation
以下是一個虛擬代理的實現,模擬了圖片延遲加載的情況下使用與圖片大小相等的臨時內容去替換原始圖片,直到圖片加載完成纔將圖片顯示出來。
public interface Image { void showImage(); }
public class HighResolutionImage implements Image { private URL imageURL; private long startTime; private int height; private int width; public int getHeight() { return height; } public int getWidth() { return width; } public HighResolutionImage(URL imageURL) { this.imageURL = imageURL; this.startTime = System.currentTimeMillis(); this.width = 600; this.height = 600; } public boolean isLoad() { // 模擬圖片加載,延遲 3s 加載完成 long endTime = System.currentTimeMillis(); return endTime - startTime > 3000; } @Override public void showImage() { System.out.println("Real Image: " + imageURL); } }
public class ImageProxy implements Image { private HighResolutionImage highResolutionImage; public ImageProxy(HighResolutionImage highResolutionImage) { this.highResolutionImage = highResolutionImage; } @Override public void showImage() { while (!highResolutionImage.isLoad()) { try { System.out.println("Temp Image: " + highResolutionImage.getWidth() + " " + highResolutionImage.getHeight()); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } highResolutionImage.showImage(); } }
public class ImageViewer { public static void main(String[] args) throws Exception { String image = "http://image.jpg"; URL url = new URL(image); HighResolutionImage highResolutionImage = new HighResolutionImage(url); ImageProxy imageProxy = new ImageProxy(highResolutionImage); imageProxy.showImage(); } }