微信号:ZTEdeveloper

介绍:中兴开发者社区是一站式云端产品研发社区,为开发者和项目提供项目管理、开源协作、代码托管、持续交付、资源服务、技术交流等服务.让开发更简单,让创新更便捷!

用设计模式解构三国是一种什么体验?——Part 3:行为型设计模式(一)

2017-02-15 17:23 王鹏洲

摘要

《三国演义》,冷兵器时代兵家计谋的最佳诠释者,几乎涵盖了三十六计的方方面面。设计模式就像Java开发领域的三十六计,既是前人思想的总结,更是后续编程思想发展的基石。用设计模式解构三国,会是怎样一种体验?


我们继续透过三国那段历史,来讨论设计模式。

 

前两期我们一起学习了创建型模式和结构型模式。除了这两种类型外,设计模式还有一类,即行为型设计模式。由于行为型设计模式较多,一共有11种,因此我们将分两期学习。

 

以下是行为型设计模式的几种子类型:

 

本期重点学习前两类,父类与子类、两个类之间。涉及的模式包括:

  • 策略模式

  • 模板方法模式

  • 观察者模式

  • 迭代器模式

  • 责任链模式

  • 命令模式

 

第一种  父类与子类


策略模式(Strategy)

孔明为先主谋划的以两川为基地、一统中原的谋划,并提出了多套策略来根据实际情况选择:

1、从荆州北上,经南阳,直捣许都,灭曹后,再北上取洛阳,西入函谷,夺下长安后,再南下横扫东吴;

 

2、从汉中西北北上,出岐山,过陈仓,乘势而下,直捣长安,然后东出函谷,横扫洛阳、许都、建康;

 

3、从汉中东北北上,出子午道,走汉王龙兴故道,奇袭长安,以秦、汉统一途径为模板,据两川关中三大天府,水陆并进,横扫山东,一举鼎定乾坤。

 

策略模式定义一系列算法,将其封装起来,使算法可以互换,且算法的变化不会影响到使用它的用户。

 

实现策略模式,需要设计一个接口类,为一系列策略类提供一个统一的方法。多个实现类实现该方法。

 

下面是类图:

 

INorthernExpedition作为北伐行动的接口,约定了统一的北伐方法,荆州计划、岐山计划和子午奇谋实现该接口,并制定了不同的北伐计划,约定了北伐所走的路线、带军将领以及北伐军的规模。使用哪套计划,是由使用者自己决定的。

 

以下是具体的业务类:

 

北伐行动接口:

 

三个北伐策略:

 

出荆州:

 

出祁山:

 

出子午道:

 

北伐计划所需的资源:

 

策略测试类,可以看到,使用哪项策略是使用者决定的:

小结

 

策略模式的核心思想

分离算法,选择实现

 

策略模式的优缺点

  • 优点:

1. 避免多重条件语句。由于算法可以互换,因此需要用if-else或switch来组织,但算法实现中本身也有可能存在分支语句,因此使用策略模式可以避免这样的情况。

2. 更好的扩展性。增加新的策略,只需要增加新的策略实现类,并添加至策略选项中即可。

  • 缺点:

1. 暴露了策略的具体实现。策略使用者必须自己了解每种策略的详情及其区别;

2. 增加了对象数目。这是避免多重条件语句这一优点的另一面,每个具体策略都要封装成类。

3. 只适合扁平的算法结构。由于继承自同一策略接口,因而策略算法之间彼此是平等的。当需要使用嵌套算法时,不同层级的算法需要组合后使用。

 

策略模式的适用场景

1. 很多类属性相同,仅仅行为存在差别,通过策略模式改造;

2. 一个算法存在很多不同实现时,可以使用策略模式来来重构;

3. 需要封装算法中的相关数据时,可以通过策略模式来避免暴露;

4. 一个类存在很多通过分支语句来选择的行为时,可以采用策略模式。



模板方法模式(Template Method)

《三国志》云:“亮性长于巧思,损益连弩,木流牛马,皆出其意;推演兵法,作八阵图,咸得其要。”正史中如此记载,可见诸葛亮非同一般。诸葛亮不仅能够制作非常精密的器械,敬天明鬼,而且严于律己,操守极苛,且深得兵法精要,有《兵法二十四篇》传于后世。因此有人推测诸葛亮为墨家弟子,不是没有根据。

 

在《兵法二十四篇》中,诸葛亮对选将、用人、排兵、布阵都有高屋建瓴的表述。联想到他事必躬亲的性格,对攻城掠地等事务制定一套框架性的流程实在是他非常有可能会做的事情。假设诸葛亮确立了一套攻城的标准,所有将领都必须按这套标准攻城。但是,如果标准定的太细太死,则无法应对时空、主将脾性、城防规模等客观因素的变化。因此,武侯的标准一定是一套通用标准,不同的攻城将领可以根据战场瞬息万变的形势,增加或改变部分细节。这就是所谓的模板方法。

 

一个抽象类中,有一个主方法,再定义多个方法,可以是抽象的,也可以是实际的方法。再定义一个类,继承该抽象类,重写抽象方法。

通过调用抽象类,实现对子类的调用。

 

下面是类图:

 

AbstractCityConqueror作为抽象类,约定了一套攻城的通用方法。事实上,作为模板使用的类,不见得必须是抽象类。MountainCityConqueror和WaterCityConqueror继承了该抽象类,并拥有自己独特的方法或属性。CityAttributes作为城市的属性类,记录城市的类型是山城还是水城,以及城市的规模等等。

 

以下是具体的业务类:

 

模板类,conquerCity(String)作为主方法,实现对本类其它方法的调用,并提供给外部使用。另外,提供一个conquerCity(CityAttributes)抽象方法,给子类继承和填充自己独有的内容:

 

山城攻打的方法类:

 

水城攻打的方法类:

 

城市属性类:

 

模板方法测试类:


小结


模板方法模式的核心思想

抽离算法框架

 

模板方法的优缺点

  • 优点:

1. 实现了代码复用;

  • 缺点

1.  算法模板与自雷耦合度高,变更代价大,需要通知所有相关的子类。

 

模板方法模式的适用场景

1. 需要多个算法,且算法存在不变的部分时,可以考虑模板方法。将不变的部分固定下来,将可变的部分交给子类。

2. 子类扩展需要控制时,模板方法只会在特定的点来调用子类方法,这样就确保了子类扩展的边界。


 第二种  类与类之间的关系


观察者模式(Observer)

三国是一场三方博弈的棋局。一方内乱,两方趁势取之;两方开战,第三方视情形选边站。比如魏吴开战,吴军不利,蜀军就从汉中北上袭扰,使魏首尾不能兼顾。二爷北上攻打魏国,试图夺取襄樊,直逼许都。东吴见蜀军将胜,便出奇兵,攻下荆州,抄了二爷的后路。又比如,武侯率蜀军出岐山北伐,东吴便过江袭扰,使魏国不能专心抗蜀。凡此种种,真心假意,令人目不暇接。

 

当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随之变化,这就是典型的观察者模式。

 

下面是类图:

 

EnemySubject是被观察者的接口,约定了添加、删除、通知观察者的方法,并声明了一个被观察者自己的动作,这个动作最终会触发观察者的联动。WeiSubject作为具体的被观察者,与其实现的接口之间还隔了一层抽象类,该抽象类负责管理观察者,这样WeiSubject只需要聚焦自己内部的事物。

 

EnemyObserver是观察者的接口,约定了一个统一的接口方法,一旦被观察者发生变化并通知观察者,那么观察者必须触发联动,WuObserver和ShuObserver是它的两个实现类。

 

以下是具体的业务代码:

 

被观察者接口:


被观察者的抽象类:

 

魏国,作为具体的被观察者:

 

观察者接口:

 

蜀国,作为观察者:

 

吴国,作为观察者:

 

观察者测试类:


测试结果:

小结

 

观察者模式的核心思想

触发联动

 

观察者模式的优缺点

  • 优点:

1. 规避了观察者和观察目标之间的直接耦合,实现了抽象层面的耦合,目标只知道观察者接口;

2. 通过动态地控制注册的观察者,可以控制某个动作的联动范围,实现动态联动;

3. 实现了广播功能,并通在目标对象中添加新的功能可以控制广播的范围。

  • 缺点:

1. 对于新手来说,容易引起无谓甚至错误的联动操作。

 

适用场景

1. 一个抽象模型有两个方面,且一个方面的操作依赖于另一个方面的状态变化;

2. 更改一个对象的同时,需要连带改变其它对象,且不想知道究竟具体连带修改的对象是谁,有多少;

3. 很多客户端框架,如swing,很好地使用了观察者模式。

 


迭代器模式(Iterator)

天子传衣袋诏于董承,以天家之血,控诉曹操目无君臣之义,号召天下英雄勤王。董承准备一个名册(集合类),请诸位有志于匡扶汉室者(对象类)留名。先后有车骑将军董承、西凉太守马腾、偏将军王子服、长水校尉种辑、议郎吴硕、太医吉平、左将军刘备等人加入,共谋杀操。但隔墙有耳,密谋一事很快泄露。曹丞相得名册,于都内逐一抓人,灭其族,唯刘备侥幸逃出。这就是三国演义中描写的衣袋诏事件,正史记载为刘备之乱。

 

曹操按照名册,逐一抓人审问处理,逼其招供。这一架构,就是迭代器模式。


迭代器模式,顾名思义,就是顺序访问聚集中的对象,一般来说,集合中非常常见。本模式涉及两个方面:一是需要遍历的对象,即聚集对象,二是迭代器对象,用于对聚集对象进行遍历访问。

 

下面是类图:

Iterator作为迭代器接口,约定了遍历勤王名册的各种方法,名册迭代器实现了这个接口。勤王者们作为一个集合对象,继承了Defenders接口,具备了遍历方法。

 

以下是具体的业务代码:

 

勤王名册的迭代器,实现具体的遍历方法:

 

勤王者的接口,提供一个供外部调用的遍历方法iterator():


具体的勤王者类,持有勤王者名单:

 

迭代器的测试类:

 

测试打印的结果,这里没有好的idea去断言,所以直接打印出结果:


小结

 

迭代器模式的核心思想

控制访问集合对象中的元素

 

迭代器模式的优缺点

  • 优点:

1. 更好的封装性:访问一个聚合对象的内容,而无需暴露该对象的内部表示;

2. 可以通过不同的遍历方式来遍历访问一个集合,因为迭代器模式将集合对象的内容和具体的迭代算法分开了。

3. 简化了集合对象的接口和客户端调用。

 

迭代器模式的适用场景

1. 希望访问一个聚合对象而不像暴露它的内部表示;

2. 希望有多种方式遍历集合对象;

3. 希望为遍历不同的集合对象提供一个统一的接口。



责任链模式

(Chain of Responsibility)

孙权用鲁肃计,拘禁诸葛子瑜家小,令其讨还荆州。子瑜孤舟入川,见弟孔明后大哭。孔明知兄来意,却不出面处理,而是将兄长推给了先主。先主创业艰难,怎肯将荆州重地轻易许人。但念及孔明忠心,便写书信一封,委托二爷交割长沙、零陵、桂阳三郡于东吴,一杆子将子瑜支往荆州。二爷看了书信,早已知晓先主之意,明言荆州乃大汉疆土,怒斥子瑜无礼。子瑜见状,只好再次入川去见孔明,孔明又推给先主,子瑜只好再见先主。先主一番宽慰后,再写书信,交与子瑜。子瑜再往荆州去见二爷。二爷佯怒,后经关平劝阻,便假装同意交割,又将子瑜支去三郡守将处。东吴交割之人去三郡讨要城池,却被三郡太守以未得公文为由赶回。东吴诸人气氛不过,鲁肃设下鸿门宴,要逼二爷就范,谁知二爷竟单刀赴会……

 

从孔明到二爷,彼此推托,形成了一条比较典型的职责链。所谓职责链模式,就是指有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求,所以,责任链模式可以实现,在隐瞒客户端的情况下,对系统进行动态的调整。

 

职责链模式有纯与不纯之分。纯粹的职责链模式是指请求最终只会被一个对象处理,其它位于职责链上的对象不参与,且请求最终一定会被处理,不能无果而终;不纯的职责链则刚好相反,一个请求可能会有多个对象参与处理,参与的每个对象可能只处理其中一部分,或者出现请求得不到处理的情况。

 

下面是类图:

 

AbstractHandler提供getHandler()和setHandler方法(),以便其子类MyHandler能够轻易地设置和修改引用的对象。MyHandler类还实现了Handler接口的returnJingZhouToDongWu()方法,多次实例化形成一系列相互持有的对象,构成一条归还荆州的责任链。

 

以下是具体的业务代码:

 

处理者接口,约定了归还荆州的方法:

 

抽象的处理者类,持有处理者接口,并实现了处理者的getter和setter方法:


具体的处理者类:

 

责任链模式的测试类,每个处理者对象都有能力处理归还荆州的事宜,但对外人(诸葛子瑜)来说,具体谁来归还,他无从知晓:


小结


责任链模式的核心思想

分离职责,动态组合

 

责任链模式的优缺点

  • 优点:

1. 请求者和接收者松散耦合;

2. 通过动态组合职责,可以灵活地给对象分配职责,也可以灵活地实现和改变对象的职责。

  • 缺点:

1. 职责链模式将职责分散到多个对象,容易产生很多细粒度对象;

2. 在未提供默认处理的职责链中,一个请求可能不一定被处理。

 

责任链模式的适用场景

1. 有多个对象可以处理同一请求,但具体由哪个对象来处理,希望动态确定;

2. 希望在不明确或者不暴露具体接收者的前提下提交请求;

3. 希望动态决定由哪些对象组合起来参与请求的处理。



以上,就是工厂方法模式、抽象工厂方法模式、单例模式、建造者模式、原型模式等五种创建型设计模式的大概情况。


命令模式(Command)

子龙是先主最为忠顺的属下,执行先主的命令,无论是进攻、撤退、夺城、打围,从来不打折扣。因此先主在时,常用子龙来打攻坚战。先主、命令、子龙三者之间互相解耦,各自可以独立扩展或替换,如先主换为孔明,命令切换为其它,子龙替换为马超等人,就形成了典型的命令模式。

 

命令模式的目的就是达到命令的发出者和执行者之间解耦,实现请求和执行分开,熟悉Struts的同学应该知道,Struts其实就是一种将请求和呈现分离的技术,其中体现的正是命令模式的思想。

 

下面是类图:

 

刘玄德让赵子龙去干件事情。从整个过程考虑,刘玄德发出命令,命令经过传递,赵子龙得到命令,执行命令。老板、命令、将军三者相互解耦,任何一方都不用依赖其它一方。

 

以下是具体的业务代码:

 

老板类,invoker类,负责命令的下发或者说调用:

 

命令接口,用于约束统一的命令调用方法:

 

具体的命令类,持有将军属性,并实现了命令的调用:

 

将军类,拥有具体的行动方法:

 

命令模式的测试类:


小结

 

命令模式的核心思想

封装请求

 

命令模式优缺点

  • 优点:

1. 使命令发起者、命令执行者、命令本身三者之间实现松散耦合;

2. 将命令(请求)封装,使其可以动态地参数化、队列化;

3. 命令对象可以轻松地组合成复合命令;

4. 三者间的松散耦合,使得各方可以轻易地扩展。

 

命令模式的适用场景

1. 希望抽象出需要执行的动作,并将这些对象参数化时;

2. 希望根据不同的时刻,动态指定、排列和执行请求时;

3. 希望某个操作能够取消、回退或重新执行;

4. 希望使用事务来管理一个操作流程。


以上就是策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式和命令模式的相关情况。

请进入公众号点击菜单“干货”,

查看更多精彩文章!

 
中兴开发者社区 更多文章 用设计模式解构三国是一种什么体验?——Part 1:创建型设计模式 用设计模式解构三国是一种什么体验?——Part 2:结构型设计模式 MWC2017 | 中兴通讯推出5G全系列革新产品 引领5G产业化 用设计模式解构三国是一种什么体验?——Part 3:行为型设计模式(二) 用设计模式解构三国是一种什么体验?——Part 3:行为型设计模式(二)
猜您喜欢 连载 | 运维——从入门到进阶不完全手册 【第624期】如何打造一个高性能的Hybrid App ✎ _简单的是我想你, 困难的是我们不在一起 这五点,让你了解IoT平台生态系统 代码的语义正确真的就够了吗?