适配器和外观模式
一、 基本概述
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》进行整理