《Head First設計模式》第七章-適配器模式、外觀模式

適配器模式

適配器模式是什么,你一定不難理解,因為現實中到處都是。比如說:
如果你需要在歐洲國家使用美國制造的筆記本電腦,你可能需要使用一個交流電的適配器……

圖片說明
當你不想改變現有的代碼,解決接口不適配問題,便可使用適配器模式,你可以寫一個類,將新廠商接口轉接成你所期望的接口。

圖片說明

定義適配器模式:將一個類的接口,轉換成客戶期望的另一個接口。適配器讓原本接口不兼容的類可以合作無間。

現在我們已經知道什么是適配器了,讓我們后退一步,再次看看各部分之間的關系。

圖片說明

類圖:

圖片說明

讓我們以開頭所提到的電腦電源插頭適配的問題為例:

新建一個電腦類:

1

2

3

4

5

6

7

8

9

10

11

public class Computer {

?????//充電方法只能使用兩孔插座,只能傳入兩孔插座做參數

????public void charge(Socket_Two socket_Two) {

????????socket_Two.connect();

????????addPower();//調用增加電量的方法

????}

????//充電成功,電量增加

????private void addPower() {

????????System.out.println("電源已連接,充電中...");

????}

創建一個兩孔插座接口:

1

2

3

public interface Socket_Two {

????void connect();//連接插座方法

}

再創建一個三孔插座類:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

public class Socket_Three {???

????//連接方法

????public void connect() {

????????//調用每個孔接通的方法

????????leftConnect();

????????rightconnect();

????????extraConner();

????}

????//三孔接通方法

????public void rightconnect() {

????????System.out.println("火線接通...");

????}

????public void leftConnect() {

????????System.out.println("零線接通...");

????}

????public void extraConner() {

????????System.out.println("地線接通...");

????}

}

創建一個適配器:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

//三孔插座與兩孔插座適配器,直接關聯被適配類,同時實現標準接口

public class LineWithSocket_Two?implements Socket_Two {

????// 直接關聯被適配類

????private Socket_Three socket_Three;

????//通過構造函數傳入具體需要適配的被適配類對象

????public LineWithSocket_Two(Socket_Three socket_Three) {

????????this.socket_Three=socket_Three;

????}

????@Override

????public void connect() {

????????//使用委托的方式完成特殊功能

????????System.out.println("我是適配器:通過我可以讓兩腳插頭使用三孔插座")

????????socket_Three.leftConnect();

????????socket_Three.rightconnect();

????}

}

測試類

1

2

3

4

5

6

7

8

public static void main(String[] args) {

????Computer computer=new Computer();

????Socket_Three socket_three=new Socket_Three();

????//調用適配器類來完成適配

????LineWithSocket_Two lineWithScoket_Two=new LineWithSocket_Two(socket_three);

????System.out.println("使用適配器:");

????computer.charge(lineWithScoket_Two);

}

運行結果:

圖片說明

其他

實際上適配器模式中有“兩種”適配器:“對象”適配器“類”適配器

究竟什么是“類”適配器?為什么我們還沒告訴你這種適配器?因為你需要多重繼承才能夠實現它,這在Java中是不可能的。但是當你在使用多重繼承語言的時候,還是可能遇到這樣的需求。

讓我們看看多重繼承的類圖:

圖片說明

看起來很熟悉嗎?沒錯,唯一的差別就在于適配器繼承了Target和Adaptee。而對象適配器利用組合的方式將請求傳送給被適配者。

對象適配器和類適配器使用兩種不同的適配方法(分別是組合與繼承)。

適配器模式適用場景

  1. 重復使用現有的類,而此類的接口不符合系統的需要。在遺留代碼復用、類庫遷移等方面非常有用。
  2. 想要建立一個可以重用的類,用于與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作。
  3. 使用第三方組件或中間件,組件接口定義和自己定義的不同,不希望修改自己的接口,但是要使用第三方組件接口的 功能,避免重復造輪子。

適配器模式優缺點

優點:

  1. 將目標類和適配者類解耦。
  2. 增加了類的透明性和復用性,將具體的實現封裝在適配者類中,對于客戶端類來說是透明的,提高了適配者的復用性。
  3. 靈活性和擴展性都非常好,符合開閉原則

類適配器優點:

  • 由于適配器類是適配者類的子類,因此可以在適配器類中置換一些適配者的方法,使得適配器的靈活性更強。

對象適配器優點:

  • 把多個不同的適配者適配到同一個目標,也就是說,同一個適配器可以把適配者類和他的子類都適配到目標接口。

缺點:

  • 實現適配器所需要的工作和目標接口的大小成正比,接口越復雜適配器也越復雜。

類適配器缺點:

  • 對于Java、C#等不支持多重繼承的語言,一次最多只能適配一個適配者類,而且目標抽象類只能為接口,不能為類,其使用有一定的局限性,不能將一個適配者類和他的子類同時適配到目標接口。

對象適配器的缺點:

  • 與類適配器模式相比,要想置換適配者類的方法就不容易。

?




通過上一篇你已經知道適配器模式是如何將一個類的接口轉換成另一個符合客戶期望的接口的。你也知道在Java中要做到這一點,必須將一個不兼容接口的對象包裝起來,變成兼容的對象。
? 我們現在要看一個改變接口的新模式,但是它改變接口的原因是為了簡化接口,這個模式被巧妙的命名為外觀模式。它將一個或數個類的復雜的一切都隱藏在背后,只顯露出一個干凈美好的外觀。

定義

外觀模式定義:外觀模式提供了一個統一的接口,用來訪問子系統中的一群接口。外觀定義了一個高層接口,讓子系統更容易使用。

外觀模式實現了最少知識原則(Least Knowledge principle),這個原則希望不要讓太多的類耦合在一起,對用戶來說只和一個外觀類打交道了,達到客戶和一群子系統的解耦。

例題:搭建一個家庭影院系統
系統內包含設備:DVD播放器、投影機、自動屏幕、立體聲音響、爆米花機........
來看看這些組件的組成:
圖片說明

當你把所有設備布置好后,準備看電源時...你忘了你必須要先一個個啟用這些設備...關閉時也還將要進行一遍反向操作(崩潰....)。
圖片說明
結果你發現要使用你的家庭影院是那么的麻煩!

因此,我們引入外觀模式,有了外觀模式,通過實現一個更合理的接口的外觀類,你可以將一個復雜的子系統變的容易使用。看看改變之后的類圖:

圖片說明

定義這些媒體類:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

class Amplifier{

????public void on(){

????????System.out.println("歡迎使用功放類。。");

????}

????public void off(){

????????System.out.println("已經關閉功放。。");

????}

????public void setCD(){

????????System.out.println("正在安放CD。。。");

????}

????public void setDVD(){

????????System.out.println("正在安放DVD、。。");

????}

????public void setStereoSound(){

????????System.out.println("設置立體聲。。");

????}

????public void setSurroundSound(){

????????System.out.println("設置環繞立體聲。。");

????}

????public void setTime(){

????????System.out.println("正在設置時間。。");

????}

????public void setVolume(){

????????System.out.println("正在設置音量。。");

????}

}

//定義Tuner類

class Tuner{

????public void on(){

????????System.out.println("正在 打開調諧器。。");

????}

????public void off(){

????????System.out.println("正在關閉調諧器。。");

????}

????public void setAM(){

????????System.out.println("正在設置am。。");

????}

????public void setFM(){

????????System.out.println("正在設置頻道。。");

????}

????public void setFrequency(){

????????System.out.println("正咋設置頻道。。");

????}

}

//定義DVD播放器類

class DVDPlayer{

????public void on(){

????????System.out.println("正在打開DVD。。");

????}

????public void off(){

????????System.out.println("正在關閉DVD。。");

????}

????public void pause(){

????????System.out.println("已經暫停DVD播放。。");

????}

????public void play(){

????????System.out.println("正在播放DVD。。");

????}

????public void setTwoChannelAudio(){

????????System.out.println("正在設置雙頻道。。");

????}

????public void setSurroundAudio(){

????????System.out.println("正在設置環繞立體聲。。");

????}

}

//定義CD播放器

class CDPlayer{

????public void on(){

????????System.out.println("正在打開CD");

????}

????public void off(){

????????System.out.println("正在關閉CD");

????}

????public void eject(){

????????System.out.println("彈出CD播放器!");

????}

????public void pause(){????

????}

????public void play(){

????}

????public String toString(){

????????return "hello panda";

????}

}

//定義投影儀

class Projector{

????public void on(){

????????System.out.println("正在打開投影儀。。");

????}

????public void off(){

????????System.out.println("正在關閉投影儀。。");

????}

????public void setTVMode(){

????????System.out.println("正在設置tv模式。。");

????}

????public void setWideScreenMode(){

????????System.out.println("正在設置寬屏模式。。");

????}

}

//定義屏幕

class Screen{

????public void up(){

????????System.out.println("正在生起屏幕。。");

????}

????public void down(){

????????System.out.println("正在放下屏幕。。");

????}

}

//定義爆米花機

class PopcornPopper{

????public void on(){

????????System.out.println("正在打開爆米花機。。");

????}

????public void off(){

????????System.out.println("正在關閉爆米花機。。");

????}

????public? void pop(){

????????System.out.println("正在蹦爆米花。。");

????}

}

//定義影院燈光

class TheaterLights{

????public void on(){

????????System.out.println("正在打開燈光。。");

????}

????public void off(){

????????System.out.println("正在關閉燈光。。");

????}

????public void dim(){

????????System.out.println("正在調暗燈光。。");

????}

?

}

多媒體的具體類準備好就可以定義外觀模式類了。

外觀模式類:倆方法,一個看電影-打開一系列設備,一個電影結束-關閉一系列設備

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

//定義外觀模式家庭影院

class HomeTheaterFacade{

????Amplifier amp;

????Tuner tuner ;

????DVDPlayer dvd;

????CDPlayer cd;

????Projector project;

????TheaterLights light;

????Screen screen;

????PopcornPopper pop;

????//構造的時候拿到這些對象

????public HomeTheaterFacade(Amplifier amp,Tuner tuner,DVDPlayer dvd,?

????????????CDPlayer cd ,Projector project,TheaterLights light,Screen screen

????????????,PopcornPopper pop){

????????this.amp = amp;

????????this.tuner = tuner;

????????this.dvd = dvd;

????????this.cd = cd;

????????this.project = project;

????????this.light? = light;

????????this.screen = screen;

????????this.pop = pop;

????}

????//看電影? 放一個方法里來執行一系列動作

????public void watchMovie(String movie){

????????System.out.println("get ready to watch a movie..");

????????pop.on();//首先打開爆米花機

????????pop.pop();//然后蹦爆米花

????????light.dim();//把燈光調暗

????????screen.down();//投影儀放下來

????????project.on();

????????project.setWideScreenMode();

????????amp.on();

????????amp.setDVD();

????????amp.setSurroundSound();

????????amp.setVolume();

????????dvd.on();

????????dvd.play();

????}

????//電影結束

????public void endMovie(String movie){

????????System.out.println("shutting movie theater down..");

????????pop.off();

????????light.on();

????????screen.up();

????????project.off();

????????amp.off();

???????dvd.off();

????}

}

測試

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

public class FacadePattern {

????public static void main(String args[]){

????????Amplifier amp =?new Amplifier();

????????Tuner tuner =?new Tuner() ;

????????DVDPlayer dvd =?new DVDPlayer();

????????CDPlayer cd =?new CDPlayer();

????????Projector project =?new Projector();;

????????TheaterLights light =?new TheaterLights();

????????Screen screen =?new Screen();

????????PopcornPopper pop =?new PopcornPopper();??????????????????

????????HomeTheaterFacade facade =?new HomeTheaterFacade(amp,tuner,dvd,cd,project,light,screen,pop);

????????facade.watchMovie("movie");

????????facade.endMovie("movie");

????}

?

}

通過上面的代碼可以看出,每個類對象都要執行一些方法,如果直接new這些類創建對象去調方法會與這些類產生耦合,這時單獨再寫一個外觀類,構造初始化時拿到這些類對象,在一個方法里去調這些類對象的方法,這樣對客戶來說只和一個類打交道,與子系統的一堆類解耦了。此類原則即為:最少知識原則

最少知識原則:只和你的密友談話。

客戶端只和外觀談話,不和子設備,如DVD播放機、投影儀等談話,降低了客戶端和設備的耦合度。

下面給出最少知識原則的指導思想:

就任何對象而言,在該對象的方法內,我們只應該調用屬于以下范圍的方法:

1.該對象本身

2.被當做方法的參數而傳過來的對象

3.該方法所創建或實例化的任何對象

4.對象的任何組件

盡可能自己封裝子設備的方法,以便減少客戶端和子設備的耦合。

外觀模式優點

松耦合:使客戶端與子系統之間解耦,讓子系統內部的模塊功能更容易擴展與維護。

簡單易用:客戶端無需了解子系統的內部實現及內部構成,只需要與外觀類交互即可。

更好地劃分訪問層次:部分方法對外,部分方法對內交互使用。子系統將暴露在外的功能集中到外觀類中可以隱藏子系統內部細節。

適配器、裝飾者、外觀模式對比

  • 在介紹裝飾者模式時,引出了一個開閉原則,即對修改關閉,對擴展開放。裝飾者模式主要強調的是在不改變原有類的基礎上,添加新功能。
  • 適配器模式主要是對適配對象進行調整,以便適合消費者的需求。從而達到消費者和被適配者解耦的目的。
  • 外觀模式的特點主要是簡化接口,以及減少客戶端對外觀組件的耦合。因為如果客戶端變化來,組件的子系統變化了,不用影響客戶端。除此之外,在封裝組件時,適當的在外觀類中添加一些自己想要的規則。如上面例子中各設備的開關順序

總結

  • 當需要使用一個現有的類,而其接口并不符合你的需要時,就使用適配器。
  • 當需要簡化并統一一個很大的接口或者一群復雜的接口時,使用外觀。
  • 適配器改變接口以符合客戶的希望
  • 外觀將一個客戶從復雜的子系統中解耦。
  • 實現一個適配器可能需要一番功夫,也可能不費工夫,視目標接口的大小與復雜度而定。
  • 實現一個外觀,需要將子系統組合進外觀中,然后將工作委托給子系統執行。
  • 適配器模式有兩種形式,對象適配器和類適配器。類適配器需要用到多重繼承。
  • 你可以為一個子系統實現一個以上的外觀。
  • 適配器將一個對象包裝起來以改變其接口;裝飾器將一個對象包裝起來以增加新的行為和責任,而外觀將一群對象包裝起來以簡化其接口。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/445420.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/445420.shtml
英文地址,請注明出處:http://en.pswp.cn/news/445420.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

《Head First設計模式》第八章筆記-模板方法模式

模板方法模式 之前所學習的模式都是圍繞著封裝進行,如對象創建、方法調用、復雜接口的封裝等,這次的模板方法模式將深入封裝算法塊,好讓子類可以在任何時候都將自己掛接進運算里。 模板方法定義:模板方法模式在一個方法中定義一…

機器學習基礎-吳恩達-coursera-(第一周學習筆記)----Introduction and Linear Regression

課程網址:https://www.coursera.org/learn/machine-learning Week 1 —— Introduction and Linear Regression 目錄 Week 1 Introduction and Linear Regression目錄一 介紹1-1 機器學習概念及應用1-2 機器學習分類 二 單變量的線性回歸2-1 假設函數hypothesis2…

常見8種機器學習算法總結

簡介 機器學習算法太多了,分類、回歸、聚類、推薦、圖像識別領域等等,要想找到一個合適算法真的不容易,所以在實際應用中,我們一般都是采用啟發式學習方式來實驗。通常最開始我們都會選擇大家普遍認同的算法,諸如SVM&a…

redis——數據結構(字典、鏈表、字符串)

1 字符串 redis并未使用傳統的c語言字符串表示,它自己構建了一種簡單的動態字符串抽象類型。 在redis里,c語言字符串只會作為字符串字面量出現,用在無需修改的地方。 當需要一個可以被修改的字符串時,redis就會使用自己實現的S…

Hotspot虛擬機的對象

創建 Step1:類加載檢查 虛擬機遇到一條 new 指令時,首先將去檢查這個指令的參數是否能在常量池中定位到這個類的符號引用,并且檢查這個符號引用代表的類是否已被加載過、解析和初始化過。如果沒有,那必須先執行相應的類加載過程。 Step2:分…

劍指offer(刷題1-10)--c++,Python版本

文章目錄目錄第一題:解題思路:代碼實現:c順序查找二分查找Python第二題:解題思路:代碼實現:cpython第三題:解題思路:代碼實現:c使用棧輔助反轉鏈表python第四題&#xff…

redis——數據結構(整數集合,壓縮列表)

4、整數集合 整數集合(intset)是 Redis 用于保存整數值的集合抽象數據結構, 可以保存 int16_t 、 int32_t 、 int64_t 的整數值, 并且保證集合中不會出現重復元素。 實現較為簡單: typedef struct intset {// 編碼方…

原 劍指offer(刷題11-20)--c++,Python版本

文章目錄目錄第11題:解題思路:代碼實現:cpython第12題:解題思路:代碼實現:cpython第13 題:解題思路:代碼實現:cpython第 14題:解題思路:代碼實現&…

LRU介紹和實現

LRU全稱是Least Recently Used,即最近最久未使用的意思。 LRU算法的設計原則是:如果一個數據在最近一段時間沒有被訪問到,那么在將來它被訪問的可能性也很小。也就是說,當限定的空間已存滿數據時,應當把最久沒有被訪問…

機器學習知識總結系列- 知識圖譜(0-0)

文章目錄目錄機器學習知識圖譜目錄 本系列的文章只是根據個人的習慣進行總結,可能結構與一些書籍上不太一樣,開始的內容比較簡單,會隨著后續的深入,不斷豐富和更新圖譜,同時也期待有相同興趣的朋友一起給我留言一起豐富…

跳表介紹和實現

想慢慢的給大家自然的引入跳表。 想想,我們 1)在有序數列里搜索一個數 2)或者把一個數插入到正確的位置 都怎么做? 很簡單吧 對于第一個操作,我們可以一個一個比較,在數組中我們可以二分,這…

機器學習知識總結系列- 基本概念(1-0)

文章目錄目錄1. 機器學習的定義2. 機器學習的分類2.1根據是否在人類監督下進行訓練監督學習非監督學習半監督學習強化學習2.2根據是否可以動態漸進的學習在線學習批量學習2.3根據是否在訓練數據過程中進行模式識別實例學習基于模型的學習3. 機器學習中的一些常見名詞4. 機器學習…

劍指offer(刷題21-30)--c++,Python版本

文章目錄目錄第 21題:解題思路:代碼實現:cpython第22 題:解題思路:代碼實現:cpython第23 題:解題思路:代碼實現:cpython第24 題:解題思路:代碼實現…

redis——對象

剛寫了redis主要的數據結構: 動態字符串、雙端鏈表、字典、壓縮列表、整數集合、跳表等 redis肯定不能直接使用這些數據結構來實現數據庫,它用這些數據庫建立了一個對象系統,包含: 字符串對象、列表對象、哈希對象、集合對象、…

劍指offer(刷題31-40)--c++,Python版本

文章目錄目錄第31 題:解題思路:代碼實現:cpython第32題:解題思路:代碼實現:cpython第33題:解題思路:代碼實現:cpython第34題:解題思路:代碼實現&a…

redis——數據庫

redis服務器將所有數據庫都保存在redis/redisServer中,數組db存放所有數據庫,每一項是一個redisdb結構。dbnum代表數據庫數量。 客戶端有一個指針指向當前數據庫,可以切換,也就是移動指針。 鍵空間 現在稍微介紹一下redisdb結構…

劍指offer(刷題41-50)--c++,Python版本

文章目錄目錄第41題:解題思路:代碼實現:cpython第42題:解題思路:代碼實現:cpython第43題:解題思路:代碼實現:cpython第44題:解題思路:代碼實現&am…

redis——持久化

因為redis是內存數據庫,他把數據都存在內存里,所以要想辦法實現持久化功能。 RDB RDB持久化可以手動執行,也可以配置定期執行,可以把某個時間的數據狀態保存到RDB文件中,反之,我們可以用RDB文件還原數據庫…

redis原理總結

數據結構(字典、鏈表、字符串) 數據結構(整數集合,壓縮列表) 數據結構(跳表介紹和手撕) LRU介紹和實現 對象(字符串對象、列表對象、哈希對象、集合對象、有序集合總結&#xff…

劍指offer(刷題51-60)--c++,Python版本

文章目錄目錄第51題:解題思路:代碼實現:cpython第52題:解題思路:代碼實現:cpython第53題:解題思路:代碼實現:cpython第54題:解題思路:代碼實現&am…