Design Patterns之Adapter Pattern總結
看了一兩節李建忠老師的WebCast《C#設計模式縱橫談》,最大的感覺就是他講得很通俗易懂,但每次看完后過段時間沒接觸又忘了,先前有看過了他講的Abstract Factory模式,雖然現在對其還有印象,但很模糊,這次去圖書館借了本Steven John Metsker的《C#設計模式》,打算按它的章節順序,再結合李建忠老師的設計模式系列課程來學習。我想有必要在每次學習之后作一個總結,于是,本文就總結一下今天看的Adapter模式吧。如果文中有什么地方總結的不好甚至寫錯了,還請各位多多指正。
以下示例代碼均基于李建忠老師設計模式課程上的示例程序。
首先,Adapter模式的用途是什么?假設我們有一些好不容易寫出來的很好很強大的類,而且它又能滿足當前項目的某種需求,那當然就最好把它應用在新項目中,而不用再去重新寫,以達到復用的目的,但它的接口卻往往跟當前環境不兼容,此時我們就可以考慮使用Adapter(適配器)模式。可以想象生活中的例子:手機,手機電池的電量用光了以后,我們就要對它充電,而市電就可以滿足這個需求,但有個問題,我們墻上的插口不是三孔的就是兩孔的,而手機的充電口只有一孔(我的是一個小圓孔),另外市電是220V的,手機不能直接接受這么高的電壓,那怎么辦呢?有什么辦法可以既發揮市電的充電功能,又能解決接口的問題呢?那就是手機充電器,它就是一個適配器,通過它,就能讓市電給手機充電了。我們的Adapter(適配器)模式也是這個道理,通過一個適配器(類)讓我們已經存在的代碼為我們的新環境服務。
接下來看例子:我們要利用.Net Framework中的System.Collections.ArrayList(已經存在的代碼)來實現一個棧(新環境),堆棧是一個FILO(First In Last Out)的數據結構,我們來實現一個簡單的版本,它有三個方法:Push(object o),把o壓入棧頂;Pop(),彈出棧頂元素;Peek(),得到棧頂元素的值,但不彈出,我們期望的棧是這樣子的:
/// <summary>
/// 新環境中期望的接口
/// </summary>
public interface IStack {
void Push(object o);
object Pop();
object Peek();
}
而在ArrayList類中是沒有什么Push,Pop方法的,所以我們用一個適配器,讓ArrayList為我們的棧服務:
///<summary>
/// 類適配器
/// </summary>
public class ClassAdapter : ArrayList, IStack {
public void Push(object o) {
this.Add(o);
}
public object Pop() {
object o = this[this.Count - 1];
this.RemoveAt(this.Count - 1);
return o;
}
public object Peek() {
return this[this.Count - 1];
}
}
OK,這個ClassAdapter不就已經是一個可以用的棧了么,我們可以在main方法中測試:
public static void
Console.WriteLine("Class Adapter Test...");
ClassAdapter ca = new ClassAdapter();
ca.Push("Lin");
ca.Push("Mou");
ca.Push("Hong");
//ca.Remove(“Mou”);
//Console.WriteLine(ca[0]);
Console.WriteLine(ca.Peek());
Console.WriteLine(ca.Pop());
}
棧的作用已經達到了。這個適配器叫類適配器,它是通過繼承已存在的代碼(ArrayList類),并實現新環境期望的接口(IStack)來達到適配器的目的。
但是,這里存在問題:就是上面Main中注釋掉的那兩行,我們要實現的是一個棧,但是ca.Remove(“Mou”)卻可以把不位于棧頂的元素給移掉,ca[0]又可以取到棧底的元素值,這還叫什么棧啊?另外,如果我們要使用的已存在的代碼不只一個,比如有兩個,那我們要讓ClassAdapter去繼承這兩個類嗎?顯然不行,C#是不允許多繼承的。
于是我們來看下面的適配器:對象適配器。
新環境中期望得到的接口仍然是IStack,但是我們的適配器是用ObjectAdapter:
///<summary>
/// 對象適配器
/// </summary>
public class ObjectAdapter : IStack {
private ArrayList adaptee; //被適配的對象
public ObjectAdapter() {
adaptee = new ArrayList();
}
public void Push(object o) {
adaptee.Add(o);
}
public object Pop() {
object o = adaptee[adaptee.Count - 1];
adaptee.RemoveAt(adaptee.Count - 1);
return o;
}
public object Peek() {
return adaptee[adaptee.Count - 1];
}
}
可以看到,對象適配器把被適配的對象(已存在的代碼)作為自己的一個字段來用,然后再實現新環境期望的接口IStack,這個對象適配器可以解決前面那個類適配器ClassAdapter留下的兩個問題:
(1)當我們實例化ObjectAdapter后,能調用的方法僅僅只是Push(object o),Pop(),Peek(),再也沒辦法去調用什么Remove()之類的方法;
(2)如果我們要使用的已有代碼不只兩個類,比如還有一個ClassTwo類,那我們只需要在ObjectAdapter中加一個ClassTwo類型的字段就可以了。
于是,我們得到了這個結論:盡量少用類適配器,而用對象適配器。
總 結:
(1) 適配器是為了解決:想重用已有代碼,但這些代碼跟當前環境接口不兼容的情況;
(2) 適配器有類適配器和對象適配器,類適配器是通過繼承要重用的已有類,并實現新環境期望的接口來實現的;而對象適配器是通過組合對象的方式來實現的,把要重用的類實例組裝在適配器中作為適配器的字段,然后適配器再去實現新環境期望的接口。
(3) 不推薦使用類適配器(但不代表不能用),而推薦使用對象適配器。
浙公網安備 33010602011771號