適配器和外觀模式
一、 基本概述
1:現實中存在三角插頭適配成雙插頭,等其他各種形式的適配器來連接不兼容的兩個物體。同理在代碼中也存在適配器模式來兼容兩個不同的代碼接口。
2:KTV包間打開一個啟動開關,就打開party模式(音響、屏幕、燈光、換氣、點歌臺等),一個簡單的開關來控制其他更多的任務。同理在代碼中也存在外觀模式來簡化子系統(更多任務)的功能。
二、詳細說明
1.適配器模式:將一個類的接口、轉換成客戶期望的另一個接口,適配器讓原本接口不兼容的類可以合作無間。
這個適配器模式充滿良好的OO設計原則,使用對象組合,以修改的接口包裝被適配者,這種做法還有額外的優點,那就是被適配者的任何子類,都可以搭配著適配器使用。
問:一個適配器需要做多少“適配”工作?如果我需要實現一個很大的目標接口,似乎有“很多”工作要做。
答:實現一個適配器所需要進行的工作,的確和目標接口的大小成正比。如果不用適配器,你就必須改寫客戶端的代碼來調用這個新的接口,將會花許多力氣來做大量的調查工作和代碼改寫工作。相比之下,提供一個適配器類,將所有的改變封裝在一個類中,是比較好的做法。
問:一個適配器只能夠封裝一個類嗎?
答:適配器模式的工作是將一個接口轉換成另一個。雖然大多數的適配器模式所采取的例子都是讓一個適配器包裝一個適配器者,但我們都知道這個世界其實復雜多了,所以你可能絮叨一些狀況,需要讓一個適配器包裝多個被被適配者。這涉及另一個模式,被稱為外觀模式,人們常常將外觀模式和適配器模式混為一談。
問:萬一我的系統中新舊並存,舊的部分期望舊的廠商接口,但我們卻已經使用新廠商的接口編寫了這一部分。
答:可以創建一個雙向的適配器,支持兩邊的接口。想創建一個雙向的適配器,就必須實現所涉及的兩個接口,這樣這個適配器可以當做舊的接口、新的接口使用。
其實適配器模式有兩種形式,對象適配器(在上面進行了說明)、和類適配器,類適配器需要用到多重繼承(在C#中是不可能的)。
2.外觀模式:提供了一個統一的接口,用來訪問子系統中的一群接口,外觀定義了一個高層接口,讓子系統更容易使用。
下圖為外觀模式的示意圖
假如有一個家庭影院,在我們觀賞電影時,必須先執行一些任務(普通方式)。
(1) 打開爆米花機
(2) 開始爆米花機
(3) 將燈光調暗
(4) 放下屏幕
(5) 打開投影機
(6) ...
(7) 省略更多步驟
(8) 開始播放
面對以上中情況,可以使用外觀來簡化接口。如下圖
問:如果外觀封裝了子系統的類,那麽需要底層功能的客戶如何接觸這些了類?
答:外觀沒有“封裝”子系統的類,外觀只提供簡化的接口。所以客戶如果覺得有必要,依然可以直接使用子系統的類。這是外觀模式一個很好的特征,提供簡化的接口的同時,依然將系統完整的功能暴露出來,以供需要的人使用。
問:每個子系統只能有一個外觀嗎?
答:不,你可以為一個子系統創建許多個外觀。
問:可不可以這樣說,適配器模式和外觀模式之間的差異在於,適配器包裝一個類,而外觀可以代表許多類?
答:不對!適配器模式將一個或多個類接口變成客戶所期望的一個接口。雖然大多數教科書所采用的例子中適配器只適配一個類,但是你可以適配許多類來提供一個接口讓客戶編碼。類似的,一個外觀也可以只針對一個擁有復雜接口的類提供簡化的接口。兩種模式的差異,不在於它們“包裝”了幾個類,而是在於它們的意圖。(可在總結中查看)
3.設計原則:最少知識原則:只和你的密友談話。
最少知識原則告訴我們要減少對象之間的交互,只留下幾個“密友”。這個原則希望我們在設計中,不要讓太多的類耦合在一起,免得修改系統中一部分,會影響到其他部分。如果許多類之間相互依賴,那麽這個系統就會變成一個易碎的系統,它需要花許多成本維護,也會因為太復雜而不容易被其他人了解。
如何不要贏得太多的朋友和影響太多的對象。
這個原則提供了一些方針:就任何對象而言,在該對象的方法內,我們只應該調用屬於以下範圍的方法:
(1) 該對象本身;
(2) 被當做方法的參數而傳遞進來的對象;
(3) 此方法所創建或實例化的任何對象;
(4) 對象的任何組件(把組件想象成是被實例化的變量所引用的任何對象);
問:還有另一個原則,叫做得墨忒耳法則(Law of Demeter),它和最少知識原則有什麽關系?
答:其實兩個名詞指的是同一個原則,我們傾向於使用最少知識原則來稱呼它是因為以下兩個原因。
(1) 這個名字更直接。
(2) 法則(Law)給人的感覺是強制的。事實上,沒有任何原則是法律,所有的原則都應該在有幫助的時候才遵守。
所有的設計都不免需要折衷(在抽象和速度之間取舍,在空間和時間之間平衡...)。雖然原則提供了方針,但在采用之前,必須全盤考慮所有的因素。
問:采用最少知識原則有什麽缺點嗎?
答:雖然這個原則減少了對象之間的依賴,研究顯示這會減少軟件的維護成本;但是采用這個原則也會導致更多的“包裝”類被制造出來,以處理和其他組件的溝通,這可能會導致復雜度和開發時間的增加,並降低運行時的性能。
4.總結:
對比三種模式的意圖:
裝飾者:不改變接口,但加入責任(將一個對象包裝起來以增加新的行為和責任)。
適配器:將一個接口轉成另一個接口(將一個對象包裝起來以改變其接口)。
外觀:讓接口更簡單(將一群對象“包裝”起來以簡化其接口)。
三、代碼列表
//適配器模式相關測試類 public interface Iterator<T> { bool HasNext(); T Next(); void Remove(); } public class EnumerationIterator<T> : Iterator<T> { private IEnumerator<T> enumerator; public EnumerationIterator(IEnumerable<T> enumerable) { this.enumerator = enumerable.GetEnumerator(); } public bool HasNext() { return enumerator.MoveNext(); } public T Next() { return enumerator.Current; } public void Remove() { throw new NotSupportedException(); } } //外觀模式的相關測試類 public class Amplifier { public void On() { Console.WriteLine("Top-0-Line Amplifier on"); } public void SetDvd(DvdPlayer dvd) { Console.WriteLine("Top-0-Line Amplifier setting DVD player to Top-0-Line DVD Player"); } public void SetVolume(int i) { Console.WriteLine("Top-0-Line Amplifier surround sound on (5 seakers, 1 subwoofer)"); Console.WriteLine("Top-0-Line Amplifier setting volume to 5"); } public void Off() { Console.WriteLine("Top-0-Line Ampliier off"); } } public class CdPlayer { } public class Doors { public void Lock() { } } public class DvdPlayer { private string movie; public void On() { Console.WriteLine("Top-0-Line DVD Player on"); } public void Play(string movie) { this.movie = movie; Console.WriteLine("Top-0-Line DVD Player playing \"{0}\"", movie); } public void Stop() { Console.WriteLine("Top-0-Line DVD Player stopped \"{0}\"", movie); } public void Eject() { Console.WriteLine("Top-0-Line DVD Player eject"); } public void Off() { Console.WriteLine("Top-0-Line DVD Player off"); } } public class Engine { public void Start() { } } public class PopcornPopper { public void On() { Console.WriteLine("Popcorn Popper on"); } public void Pop() { Console.WriteLine("Popcorn popper popping popcorn!"); } public void Off() { Console.WriteLine("Popcorn Popper off"); } } public class Projector { public void On() { Console.WriteLine("Top-0-Line Projector on"); } public void WideScreenMode() { Console.WriteLine("Top-0-Line Projector in widescreen mode (19*9 aspect ratio)"); } public void Off() { Console.WriteLine("Top-0-Line Projector off"); } } public class Screen { public void Down() { Console.WriteLine("Theater Screen going down"); } public void Up() { Console.WriteLine("Theater Screen going up"); } } public class TheaterLights { public void Dim(int i) { Console.WriteLine("Theater Ceiling Lights dimming to {0}%", i); } public void On() { Console.WriteLine("Theater Ceiling Lights on"); } } public class Tuner { } public class HomeTheaterFacade { private Amplifier amp; private Tuner tuner; private DvdPlayer dvd; private CdPlayer cd; private Projector projector; private TheaterLights lights; private Screen screen; private PopcornPopper popper; public HomeTheaterFacade(Amplifier amp, Tuner tuner, DvdPlayer dvd, CdPlayer cd, Projector projector, TheaterLights lights, Screen screen, PopcornPopper popper) { this.amp = amp; this.tuner = tuner; this.dvd = dvd; this.cd = cd; this.projector = projector; this.lights = lights; this.screen = screen; this.popper = popper; } public void WatchMovie(string movie) { Console.WriteLine("Get ready to watch a movie..."); popper.On(); popper.Pop(); lights.Dim(10); screen.Down(); projector.On(); projector.WideScreenMode(); amp.On(); amp.SetDvd(dvd); amp.SetVolume(5); dvd.On(); dvd.Play(movie); } public void EndMovie() { Console.WriteLine("Shutting movie theathe down..."); popper.Off(); lights.On(); screen.Up(); projector.Off(); amp.Off(); dvd.Stop(); dvd.Eject(); dvd.Off(); } } //執行測試 [Test] public void HomeTheaterTestDrive() { Amplifier amp = new Amplifier(); Tuner tuner = new Tuner(); DvdPlayer dvd = new DvdPlayer(); CdPlayer cd = new CdPlayer(); Projector projector = new Projector(); Screen screen = new Screen(); TheaterLights lights = new TheaterLights(); PopcornPopper popper = new PopcornPopper(); HomeTheaterFacade homeTheater = new HomeTheaterFacade(amp, tuner, dvd, cd, projector, lights, screen, popper); homeTheater.WatchMovie("Raiders of the lost ark"); homeTheater.EndMovie(); }View Code
------------------------以上內容根據《Head First Design Mode》進行整理
適配器和外觀模式