<script type="text/javascript"> </script> <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>
适配器模式就是将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
不知道大家在日常编码中是否用过适配器模式,但适配器的工作模式在生活中却非常常见。本文还是用那个最老套却最有代表性的例子来讲解。现在有一种 价值1000元、在3孔插座下工作的电器,但用户只有一个2孔的插座,显然这个电器没法用了。用代码模拟这个过程。
- 模拟电器:
- public class 电器
- {
- public void 在三孔插座下播放(string 火线,string 地线,string 零线)
- {
- Console.Write("正常播放音乐……/n/n");
- }
- }
- 用户期待的接口:
- public interface 用户期待接口
- {
- void 在两孔插座下播放(string 火线, string 地线);
- }
- public class 适配后的电器 : 电器,用户期待接口
- {
- public void 在两孔插座下工作(string 火线, string 地线)
- {
- string 零线 = "";
- this.在三孔插座下工作(火线, 地线, 零线);
- }
- }
- static void Main(string[] args)
- {
- 用户期待接口 收音机 = new 适配后的电器();
- 收音机.在两孔插座下播放("火线", "地线");
- Console.Read();
- }
UML图与上面的电器代码中的角色对应关系如下:
Taget =〉用户期待接口
Adapter =〉适配后的电器
Adaptee =〉电器
注意,这里用的是“适配后的电器”,而不是像很多教程中简单的用“适配器”,因为从上周五的讨论中发现,很多同学在没有提前预习的情况下将 “Adapter”等价于日常生活中的“变压器”或“插头转换器”,认为它只应该具有电压或插头转化功能,而不应该具有播放功能,单词的直译很容易给 没有学过“适配器模式”的同学一些误导,GOF23更多的从他的转换原理类似适配器的工作原理,而事实上并没有转换器这个对象存在,还是从它的工作意 图并结合类图上来加深理解。
好了下面重新梳理一下关于电器问题的整个解决过程,首先手边有一个现成的电器,但他的工作方式和用户期待的不一样,不能随意改造这个电器,为 了最大限度利用手边的资源,只有转换它的工作方式,使其和用户期待的一致。代码中就是通过适配器模式复用电器类,使原本不能工作的电器可以在用户 期待的环境下工作。
现在在深入下去,已经满足了用户的要求,但在看看代码,按照上面的继承关系,“适配后的电器”除了实现“用户期待接口”外还具有“电器 ”的全部功能,这显然违反了“单一原则”,按照以前讲的“7个原则”,应该使用“组合”而不是“继承”来降低对象之间的耦合。事实上,在GOF23中针对该模式提到了两种结构,一种是“类适配器”,就是上面讲的结构,还有一种是“对象适配器”。“对象适配器”的结构图如下:
<script type="text/javascript"> </script> <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>
“对象适配器”通过组合除了满足“用户期待接口”还降低了代码间的不良耦合。在工作中推荐使用“对象适配”。重新修改一下“适配后的电器”类:
- public class 适配后的电器 : 用户期待接口
- {
- 电器 收音机 = new 电器();
- public void 在两孔插座下播放(string 火线, string 地线)
- {
- string 零线 = "零线";
- 收音机.在三孔插座下播放(火线, 地线, 零线);
- }
- }
- public interface 用户期待接口
- {
- void 在两孔插座下播放(电器 三孔电器,string 火线, string 地线);
- }
- public class 适配后的电器 : 用户期待接口
- {
- public void 在两孔插座下播放(电器 三孔电器,string 火线, string 地线)
- {
- string 零线 = "";
- 三孔电器.在三孔插座下播放(火线, 地线, 零线);
- }
- }
- static void Main(string[] args)
- {
- 电器 收音机 = new 电器();
- 适配后的电器 新电器 = new 适配后的电器();
- 新电器.在两孔插座下播放(收音机, "火线", "地线");
- Console.Read();
- }
工作原理很简单,大家自己仔细看看。
有很多朋友认为适配器和代理、装饰都是通过组合一个现存对象,通过调用该对象的方法来实现自己的功能,说他们之间很像,其实结构型模式都是继承和组合的方式来实现新的功能,如果单看实现过程,他们的确都很像,但如果从意图上分析,他们的区别就大了。
代理模式着重将复杂部分抽到中间层,通过这个中间层(代理层)来控制对目标对象的访问,要求代理层和目标对象的接口相同。而适配器模式解决的恰恰是接口发生了变化导致现有对象不能工作的情景,通过组合这个现有对象,将现有接口转化为目标接口的。
装 饰模式强调的是通过组合来动态扩展对象功能。比如上述的电器收音机,本身具有播放功能,现在有很多种录音机(日语录音机、中文录音机),在这个收音机上组 装一种录音机,使其除了播放功能外还具有录音功能,而且组装不同的录音机,将具有不同语言的录音功能。这种需求就是装饰模式的应用场景。它和适配器模式在 意图上有明显的不同。
如果仅仅讲“如何实现适配器”那很简单,但要解释“为什么用、什么时候用适配器模式”就难得多,上面是在现在这个水平对 “适配器模式”的理解,相信对设计模式每个人因为工作经验的不同会有不同的认识,欢迎大家讨论.