面向對象-設計模式-結構型
一年好景君須記,最是橙黃橘綠時。
簡介:面向對象-設計模式-結構型。
一、概述
何謂設計模式:
設計模式(Design Pattern)是一套被反復使用、多數人知曉的、經過分類的、代碼設計經驗的總結。
設計模式的好處&學習目的:
1、為了代碼可重用行、讓代碼更易被他人理解、保證代碼的可靠性、使代碼編寫真正實現工程化;
2、設計模式便於我們維護項目,增強系統的健壯性和可擴展性;
3、設計模式還可以鍛煉碼農的設計思維、升華代碼質量等。
二、結構型
適配器模式、橋接模式、組合模式、裝飾模式、外觀模式、享元模式、代理模式。
1. 適配器(Adapter)
Intent
把一個類接口轉換成另一個用戶需要的接口。

Class Diagram

Implementation
鴨子(Duck)和火雞(Turkey)擁有不同的叫聲,Duck 的叫聲調用 quack() 方法,而 Turkey 調用 gobble() 方法。
要求將 Turkey 的 gobble() 方法適配成 Duck 的 quack() 方法,從而讓火雞冒充鴨子!
1 public interface Duck { 2 void quack(); 3 }
1 public interface Turkey { 2 void gobble(); 3 }
1 public class WildTurkey implements Turkey { 2 @Override 3 public void gobble() { 4 System.out.println("gobble!"); 5 } 6 }
1 public class TurkeyAdapter implements Duck { 2 Turkey turkey; 3
4 public TurkeyAdapter(Turkey turkey) { 5 this.turkey = turkey; 6 } 7
8 @Override 9 public void quack() { 10 turkey.gobble(); 11 } 12 }
1 public class Client { 2 public static void main(String[] args) { 3 Turkey turkey = new WildTurkey(); 4 Duck duck = new TurkeyAdapter(turkey); 5 duck.quack(); 6 } 7 }
2. 橋接(Bridge)
Intent
將抽象與實現分離開來,使它們可以獨立變化。
Class Diagram
- Abstraction:定義抽象類的接口
- Implementor:定義實現類接口

Implementation
RemoteControl 表示遙控器,指代 Abstraction。
TV 表示電視,指代 Implementor。
橋接模式將遙控器和電視分離開來,從而可以獨立改變遙控器或者電視的實現。
1 public abstract class TV { 2 public abstract void on(); 3
4 public abstract void off(); 5
6 public abstract void tuneChannel(); 7 }
1 public class Sony extends TV { 2 @Override 3 public void on() { 4 System.out.println("Sony.on()"); 5 } 6
7 @Override 8 public void off() { 9 System.out.println("Sony.off()"); 10 } 11
12 @Override 13 public void tuneChannel() { 14 System.out.println("Sony.tuneChannel()"); 15 } 16 }
1 public class RCA extends TV { 2 @Override 3 public void on() { 4 System.out.println("RCA.on()"); 5 } 6
7 @Override 8 public void off() { 9 System.out.println("RCA.off()"); 10 } 11
12 @Override 13 public void tuneChannel() { 14 System.out.println("RCA.tuneChannel()"); 15 } 16 }
1 public abstract class RemoteControl { 2 protected TV tv; 3
4 public RemoteControl(TV tv) { 5 this.tv = tv; 6 } 7
8 public abstract void on(); 9
10 public abstract void off(); 11
12 public abstract void tuneChannel(); 13 }
1 public class ConcreteRemoteControl1 extends RemoteControl { 2 public ConcreteRemoteControl1(TV tv) { 3 super(tv); 4 } 5
6 @Override 7 public void on() { 8 System.out.println("ConcreteRemoteControl1.on()"); 9 tv.on(); 10 } 11
12 @Override 13 public void off() { 14 System.out.println("ConcreteRemoteControl1.off()"); 15 tv.off(); 16 } 17
18 @Override 19 public void tuneChannel() { 20 System.out.println("ConcreteRemoteControl1.tuneChannel()"); 21 tv.tuneChannel(); 22 } 23 }
1 public class ConcreteRemoteControl2 extends RemoteControl { 2 public ConcreteRemoteControl2(TV tv) { 3 super(tv); 4 } 5
6 @Override 7 public void on() { 8 System.out.println("ConcreteRemoteControl2.on()"); 9 tv.on(); 10 } 11
12 @Override 13 public void off() { 14 System.out.println("ConcreteRemoteControl2.off()"); 15 tv.off(); 16 } 17
18 @Override 19 public void tuneChannel() { 20 System.out.println("ConcreteRemoteControl2.tuneChannel()"); 21 tv.tuneChannel(); 22 } 23 }
1 public class Client { 2 public static void main(String[] args) { 3 RemoteControl remoteControl1 = new ConcreteRemoteControl1(new RCA()); 4 remoteControl1.on(); 5 remoteControl1.off(); 6 remoteControl1.tuneChannel(); 7 RemoteControl remoteControl2 = new ConcreteRemoteControl2(new Sony()); 8 remoteControl2.on(); 9 remoteControl2.off(); 10 remoteControl2.tuneChannel(); 11 } 12 }
3. 組合(Composite)
Intent
將對象組合成樹形結構來表示“整體/部分”層次關系,允許用戶以相同的方式處理單獨對象和組合對象。
Class Diagram
組件(Component)類是組合類(Composite)和葉子類(Leaf)的父類,可以把組合類看成是樹的中間節點。
組合對象擁有一個或者多個組件對象,因此組合對象的操作可以委托給組件對象去處理,而組件對象可以是另一個組合對象或者葉子對象。

Implementation
1 public abstract class Component { 2 protected String name; 3
4 public Component(String name) { 5 this.name = name; 6 } 7
8 public void print() { 9 print(0); 10 } 11
12 abstract void print(int level); 13
14 abstract public void add(Component component); 15
16 abstract public void remove(Component component); 17 }
1 public class Composite extends Component { 2
3 private List<Component> child; 4
5 public Composite(String name) { 6 super(name); 7 child = new ArrayList<>(); 8 } 9
10 @Override 11 void print(int level) { 12 for (int i = 0; i < level; i++) { 13 System.out.print("--"); 14 } 15 System.out.println("Composite:" + name); 16 for (Component component : child) { 17 component.print(level + 1); 18 } 19 } 20
21 @Override 22 public void add(Component component) { 23 child.add(component); 24 } 25
26 @Override 27 public void remove(Component component) { 28 child.remove(component); 29 } 30 }
1 public class Leaf extends Component { 2 public Leaf(String name) { 3 super(name); 4 } 5
6 @Override 7 void print(int level) { 8 for (int i = 0; i < level; i++) { 9 System.out.print("--"); 10 } 11 System.out.println("left:" + name); 12 } 13
14 @Override 15 public void add(Component component) { 16 throw new UnsupportedOperationException(); // 犧牲透明性換取單一職責原則,這樣就不用考慮是葉子節點還是組合節點
17 } 18
19 @Override 20 public void remove(Component component) { 21 throw new UnsupportedOperationException(); 22 } 23 }
1 public class Client { 2 public static void main(String[] args) { 3 Composite root = new Composite("root"); 4 Component node1 = new Leaf("1"); 5 Component node2 = new Composite("2"); 6 Component node3 = new Leaf("3"); 7 root.add(node1); 8 root.add(node2); 9 root.add(node3); 10 Component node21 = new Leaf("21"); 11 Component node22 = new Composite("22"); 12 node2.add(node21); 13 node2.add(node22); 14 Component node221 = new Leaf("221"); 15 node22.add(node221); 16 root.print(); 17 } 18 }
1 輸出: 2 Composite:root 3 --left:1
4 --Composite:2
5 ----left:21
6 ----Composite:22
7 ------left:221
8 --left:3
4. 裝飾(Decorator)
Intent
為對象動態添加功能。
Class Diagram
裝飾者(Decorator)和具體組件(ConcreteComponent)都繼承自組件(Component),具體組件的方法實現不需要依賴於其它對象,而裝飾者組合了一個組件,這樣它可以裝飾其它裝飾者或者具體組件。所謂裝飾,就是把這個裝飾者套在被裝飾者之上,從而動態擴展被裝飾者的功能。裝飾者的方法有一部分是自己的,這屬於它的功能,然后調用被裝飾者的方法實現,從而也保留了被裝飾者的功能。可以看到,具體組件應當是裝飾層次的最低層,因為只有具體組件的方法實現不需要依賴於其它對象。

Implementation
設計不同種類的飲料,飲料可以添加配料,比如可以添加牛奶,並且支持動態添加新配料。每增加一種配料,該飲料的價格就會增加,要求計算一種飲料的價格。
下圖表示在 DarkRoast 飲料上新增新添加 Mocha 配料,之后又添加了 Whip 配料。DarkRoast 被 Mocha 包裹,Mocha 又被 Whip 包裹。它們都繼承自相同父類,都有 cost() 方法,外層類的 cost() 方法調用了內層類的 cost() 方法。

1 public interface Beverage { 2 double cost(); 3 }
1 public class DarkRoast implements Beverage { 2 @Override 3 public double cost() { 4 return 1; 5 } 6 }
1 public class HouseBlend implements Beverage { 2 @Override 3 public double cost() { 4 return 1; 5 } 6 }
1 public abstract class CondimentDecorator implements Beverage { 2 protected Beverage beverage; 3 }
1 public class Milk extends CondimentDecorator { 2
3 public Milk(Beverage beverage) { 4 this.beverage = beverage; 5 } 6
7 @Override 8 public double cost() { 9 return 1 + beverage.cost(); 10 } 11 }
1 public class Mocha extends CondimentDecorator { 2
3 public Mocha(Beverage beverage) { 4 this.beverage = beverage; 5 } 6
7 @Override 8 public double cost() { 9 return 1 + beverage.cost(); 10 } 11 }
1 public class Client { 2
3 public static void main(String[] args) { 4 Beverage beverage = new HouseBlend(); 5 beverage = new Mocha(beverage); 6 beverage = new Milk(beverage); 7 System.out.println(beverage.cost()); // 3.0
8 } 9 }
設計原則
類應該對擴展開放,對修改關閉:也就是添加新功能時不需要修改代碼。飲料可以動態添加新的配料,而不需要去修改飲料的代碼。
不可能把所有的類設計成都滿足這一原則,應當把該原則應用於最有可能發生改變的地方。
5. 外觀(Facade)
Intent
提供了一個統一的接口,用來訪問子系統中的一群接口,從而讓子系統更容易使用。
Class Diagram

Implementation
觀看電影需要操作很多電器,使用外觀模式實現一鍵看電影功能。
1 public class SubSystem { 2 public void turnOnTV() { 3 System.out.println("turnOnTV()"); 4 } 5
6 public void setCD(String cd) { 7 System.out.println("setCD( " + cd + " )"); 8 } 9
10 public void startWatching(){ 11 System.out.println("startWatching()"); 12 } 13 }
1 public class Facade { 2 private SubSystem subSystem = new SubSystem(); 3
4 public void watchMovie() { 5 subSystem.turnOnTV(); 6 subSystem.setCD("a movie"); 7 subSystem.startWatching(); 8 } 9 }
1 public class Client { 2 public static void main(String[] args) { 3 Facade facade = new Facade(); 4 facade.watchMovie(); 5 } 6 }
設計原則
最少知道原則:只和你的密友談話。也就是說客戶對象所需要交互的對象應當盡可能少。
6. 享元(Flyweight)
Intent
利用共享的方式來支持大量細粒度的對象,這些對象一部分內部狀態是相同的。
Class Diagram
- Flyweight:享元對象
- IntrinsicState:內部狀態,享元對象共享內部狀態
- ExtrinsicState:外部狀態,每個享元對象的外部狀態不同

Implementation
1 public interface Flyweight { 2 void doOperation(String extrinsicState); 3 }
1 public class ConcreteFlyweight implements Flyweight { 2
3 private String intrinsicState; 4
5 public ConcreteFlyweight(String intrinsicState) { 6 this.intrinsicState = intrinsicState; 7 } 8
9 @Override 10 public void doOperation(String extrinsicState) { 11 System.out.println("Object address: " + System.identityHashCode(this)); 12 System.out.println("IntrinsicState: " + intrinsicState); 13 System.out.println("ExtrinsicState: " + extrinsicState); 14 } 15 }
1 public class FlyweightFactory { 2
3 private HashMap<String, Flyweight> flyweights = new HashMap<>(); 4
5 Flyweight getFlyweight(String intrinsicState) { 6 if (!flyweights.containsKey(intrinsicState)) { 7 Flyweight flyweight = new ConcreteFlyweight(intrinsicState); 8 flyweights.put(intrinsicState, flyweight); 9 } 10 return flyweights.get(intrinsicState); 11 } 12 }
1 public class Client { 2
3 public static void main(String[] args) { 4 FlyweightFactory factory = new FlyweightFactory(); 5 Flyweight flyweight1 = factory.getFlyweight("aa"); 6 Flyweight flyweight2 = factory.getFlyweight("aa"); 7 flyweight1.doOperation("x"); 8 flyweight2.doOperation("y"); 9 } 10 }
1 輸出: 2 Object address: 1163157884
3 IntrinsicState: aa 4 ExtrinsicState: x 5 Object address: 1163157884
6 IntrinsicState: aa 7 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
以下是一個虛擬代理的實現,模擬了圖片延遲加載的情況下使用與圖片大小相等的臨時內容去替換原始圖片,直到圖片加載完成才將圖片顯示出來。
1 public interface Image { 2 void showImage(); 3 }
1 public class HighResolutionImage implements Image { 2
3 private URL imageURL; 4 private long startTime; 5 private int height; 6 private int width; 7
8 public int getHeight() { 9 return height; 10 } 11
12 public int getWidth() { 13 return width; 14 } 15
16 public HighResolutionImage(URL imageURL) { 17 this.imageURL = imageURL; 18 this.startTime = System.currentTimeMillis(); 19 this.width = 600; 20 this.height = 600; 21 } 22
23 public boolean isLoad() { 24 // 模擬圖片加載,延遲 3s 加載完成
25 long endTime = System.currentTimeMillis(); 26 return endTime - startTime > 3000; 27 } 28
29 @Override 30 public void showImage() { 31 System.out.println("Real Image: " + imageURL); 32 } 33 }
1 public class ImageProxy implements Image { 2
3 private HighResolutionImage highResolutionImage; 4
5 public ImageProxy(HighResolutionImage highResolutionImage) { 6 this.highResolutionImage = highResolutionImage; 7 } 8
9 @Override 10 public void showImage() { 11 while (!highResolutionImage.isLoad()) { 12 try { 13 System.out.println("Temp Image: " + highResolutionImage.getWidth() + " " + highResolutionImage.getHeight()); 14 Thread.sleep(100); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 } 19 highResolutionImage.showImage(); 20 } 21 }
1 public class ImageViewer { 2
3 public static void main(String[] args) throws Exception { 4 String image = "http://www.ttt.com/image.jpg"; 5 URL url = new URL(image); 6 HighResolutionImage highResolutionImage = new HighResolutionImage(url); 7 ImageProxy imageProxy = new ImageProxy(highResolutionImage); 8 imageProxy.showImage(); 9 } 10 }
JDK
- java.lang.reflect.Proxy
- RMI(Remote Method Invocation 遠程方法調用)
一年好景君須記
最是橙黃橘綠時

