我们继续透过三国那段历史,来讨论设计模式。
上一期我讲完了创建型模式,接下来我们讲下结构型模式:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
其中对象的适配器模式是各种模式的起源,我们看下面的图:
6、适配器模式(Adapter)
孙刘联合抗曹,但两军军制、口令都有差别,冒然合兵一处,刘备军听不懂大都督周瑜周公瑾的调度,因此经常进退失据,陷入混乱。
为求两军合力,副都督鲁肃建议请刘备军中的诸葛孔明出使东吴,两人一起组建一个联络司,常设联络司马,负责将吴国军令翻译为刘备军军令。
周瑜所发号令,涉及刘备军的,先经联络司,由联络司马翻译后,再统一调度刘备军。
适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。
类的适配器模式
类图:
类适配器:核心思想就是:有一个Source类,拥有一个方法,待适配,目标接口是Targetable,通过Adapter类,将Source的功能扩展到Targetable里。
联络司马继承自蜀军,同时也实现了吴军提供的接口,这样,吴军的实现类就具有了蜀国类的功能。
以下是主要的业务代码。
IDongWuArmy是东吴军队的接口类,包含执行命令的方法:
联络司作为新设机构,既实现了东吴军接口,又继承了刘备军接口,这样就可以通过建立对应关系,来将大都督发出的军令转换为刘备军能够听懂的语言:
刘备军,同样军备自己独有的执行命令方法:
测试类。创建联络司实例(联络司马),将军令中转后下达,两军最后执行的应该是相同的动作:
对象适配器模式
基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承Source类,而是持有Source类的实例,以达到解决兼容性的问题。
吴国军中拥有刘备军的实例。
以下是类图:
以下是主要的业务代码,东吴军接口、刘备军类与上文同,主要变化在适配器类。这里创建一个CommandWrapper类,来完成命令的转换。作为实现了东吴军接口的转换器类,CommandWrapper持有LiuBeiArmy的实例,这样就可以直接通过该实例完成正确命令的执行:
测试类。首先创建LiuBeiArmy的实例(刘备军派出一人到吴国军营),然后创建联络司马,联络司马负责调度,确保两军执行了相同的命令:
接口适配器
刘备军接口定义了两个方法,调动骑兵、调动步兵。如果要设置几个联络机构,负责调动刘备军中的步兵或骑兵。如果不增设一个抽象的联络司出来,则每个联络司马都必须具备调动步兵和骑兵的能力,显得笨重不专业。因此,可以设置一个虚拟的联络司出来,继承自联络司的人员,可以只负责一部分工作,有的司马负责调度骑兵,有的司马负责调度步兵,达到专业适配的目的。
接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题。
我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。在实际开发中,我们也常会遇到这种接口中定义了太多的方法,以致于有时我们在一些实现类中并不是都需要。
类图:
以下为主要的业务代码:
刘备军接口:
联络司,实现刘备军接口:
联络司马1,继承联络司,只Override父类中的一个方法:
联络司马2,继承联络司,只Override父类中的另一个方法:
接口适配器的测试类:
可以看出,接口适配器与其它两种适配器在用途上是有显著区别的。
此外,双向适配是一个非常有意思的场景,表示支持调用双方的接口。
小结
类的适配器和对象适配器的权衡
类适配器使用对象继承的方式,是静态的定义方式;对象适配器使用对象组合的方式,是动态组合的方式
对于类适配器,适配器可以重新定义Adaptee的部分行为,相当于子类覆盖父类的部分实现方法;对于对象适配器,要重新定义Adaptee的行为比较困难,这种情况下,需要首先定义Adaptee的子类来实现重定义,然后让适配器组合子类。
对于类适配器,仅仅引入了一个对象,并不需要额外的引用来间接得到Adaptee.对于对象适配器,需要额外的引用来间接得到Adaptee。
适配器模式的优缺点
优点
更好的复用性:通过适配器模式可以复用已存在但接口不兼容的功能代码
更好的扩展性:适配已有的功能代码,可以使系统自然扩展
缺点
过多使用适配器,会让系统非常凌乱,业务逻辑不清晰、程序分层界限不明
适配器模式的本质
转换匹配,复用功能
适配器模式的适用场景
希望使用一个已经存在的类,但其接口不符合需求;
希望创建一个可复用的类,可能和一些彼此不兼容的类一起工作;
希望使用一些已经存在的子类,但对每个子类适配代价太大,可以选用对象适配器,直接适配父类
相关模式
桥接模式:结构略微相似,但功能完全不同。适配器模式是将多个接口的功能进行转换匹配,桥接模式是让接口和实现分离,以便它们可以相对独立的变化
装饰模式:适配器模式能模拟实现简单的装饰模式,但区别在于:适配器适配过后需要改变接口,但装饰模式不需要改变接口,无论多少层装饰都是一个接口。因此装饰模式可以支持递归组合,但适配器无法做到,因为每次的接口不同,无法递归。
代理模式:可以和代理模式组合使用,实现适配器时,通过代理来调用Adaptee,以求更大灵活性
抽象工厂模式:如果适配对象是接口,就可以和抽象工厂等模式,来得到被适配的对象实例
7、装饰模式(Decorator)
马超外表俊朗,作战悍勇,士族美誉锦马超。攻城掠地是一把好手,西凉人称神威无敌天将军,是凉州大马的代表性人物。凉州大马,一支地处边陲但战功赫赫的神奇部队,屡次勤王,往往起到一锤定音的作用。一首魏晋“诗妖”曾言:“凉州大马,横行天下。凉州鸱苕,寇贼消;鸱苕翩翩,怖杀人。”
但据传马超十分弑杀,每夺一地,人畜无生。因此百姓也是怨声载道。诸葛丞相根据战场需要,动态决定是否在用马超时辅以马岱。
马岱,性恭谨平和,善于安抚。与其兄马超配合,可以减轻百姓怨气。
顾名思义,装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例。
以下是类图:
以下是代码:
凉州大马接口:
马超军实现了凉州大马接口,具有凉州大马的典型能力——攻城:
神威军也实现了凉州大马的接口,不但具有凉州大马的能力,还包含了马超的嫡系部队(持有马超军的实例),并拥有自己的私有方法,来帮助减轻马超攻城造成的负面影响:
以下是测试类,目的是要既拿下城池,也要得到当地世族拥护:
小结
装饰模式优缺点
优点
比继承更灵活。继承是静态的,但装饰模式通过对象组合,可以动态的添加功能
容易复用。将复杂的功能分散到装饰器当中,一个装饰器只实现一个功能,化整为零的同时,也有利于装饰功能的复用
简化高层定义。通过装饰模式,无需将所有功能都定义出来,而只需定义最基本的功能,并根据需要,动态组合相应的需要。
缺点
产生许多细粒度对象。功能越复杂,产生的细粒度对象越多
装饰模式本质
动态组合
动态是手段,组合是目的。把复杂的功能简单化、分散化,然后在运行期间,根据需要来动态组合。
组合有两层含义:一个动态功能的组合,就是动态进行装饰器的组合;另一个是指对象组合,通过对象组合来实现为被装饰对象透明地增加功能
装饰模式不仅可以增加功能,也可以控制功能的访问,完全实现新的功能
装饰模式的适用场景
希望在不影响其它对象的情况下,以动态、透明的方式为对象添加职责
不适合用子类来进行扩展时,比如扩展功能需要的子类太多
相关模式
适配器模式:二者没什么关联,但有一个共同的别名,wrapper。适配器用来改变接口,装饰者用来改变对象功能
组合模式:两种模式都涉及到对象的递归调用。从某个角度说,可以将装饰者看做是只有一个组件的组合。但二者目的完全不同,装饰模式是要动态地给对象增加功能,但组合模式想要管理组合对象和叶子对象,为它们提供一个一致操作接口给客户端,方便客户端使用。
策略模式:两种模式可以组合使用。策略模式也可以实现动态地改变对象的功能,但策略模式只一层选择,即根据策略选择具体的实现类。装饰模式不是一层,而是递归调用,无数层都可以,只要组合好装饰器的对象,就可以依次调用下去。另外,策略模式改变了原始对象的功能,而装饰模式改变的只是经过装饰后的对象。
模板方法模式:模板方法模式主要应用于算法骨架固定的情况,当算法动态变化时,就可以使用装饰模式,实现动态的算法组装。因此装饰者模式可以模拟实现模板方法模式,但两种模式的设计目的、原本的功能和本质思想都是不一样的。
8、代理模式(Proxy)
曹魏与东吴隔江对质,丞相见周公瑾治军严整,颇有将才,便有意招致麾下,但自己又不好出面,便找来蒋干去做说客,试图劝服公瑾,归降朝廷。蒋干便是丞相的代理。
代理模式就是多一个代理类出来,替原对象进行一些操作,比如我们在租房子的时候回去找中介,为什么呢?因为你对该地区房屋的信息掌握的不够全面,希望找一个更熟悉的人去帮你做,此处的代理就是这个意思。
再如我们有的时候打官司,我们需要请律师,因为律师在法律方面有专长,可以替我们进行操作,表达我们的想法。先来看看类图:
以下为业务代码:
曹魏部队接口,具备劝降能力:
丞相实现了曹魏部队接口,意图劝降周瑜:
蒋干实现了曹魏部队接口,同时得到丞相批准,持有了丞相的授权,去劝降周瑜,并附加了一些动作:
测试类,测试蒋干能够做到的事情:
动态代理
不为具体某一个人做事,而是为一类满足要求的人(实现同一接口)做事。与普通代理的区别在于,你是请朋友帮忙买火车票,还是到火车票代售点买火车票。
姑且以吕布为例。
吕布被张飞戏称为三姓家奴,意思是谁对他好他就替谁干。用今天的话来说,就是专业杀手,或者雇佣军。站在吕布的角度,我不管你是谁,只要你能满足我的要求,我跟愿意替你做事。这一点,就跟动态代理很像。
以下是类图:
以下是业务代码:
领主接口,顶层框架:
老板抽象类,实现领主接口,具备攻城能力,并拥有自己的财产:
财产类,衡量领主拥有的财富:
太守类,丁原是其实例:
将军类,董卓是其实例:
雇佣军类,实现动态代理接口,为钱任人雇佣,并在每次完成任务时,从所得中扣除自己的佣金:
动态代理测试类,通过获取和设置不同的代理对象,即可完成幕后老板花钱让办的事情:
代理从功能上可以分为:虚代理、远程代理、copy-on-write代理、保护代理、Cache代理、防火墙代理、同步代理、智能指引等
小结
代理模式的特点
代理模式在客户和被客户访问的对象之间,引入了一定程度的间接性,客户通过代理,来与被访问的对象交互。正是这个间接性,给了代理对象很对的活动空间。代理对象可以在调用具体的目标对象前后,附加很多操作,从而实现新的功能或是扩展目标对象的功能。
更进一步,代理对象甚至可以不去创建和调用目标对象,即完全替换掉目标对象。
代理模式的本质
控制对象访问
代理模式的适用场景
需要为一个我对象在不同的地址空间提供局部代表时,选用远程代理。
需要按照需求创建开销很大的对象时,使用虚代理。
需要控制对原始对象的访问时,可以使用保护代理。
需要在访问对象时附加一些操作,使用智能指引代理。
相关模式
适配器模式:相似点在于,两种模式都是为另一个对象提供间接性的访问,而且都是从自身以外的一个接口向这个对象转发请求。区别在于,适配器主要用来解决接口之间的不匹配,通常为需要适配的对象提供一个新的不同的接口,而代理模式会实现与目标对象相同的接口。
装饰模式:两种模式实现类似,但功能不同。实现上,都是在转调其它对象的前后,执行一定的功能。但装饰模式的目的是为了在不生成子类的前提下就可以给原对象添加职责,是为了动态的增加功能;但代理模式的主要目的是控制对对象的访问。
9、外观模式(Facade)
诸葛孔明深谙用兵之道。用兵之要,如臂使指。执行力是最重要的,哪怕身处险境,哪怕做出了错误选择,只要三军一气,往往能够挽回败局,甚至扭转乾坤。从其多次北伐作战,可窥见一二。
如何做到如臂使指?除了平时的恩威治军以外,战场上的进退自如同样重要。
两军对垒,将领下达击鼓进兵命令或鸣金收兵命令时,并不会将命令逐一下发给弓弩手、长矛手或剑手方阵,或由一个方阵逐次传达给下一个方阵。实际情况是,由传令兵完成这样的统一号令,各方阵视旗语或听鼓声行事。这其实就是门面模式。
外观模式是为了解决类与类之家的依赖关系的,像spring一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度,该模式中没有涉及到接口。
以下是类图:
以下是主要的业务代码:
骑兵、步兵、轻兵3个方阵均具有进攻和撤退方法,但在作战时,指令逐一下达则步调很难协调,因此需要进行统一号令:
外观类,持有上述三个类的实例,统一发号施令:
测试类:
如果我们没有北伐军NorthernExpeditionForce类,那么,骑兵Carvalrymen、步兵infantrymen、轻兵Sacrificemen,他们之间将会相互持有实例,产生关系,这样会造成严重的依赖,修改一个类,可能会带来其他类的修改,这不是我们想要看到的,有了NorthernExpeditionForce类,他们之间的关系被放在了NorthernExpeditionForce类里,这样就起到了解耦的作用。
小结
外观模式的优缺点
优点
松散耦合:门面模式也叫外观模式,松散了客户端与子系统之间的耦合关系,让子系统内部更加独立,便于扩展
简单易用:客户端无需北京白癜风医院好北京治疗白癜风哪家医院不错