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) 不推荐使用类适配器(但不代表不能用),而推荐使用对象适配器。