为什么要把适配器模式和外观模式放在同一篇文章中,主要是其相对前面的几个模式来讲会简单些并且具有相似之处。下面就分别通过例子来看理解一下两种模式,然后再进行对其进行比较。
一、适配器模式
1.1适配器模式的定义
适配器模式定义:将一个类的接口,转化成客户期望的另一个接口,适配器让原本接口不兼容的类可以合作无间。
感觉是从定义就可以看出来怎么实现的设计模式。简单的来说就是把不符合要求的类,通过实现期望的接口来来达到以假乱真的效果。好了,废话不多说,还是通过例子来说明。
1.2适配器的例子
最常见的例子是三孔插座和两孔插座,如果墙上有一个三孔插座,但是我们的充电器又只能使用两孔的插座,那么我们通常是接一条带有两孔和三孔的插座其插头是三个的,以适应不同的需求。在以上的例子中,我们来简单分析一下适配器模式中的对象有哪些:
适配器模式的对象:
1.请求对象(手机)
2.适配器对象(带有两孔和三孔的插座)
3.需要适配的对象(三孔插座)
4.请求对象所需要的接口。(插座要有两孔)
接来下就使用代码实现一下:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Mobile mobile = new Mobile(); 6 ThreeHole threeHole = new ThreeHole(); 7 LineWithTwoHole lineWithTwoHole = new LineWithTwoHole(threeHole); 8 mobile.Charge(lineWithTwoHole); 9 Console.ReadKey(); 10 } 11 } 12 13 /// <summary> 14 /// 手机类 15 /// </summary> 16 public class Mobile 17 { 18 public void Charge(ITwoHole twoHole) 19 { 20 twoHole.Connect(); 21 AddPower(); 22 } 23 public void AddPower() 24 { 25 Console.WriteLine("电量增加中。。。。"); 26 } 27 } 28 29 /// <summary> 30 /// 两孔插座接口 31 /// </summary> 32 public interface ITwoHole 33 { 34 void Connect(); 35 } 36 37 /// <summary> 38 /// 三孔插座 39 /// </summary> 40 public class ThreeHole 41 { 42 public void Connect() 43 { 44 LeftConnect(); 45 RightConnect(); 46 ExtraConnect(); 47 } 48 49 public void LeftConnect() 50 { 51 Console.WriteLine("零线接通中。。。"); 52 } 53 public void RightConnect() 54 { 55 Console.WriteLine("火线接通中。。。。。"); 56 } 57 public void ExtraConnect() 58 { 59 Console.WriteLine("底线接通中。。。。"); 60 } 61 } 62 63 public class LineWithTwoHole:ITwoHole 64 { 65 private ThreeHole threeHole; 66 public LineWithTwoHole(ThreeHole threeHole) 67 { 68 this.threeHole = threeHole; 69 } 70 public void Connect() 71 { 72 threeHole.LeftConnect(); 73 threeHole.RightConnect(); 74 } 75 }
运行结果:
接下来看看适配器模式的类图:
1.3适配器模式类图
适配器模式的关系:请求对象引用需要适配的接口,适配器引用需要适配对象,适配器需要通过被适配对象来实现需要适配的接口。
1.4适配器模式需要注意的问题
在三孔插座转化成两孔插座时,工作量很小,是只调用了三孔插座的左孔和右孔。但是也有可能是需要很大的目标接口,就会有很多工作要做。也即实现适配器所需要的工作和目标接口的大小成正比。
一个适配器真的只需要一个被适配者吗?
其实适配的目标是一个完成接口,在需要比较大型的接口时,可能需要多个被适配者才能实现。
二、外观模式
2.1外观模式的定义
外观模式定义:外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
外观模式让我想起了一套设备,不知道大家有没有照过大头贴,我是没有照过,但是我见过照大头贴的设备。其是由电脑,打印机,白炽灯,相机组成,基本的操作是:打开电脑,打印机,白炽灯,相机设备,然后按一下拍照开关,接着点击打印,照片就出来了,最终关闭所有装置。
各个设备要去单独的打开,拍完照,又要单独去关闭,显然不是很方便。其实如果有一个开关能控制所有设备的开关,那么就能省下好多步骤,就可以大大的提高生产率了。
2.2外观模式例子
其实这个就和我们的外观模式相一致,我们可以定义为这套设备定义统一的接口,接口中的方法包括打开,拍照,打印,关闭,在使用外观模式之前,直接在客户端调用每个设备的开关,以及其他的操作,直接将客户端和各个设备耦合在了一起。由于代码比较简单,在此不作演示,下面就只演示使用外观模式的例子。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 6 ITakePicture takephoto = new TakePicture(); 7 takephoto.Open(); 8 takephoto.TakePictures(); 9 takephoto.Printing(); 10 takephoto.Close(); 11 Console.ReadKey(); 12 } 13 } 14 15 public class Computer 16 { 17 public void Open() 18 { 19 Console.WriteLine("电脑开了"); 20 } 21 public void Close() 22 { 23 Console.WriteLine("电脑已经关闭了"); 24 } 25 } 26 27 public class Light 28 { 29 public void Open() 30 { 31 Console.WriteLine("灯开了"); 32 } 33 public void Close() 34 { 35 Console.WriteLine("灯关闭了"); 36 } 37 } 38 39 public class Print 40 { 41 public void Open() 42 { 43 Console.WriteLine("打印机打开了"); 44 } 45 public void Printing() 46 { 47 Console.WriteLine("打印完成"); 48 } 49 public void Close() 50 { 51 Console.WriteLine("打印机已关闭"); 52 } 53 } 54 55 public class Cinema 56 { 57 public void Open() 58 { 59 Console.WriteLine("照相机打开了"); 60 } 61 public void Close() 62 { 63 Console.WriteLine("照相机已关闭"); 64 } 65 public void TakePictures() 66 { 67 Console.WriteLine("已经拍完了"); 68 } 69 } 70 71 public interface ITakePicture 72 { 73 void Open(); 74 void TakePictures(); 75 void Printing(); 76 void Close(); 77 } 78 79 public class TakePicture : ITakePicture 80 { 81 Computer computer = new Computer(); 82 Light light = new Light(); 83 Print print = new Print(); 84 Cinema cinema = new Cinema(); 85 public TakePicture() 86 { 87 88 } 89 public void Open() 90 { 91 light.Open(); 92 computer.Open(); 93 print.Open(); 94 cinema.Open(); 95 } 96 97 public void TakePictures() 98 { 99 cinema.TakePictures(); 100 } 101 102 public void Printing() 103 { 104 print.Printing(); 105 } 106 107 public void Close() 108 { 109 light.Close(); 110 computer.Close(); 111 print.Close(); 112 cinema.Close(); 113 } 114 }
可以看出外观模式的好处是:简化了设备的接口,同时降低了客户端和各个设备的耦合。例如,照相机的照相方法改成了TakePhoto(),那么也只需要在外观的实现方法中修改该设备的方法即可,不用去修改客户端代码。
2.3外观模式需要注意的地方
外观封装了子设备的类,那么客户端是如何调用子设备的方法的?
通过上面的代码可以看出,对于每一个单独的设备的方法的调用我们也是将设备的方法封装到了外观类中(如cinema的takepicture方法被封装在外观类的一个方法中),这里使用了一个原则:
最少知识原则:只和你的密友谈话。
客户端只和外观谈话,不和子设备,如相机,电脑等谈话,降低了客户端和设备的耦合度。
下面给出最少知识原则的指导思想:
就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:
1.该对象本身
2.被当做方法的参数而传过来的对象
3.该方法所创建或实例化的任何对象
4.对象的任何组件
尽管我们有时可以在客户端使用takephoto.print.Printing();但是最好也不要使用,因为客户端这样不仅耦合了TakePhoto类还耦合了Print。所以尽可能自己封装子设备的方法,以便减少客户端和子设备的耦合。
三、适配器、外观、装饰者模式对比
在介绍装饰者模式时,引出了一个开闭原则,即对修改关闭,对扩展开放。装饰者模式主要强调的是在不改变原有类的基础上,添加新功能。
适配器模式,主要是对适配对象进行调整,以便适合消费者的需求。从而达到消费者和被适配者解耦的目的。
外观模式的特点主要是简化接口,以及减少客户端对外观组件的耦合。因为如果客户端变化来,组件的子系统变化了,不用影响客户端。除此之外,在封装组件时,适当的在外观类中添加一些自己想要的规则。如上面例子中各设备的开关顺序,或者拍照和打印之前其设备是否开启等。
四、总结和源码
本文主要通过举例来介绍了适配器和外观模式,最后对比了适配器,外观,装饰者模式的区别。