物件導向-設計模式-結構型
一年好景君須記,最是橙黃橘綠時。
簡介:物件導向-設計模式-結構型。
一、概述
何謂設計模式:
設計模式(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 遠端方法呼叫)
一年好景君須記
最是橙黃橘綠時