模式一:模版方法
Define the skeleton of an algorithm in an operation,deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
(来自《设计模式之禅》)
解释一下上面的介绍,意思是由父类来定义框架,让子类来具体实现。
你要的故事
刚过完春节,大家都买新鞋了么?今天要讲的故事和鞋子有关。一双鞋子从表面来看,由鞋底、鞋垫、鞋面、鞋带组成,同一系列的鞋子这几个部分都是一样的,用同样的材料做出来,不同系列的鞋子就大相径庭了。根据模板方法模式,组装一双鞋子的制造过程可以归并为固定的框架,至于用什么材料,那由每个系列的鞋子去具体实现。我们先看定义组装鞋子的框架代码。
/** * 定义鞋子制造的工序框架 */ abstract class ShoeInstallTemplate { public abstract void installSole(); public abstract void installInsole(); public abstract void installVamp(); public abstract void installShoelace(); public void installShot(){ System.out.println("组装一双鞋,步骤如下:"); // 组装鞋底 installSole(); // 组装鞋垫 installInsole(); // 组装鞋面 installVamp(); // 组装鞋带 installShoelace(); } }
定义了一个组装鞋子框架的抽象类 ShoeInstallTemplate,里面有 4 个工序未具体实现,由鞋子制造商去实现,因为只有鞋子制造商才知道鞋子要用什么材料来做。
下面举 2 个比较出名的鞋子:Adidas 的 Boost 系列和 Nike 的 Jordan 系列。下面分别实现这 2 个系列鞋子的制造代码。
/** * Adidas Boost 鞋制造 */ class AdidasBoostShoeInstall extends ShoeInstallTemplate { @Override public void installSole() { System.out.println("组装白色 Boost 鞋底"); } @Override public void installInsole() { System.out.println("组装黑色 Boost 鞋垫"); } @Override public void installVamp() { System.out.println("组装黑色 Boost 鞋面"); } @Override public void installShoelace() { System.out.println("组装黑色 Boost 鞋带"); } } /** * Nike Jordan 鞋制造 */ class NikeJordanShoeInstall extends ShoeInstallTemplate { @Override public void installSole() { System.out.println("组装黑色 Jordan 鞋底"); } @Override public void installInsole() { System.out.println("组装黑色 Jordan 鞋垫"); } @Override public void installVamp() { System.out.println("组装红色 Jordan 鞋面"); } @Override public void installShoelace() { System.out.println("组装红色 Jordan 鞋带"); } }
实现了制造商制造鞋子的代码之后,我们通过代码测试怎么制造 Boost 和 Jordan 鞋子。
public class TemplateMethodTest { public static void main(String[] args) { ShoeInstallTemplate adidasBoost = new AdidasBoostShoeInstall(); adidasBoost.installShot(); ShoeInstallTemplate nikeJordan = new NikeJordanShoeInstall(); nikeJordan.installShot(); } } 组装一双鞋,步骤如下: 组装白色 Boost 鞋底 组装黑色 Boost 鞋垫 组装黑色 Boost 鞋面 组装黑色 Boost 鞋带 组装一双鞋,步骤如下: 组装黑色 Jordan 鞋底 组装黑色 Jordan 鞋垫 组装红色 Jordan 鞋面 组装红色 Jordan 鞋带
模板方法模式就这么简单。是不是掌握了?
代码:
https://github.com/1CSH1/DesignPatterns/blob/master/src/com/liebrother/designpatterns/templatemethod/
总结
模板方法是一个比较实用的模式,为什么说实用呢?举个现实的例子,Java 能有如今的发展,离不开各大开源框架,比如 Dubbo,有看过源码的朋友就知道,里面大量代码运用了模板方法设计模式,为什么 Dubbo 可以支持很多种注册中心?其实本质就是用了模板方法设计模式,使得可以扩展多种注册中心。掌握好模板方法,对读源码有非常大的帮助,很多人包括我在内,在刚开始阅读源码的时候,有相当长的一段时间怀疑人生,怎么这些代码那么绕?调来调去的。当你了解了常用的设计模式之后,看源代码就可以直截了当的知道是用什么设计模式,为什么用这个设计模式?原来是为了什么什么。。。有了这层思考,就像有一条线将以前散落在各地的知识点连接起来,成了可以推敲的知识。
型模式之二:中介者模式
Define an object that encapsulates how a set of objects interact.Mediator promotes loose coupling by keeping objects from referring to each other explicitly,and it lets you vary their interaction independently.
用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
(来自《设计模式之禅》)
你要的故事
看了这小伙子的名字,大家会很直观的想到那些拿了我们半个月租的租房中介同学。在这不讲讲房租中介同学,以后可没机会了。大家现在找房子,不管是买还是租,一登录什么安居客、58同城,是不是有 80% 是经纪人房源,说 80% 还是比较保守的,经历了 4 次找房,发现个人房源越来越少。每个网站都有个选项:经纪人房源。如下图:
(图片截自:安居客网站)
经纪人就扮演着中介的角色,和本文要讲的中介者模式完全吻合。我们在找房子的时候,经纪人扮演什么角色呢?我们通过个人房源和经纪人房源的租房案例来简单描述经纪人的角色。
个人房源
我们通过个人房源找房子的方式是这样的:在网上找个人房源的房东,然后挨个联系,和房东约定好时间去看房,我们跟房东的关系是一对多的关系。小明就在网上看了个人房源,联系了房东,分别去看了农民房和小区房,用代码表示如下。
public class PersonalTest { public static void main(String[] args) { Tenant xiaoMing = new Tenant("小明"); xiaoMing.lookAtHouse(); } } class Tenant { private String name; private XiaoQuFangLandlord xiaoQuFangLandlord2 = new XiaoQuFangLandlord(); private NongMinFangLandlord nongMinFangLandlord2 = new NongMinFangLandlord(); public Tenant(String name) { this.name = name; } public void lookAtHouse() { System.out.println(this.name +"想看农民房"); nongMinFangLandlord2.supply(); System.out.println(this.name + "想看小区房"); xiaoQuFangLandlord2.supply(); } } /** * 房东 */ abstract class Landlord { // 提供房子 public abstract void supply(); } class XiaoQuFangLandlord extends Landlord { @Override public void supply() { System.out.println("小区房的房东提供一间小区房"); } } class NongMinFangLandlord extends Landlord { @Override public void supply() { System.out.println("农民房的房东提供一间小区房"); } } 打印结果如下: 小明想看农民房 农民房的房东提供一间小区房 小明想看小区房 小区房的房东提供一间小区房
小明分别联系小区房的房东和农民房的房东,然后依次去看了农民房和小区房。这样子有个弊端就是小明和房东是强关联的关系,其实小明只是去看一下房,看完不想租就和房东没啥关系了。这个时候经纪人就派上用场了,经纪人的主要任务就是把房子租出去,所以他和房东应该是强关系,直到把房子成功租出去了,才和房东脱离关系,而小明也不用去挨个找房东看房子了,这个职责转给经纪人,小明只需要联系一个人,那就是经纪人,跟他说我要看小区房和农民房,经纪人就带他去看。下面就介绍经纪人房源的方式,也就是本文要讲的中介者模式。
经纪人房源
在代码中,我们可以看到小明和经纪人是一对一关系,经纪人和房东是一对多关系。小明找房经历也轻松多了,只花了一下午就把房子都看了并看中了。这也是中介者模式的优点,减少了不必要的依赖,降低了类间的耦合。
public class MediatorTest { public static void main(String[] args) { System.out.println("小明想要看小区房和农民房"); Tenant2 xiaoMing = new Tenant2("小明", Arrays.asList("XiaoQuFang", "NongMinFang")); xiaoMing.lookAtHouse(); } } /** * 租客 */ class Tenant2 { private String name; private List<String> wantTypes; private RentingMediator rentingMediator = new RentingMediator(); public Tenant2(String name, List<String> wantTypes) { this.name = name; this.wantTypes = wantTypes; } public void lookAtHouse() { rentingMediator.supplyHouse(wantTypes); } } /** * 中介抽象类 */ abstract class Mediator { // 看房 public abstract void supplyHouse(List<String> types); } /** * 租房中介 */ class RentingMediator extends Mediator { private XiaoQuFangLandlord xiaoQuFangLandlord; private NongMinFangLandlord nongMinFangLandlord; public RentingMediator() { xiaoQuFangLandlord = new XiaoQuFangLandlord(); nongMinFangLandlord = new NongMinFangLandlord(); } @Override public void supplyHouse(List<String> types) { System.out.println("经纪人提供了如下房源"); if (types.contains("XiaoQuFang")) { xiaoQuFangLandlord.supply(); } if (types.contains("NongMinFang")) { nongMinFangLandlord.supply(); } } } 打印结果: 小明想要看小区房和农民房 经纪人提供了如下房源 小区房的房东提供一间小区房 农民房的房东提供一间小区房
在代码中,我们可以看到小明和经纪人是一对一关系,经纪人和房东是一对多关系。小明找房经历也轻松多了,只花了一下午就把房子都看了并看中了。这也是中介者模式的优点,减少了不必要的依赖,降低了类间的耦合。
行为型模式之三:命令模式
将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
(来自《设计模式之禅》)
你要的故事
作为一个程序猿,我们每天都在经历着命令模式,技术经理把需求任务分配给工程师开发,有时因为第三方或者其他不可抗拒的因素导致需求停止开发。这种工作模式就是命令模式。好了,开始故事了。小明在 XX 科技公司做一个安静的程序猿,有一天技术经理给他分配了一个任务:新增黑名单,也就是在他们系统的某个模块里面可以手工对电话打黑名单标签的功能。小明接到任务后就立马开发,在开发了 2 天之后,因为战略原因,技术经理大明暂停了这个开发任务,接下来我们通过非命令模式和命令模式 2 种代码实现来体现这个过程。在这个场景中,为了简单,我们假定技术经理大明手下只有小明一个开发人员。
经纪人就扮演着中介的角色,和本文要讲的中介者模式完全吻合。我们在找房子的时候,经纪人扮演什么角色呢?我们通过个人房源和经纪人房源的租房案例来简单描述经纪人的角色。
非命令模式
非命令模式也就是不使用命令模式的代码实现。代码中,我们出现了 Developer 开发人,开发同学是接受技术经理传达的任务,技术经理让他开发哪个需求就开发哪个需求,如果项目有问题需要中断,也需要技术经理评估后传达给开发同学,所以 Developer 有 2 个方法,分别是 develop() 开发需求和 suspend() 暂停需求。 Requirement 则为需求类,TechnicalManager1 则为技术经理类,他有一个方法 action(),通过这个方法来指定开发同学开发任务或者暂停任务。
public class NoCommandTest { public static void main(String[] args) { Developer xiaoMing = new Developer("小明"); Requirement requirement = new Requirement("新增黑名单"); TechnicalManager1 technicalManager2 = new TechnicalManager1("大明"); technicalManager2.setDeveloper(xiaoMing); technicalManager2.action(requirement, "develop"); System.out.println("开发了 2 天,需求变故,需要暂停。。。"); technicalManager2.action(requirement, "suspend"); } } /** * 开发人员 */ class Developer { private String name; public Developer(String name) { this.name = name; } public void develop(Requirement requirement) { System.out.println(this.name + " 开始开发需求:" + requirement.getName()); } public void suspend(Requirement requirement) { System.out.println(this.name + " 停止开发需求:" + requirement.getName()); } public String getName() { return name; } } /** * 需求 */ class Requirement { private String name; public Requirement(String name) { this.name = name; } public String getName() { return name; } } /** * 技术经理 */ class TechnicalManager1 { private String name; private Developer developer; public TechnicalManager1(String name) { this.name = name; } public void setDeveloper(Developer developer) { this.developer = developer; } public void action(Requirement requirement, String type) { if ("develop".equals(type)) { this.developer.develop(requirement); } else if ("suspend".equals(type)) { this.developer.suspend(requirement); } } }
通过代码,我们可以发现技术经理和开发同学是强依赖关系。如果技术经理下达了一个任务,要求小明写一下周报,这时候得怎么写?是不是小明需要一个写周报的方法,大明也需要新增一个处理事务类型?有没有更好的方法让技术经理不需要做任何改变?命令模式就来解决这个问题。
命令模式
在这个例子中,不管大明叫小明做什么事情,其实都是一样的,就是下达任务命令,让小明去执行命令。我们可以利用命令模式把下达任务这个抽象起来,当做父类,下达开发命令、下达暂停命令、下达写周报等等都是不同的子命令。代码如下。
public class CommandTest { public static void main(String[] args) { Developer xiaoMing = new Developer("小明"); Command developCommand = new DevelopCommand(xiaoMing); Command suspendCommand = new SuspendCommand(xiaoMing); Requirement requirement = new Requirement("新增黑名单"); TechnicalManager2 technicalManager = new TechnicalManager2("大明"); technicalManager.setCommand(developCommand); technicalManager.action(requirement); System.out.println("开发了 2 天,需求变故,需要暂停。。。"); technicalManager.setCommand(suspendCommand); technicalManager.action(requirement); } } /** * 命令 */ abstract class Command { protected Developer developer; public Command(Developer developer) { this.developer = developer; } public abstract void execute(Requirement requirement); } /** * 开始开发 */ class DevelopCommand extends Command { public DevelopCommand(Developer developer) { super(developer); } @Override public void execute(Requirement requirement) { this.developer.develop(requirement); } } /** * 开发中断 */ class SuspendCommand extends Command { public SuspendCommand(Developer developer) { super(developer); } @Override public void execute(Requirement requirement) { this.developer.suspend(requirement); } } /** * 技术经理 */ class TechnicalManager2 { private String name; private Command command; public TechnicalManager2(String name) { this.name = name; } public void action(Requirement requirement) { this.command.execute(requirement); } public void setCommand(Command command) { this.command = command; } }
代码中用 Command 来抽象下达任务,而技术经理 TechnicalManager2 并没有和 Developer 有直接的关系,而是 TechnicalManager2 和 Command 建立的联系,Command 和 Developer 建立了联系。这样子把大明和小明的强依赖关系给剥离开,而新增一个下达写周报的任务也很简单,在 Developer 中新增一个处理写周报的方法,新增一个写周报的 Command 子类,就可以了,TechnicalManager2 如上面所愿不用修改。这就是完整的一个命令模式代码。
行为型模式之四:责任链模式
使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
(来自《设计模式之禅》)
你要的故事
快要金三银四了,很多同学蠢蠢欲动想要去外面看看世界,而大家有没有提前了解各大企业的面试流程呢?这里我就给大家科普一下大多数互联网企业的面试流程,正好责任链模式用得上。
在互联网企业中,程序员这个岗位的招聘流程大同小异,而一般至少都会有 3 轮面试,分别是 2 轮技术面和 1 轮 HR 面。而这几轮面试都是层层递进的,最开始面试一般是组长面试,通过之后就是部门领导面试,再通过之后就是 HR 面试,HR 面试通过之后就可以成功拿到 Offer 了。
故事从小明参加某公司的面试开始,某公司的招聘流程就是上面说的 3 轮面试。招聘流程的面试官分别是:第一面是组长老刚,第二面是部门经理老孙,第三面也就是终面是 HR 老刘。为什么说这个场景符合责任链模式呢?首先不管是组长还是部门经理还是 HR,他们都作为面试官,面试官赋予他们的权利是去面试来公司面试的同学,而面试的结果是可传递性的,也就是如果面试通过,就会到下一轮面试,最终成为一条传递链。我们用代码模拟这个过程。
public class ChainOfResponsibilityTest { public static void main(String[] args) { Interviewee interviewee = new Interviewee("小明"); TeamLeader teamLeader = new TeamLeader("老刚"); DepartMentManager departMentManager = new DepartMentManager("老孙"); HR hr = new HR("老刘"); // 设置面试流程 teamLeader.setNextInterviewer(departMentManager); departMentManager.setNextInterviewer(hr); // 开始面试 teamLeader.handleInterview(interviewee); } } /** * 面试者 */ class Interviewee { private String name; private boolean teamLeaderOpinion; private boolean departMentManagerOpinion; private boolean hrOpinion; public Interviewee(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isTeamLeaderOpinion() { return teamLeaderOpinion; } public void setTeamLeaderOpinion(boolean teamLeaderOpinion) { this.teamLeaderOpinion = teamLeaderOpinion; } public boolean isDepartMentManagerOpinion() { return departMentManagerOpinion; } public void setDepartMentManagerOpinion(boolean departMentManagerOpinion) { this.departMentManagerOpinion = departMentManagerOpinion; } public boolean isHrOpinion() { return hrOpinion; } public void setHrOpinion(boolean hrOpinion) { this.hrOpinion = hrOpinion; } } /** * 面试官 */ abstract class Interviewer { protected String name; protected Interviewer nextInterviewer; public Interviewer(String name) { this.name = name; } public Interviewer setNextInterviewer(Interviewer nextInterviewer) { this.nextInterviewer = nextInterviewer; return this.nextInterviewer; } public abstract void handleInterview(Interviewee interviewee); } /** * 组长 */ class TeamLeader extends Interviewer { public TeamLeader(String name) { super(name); } @Override public void handleInterview(Interviewee interviewee) { System.out.println("组长[" + this.name + "]面试[" + interviewee.getName() + "]同学"); interviewee.setTeamLeaderOpinion(new Random().nextBoolean()); if (interviewee.isTeamLeaderOpinion()) { System.out.println("[" + interviewee.getName() + "]同学组长轮面试通过"); this.nextInterviewer.handleInterview(interviewee); } else { System.out.println("[" + interviewee.getName() + "]同学组长轮面试不通过"); } } } /** * 部门经理 */ class DepartMentManager extends Interviewer { public DepartMentManager(String name) { super(name); } @Override public void handleInterview(Interviewee interviewee) { System.out.println("部门经理[" + this.name + "]面试[" + interviewee.getName() + "]同学"); interviewee.setDepartMentManagerOpinion(new Random().nextBoolean()); if (interviewee.isDepartMentManagerOpinion()) { System.out.println("[" + interviewee.getName() + "]同学部门经理轮面试通过"); this.nextInterviewer.handleInterview(interviewee); } else { System.out.println("[" + interviewee.getName() + "]同学部门经理轮面试不通过"); } } } /** * HR */ class HR extends Interviewer { public HR(String name) { super(name); } @Override public void handleInterview(Interviewee interviewee) { System.out.println("HR[" + this.name + "]面试[" + interviewee.getName() + "]同学"); interviewee.setHrOpinion(new Random().nextBoolean()); if (interviewee.isHrOpinion()) { System.out.println("[" + interviewee.getName() + "]同学HR轮面试通过, 恭喜拿到 Offer"); } else { System.out.println("[" + interviewee.getName() + "]同学HR轮面试不通过"); } } } 打印结果: 组长[老刚]面试[小明]同学 [小明]同学组长轮面试通过 部门经理[老孙]面试[小明]同学 [小明]同学部门经理轮面试通过 HR[老刘]面试[小明]同学 [小明]同学HR轮面试通过, 恭喜拿到 Offer
上面代码打印结果是小明通过层层筛选,通过了面试,拿到了 Offer。下面的图来展现这次面试的流程。
讲解一下代码,Interviewee 是面试者,对于企业来说这个面试者来面试的过程会有 3 个关键标识,就是 3 位面试官对这位面试者的评价,只有都评价好才能通过面试拿到 Offer,所以 Interviewee 类有 3 位面试官的面试结果。Interviewer 是面时官,企业中面试官不是一个职位,而是一个工作,帮企业招到合适的人才,所以它是一个抽象类,有一个抽象方法就是去面试应聘者,具体面试过程又各面试官实现,而因为这个面试会有结果反馈,结果好的会进入下一轮面试,所以会有下一个面试官的对象引用,责任链模式也就在这里体现。TeamLeader、DepartMentManager、HR 则为公司的不同职位,而这些职位当公司需要招聘时,都需要去充当面试官,所以都继承了 Interviewer。这整个过程就构成了责任链模式代码示例,希望在金三银四各位蠢蠢欲动的朋友们都能闯到最后一关拿下 HR 姐姐。
代码:
https://github.com/1CSH1/DesignPatterns/blob/master/src/com/liebrother/designpatterns/chainofresponsibility/
总结
责任链模式很好的把处理的逻辑封装起来,在代码中,我们看到的只是组长面试,但是其实背后隐藏着部门经理和HR的面试。责任链是不是看起来很熟悉?在开发 Java Web 项目的时候是不是有用到过?Filter 过滤器里面就是用责任链模式来实现的。上面代码还用到了另一个模式,不明确指出来了,考验大家学习这些设计模式的效果,知道的同学可以留言说一下。
行为型模式之五:策略模式
定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。
(来自《设计模式之禅》)
你要的故事
先看一张拍得不好看的图片
每天上完班回到家第一件事情是干啥?有人一进门就躺在沙发上闭目养神、有人一进门躺在沙发上玩手机、有人一进门就陪自己的小宠物玩等等。而我进门第一件事就是洗澡,洗完澡很容易就把一整天的疲惫感给消除掉,然后就可以开始美好的下班时光。现实没那么美好,洗完澡后还要洗衣服,大学手洗了 4 年的衣服,一出来工作,宿舍第一必需品就是洗衣机。细看洗衣机,有很多种洗衣类型,比如:标准、大物、快洗、轻柔。洗衣类型的区别在于洗衣服的过程不一样,洗衣过程包括有浸泡、洗涤、漂洗、脱水,还有洗衣服的时间也不一样。细想可以发现这 4 种洗衣类型其实是洗衣服的 4 种不同的策略,也即是 4 种不同的算法。根据这个思路,我们可以用代码实现它,定义一个接口 WashingStrategy 定义洗衣服类型,而这些类型都有各自的洗衣过程,比如标准洗衣类型就包括浸泡、洗涤、漂洗、脱水,而快洗则只包括洗涤、漂洗、脱水。而我们洗衣服则需要选择某个洗衣类型后,洗衣机就开始工作了。过程如下代码所示。
public class StrategyTest { public static void main(String[] args) { WashingStrategy washingStrategy = new StandardWashingStrategy(); WashingMachine washingMachine = new WashingMachine(washingStrategy); washingMachine.washingClothes(); } } /** * 洗衣类型 */ interface WashingStrategy { void washing(); } /** * 洗衣机 */ class WashingMachine { private WashingStrategy washingStrategy; public WashingMachine(WashingStrategy washingStrategy) { this.washingStrategy = washingStrategy; } public void washingClothes() { this.washingStrategy.washing(); } } /** * 标准 */ class StandardWashingStrategy implements WashingStrategy{ @Override public void washing() { System.out.println("标准流程:"); System.out.println("[浸泡] 10 分钟"); System.out.println("[洗涤] 2 次,每次 15 分钟"); System.out.println("[漂洗] 1 次,每次 10 分钟"); System.out.println("[脱水] 5 分钟"); System.out.println("总共耗时:55 分钟"); } } /** * 快洗 */ class QuickWashingStrategy implements WashingStrategy { @Override public void washing() { System.out.println("快洗流程:"); System.out.println("[洗涤] 1 次,每次 10 分钟"); System.out.println("[漂洗] 1 次,每次 10 分钟"); System.out.println("[脱水] 5 分钟"); System.out.println("总共耗时:25 分钟"); } } /** * 大物 */ class BigClothesWashingStrategy implements WashingStrategy { @Override public void washing() { System.out.println("大物流程:"); System.out.println("[浸泡] 30 分钟"); System.out.println("[洗涤] 3 次,每次 15 分钟"); System.out.println("[漂洗] 2 次,每次 10 分钟"); System.out.println("[脱水] 5 分钟"); System.out.println("总共耗时:100 分钟"); } } /** * 轻柔 */ class SoftWashingStrategy implements WashingStrategy { @Override public void washing() { System.out.println("轻柔流程:"); System.out.println("[浸泡] 10 分钟"); System.out.println("[漂洗] 2 次,每次 15 分钟"); System.out.println("[脱水] 5 分钟"); System.out.println("总共耗时:45 分钟"); } } 标准流程: [浸泡] 10 分钟 [洗涤] 2 次,每次 15 分钟 [漂洗] 1 次,每次 10 分钟 [脱水] 5 分钟 总共耗时:55 分钟
是不是感觉策略模式很简单呢?上面代码就是完整的策略模式示例,是不是感觉有些问题,这 4 种洗衣类型对象完全暴露给了用户,这也是策略模式的缺点。往往策略模式不会单独使用,会和其他设计模式一起使用,比如和简单工厂模式一起使用就可以解决这个对外暴露对象的问题,看下面代码。
/** * 洗衣类型选择 */ class WashingFactory { public static WashingStrategy getWashingStrategy(String type) { if ("Quick".equals(type)) { return new QuickWashingStrategy(); } if ("BigClothes".equals(type)) { return new BigClothesWashingStrategy(); } if ("Soft".equals(type)) { return new SoftWashingStrategy(); } return new StandardWashingStrategy(); } } public class StrategyTest { public static void main(String[] args) { WashingStrategy washingStrategy2 = WashingFactory.getWashingStrategy("Soft"); WashingMachine washingMachine2 = new WashingMachine(washingStrategy2); washingMachine2.washingClothes(); } } 打印结果: 轻柔流程: [浸泡] 10 分钟 [漂洗] 2 次,每次 15 分钟 [脱水] 5 分钟 总共耗时:45 分钟
代码中使用 WashingFactory 来封装 4 种策略,使得策略没有对外暴露,我们也了解到设计模式之间具有互补的关系,有些时候并不是单独存在的。
行为型模式之六:迭代器模式
它提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。
(来自《设计模式之禅》)
你要的故事
大家伙听歌频率高么?是不是经常听歌曲来放松心情?我是经常会听歌,心情不好的时候听歌,心情好的时候也听歌。。。今天讲的迭代器模式,我们就拿听歌这件事来说说,大家都知道听歌有几种模式:单曲循环、列表循环、随机等等。。。现在网易云音乐还多了一个心动模式。
既然说到迭代器模式,那这里就要着重讲讲列表循环这个听歌模式,其他的就先抛到脑后。在列表循环中,歌曲从第一条播放到最后一条,也就是一个遍历歌单的过程。我们有 2 种实现方式,一种是没有迭代器,通过获取歌单,用 for 循环遍历每一个歌曲,然后播放;另外一种是使用迭代器,获取歌单的一个迭代器,通过迭代器来遍历每一个歌曲,然后播放。下面我们就用代码来实现这 2 种方式。
木有迭代器
public class NoIteratorTest { public static void main(String[] args) { NetEaseMusic1 netEaseMusic1 = new NetEaseMusic1(); netEaseMusic1.listenToMusicByLoop(); } } /** * 网易云音乐 */ class NetEaseMusic1 { private IList1 songList; public NetEaseMusic1() { songList = new SongList1(3); songList.add(new Song("让我留在你身边", "陈奕迅")); songList.add(new Song("你曾是少年", "SHE")); songList.add(new Song("Perfect", "Ed Sheeran")); } /** * 列表循环 */ public void listenToMusicByLoop() { for (int i = 0; i < songList.size(); i++) { System.out.println("听歌:" + ((ISong)songList.get(i)).getSongInfo()); } } } /** * 容器接口 */ interface IList1 { void add(Object object); Object get(int index); int size(); } /** * 歌单 */ class SongList1 implements IList1 { private ISong[] songs; private int index; private int size; public SongList1(int size) { songs = new ISong[size]; index = 0; size = 0; } @Override public void add(Object object) { songs[index++] = (ISong) object; size ++; } @Override public Object get(int index) { if (index < size) { return songs[index]; } return null; } @Override public int size() { return size; } } /** * 歌曲接口 */ interface ISong { String getSongInfo(); } /** * 歌曲 */ class Song implements ISong{ private String name; private String singer; public Song(String name, String singer) { this.name = name; this.singer = singer; } @Override public String getSongInfo() { return this.name + "--" + this.singer; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSinger() { return singer; } public void setSinger(String singer) { this.singer = singer; } } 打印结果: 听歌:让我留在你身边--陈奕迅 听歌:你曾是少年--SHE 听歌:Perfect--Ed Sheeran
我们定义了 ISong 接口,里面有个 getSongInfo() 方法来获取歌曲信息,用 Song 类来定义歌曲。没有用 Java 自带的容器来存放歌曲,这里实现了一个自定义容器接口 IList1,定义 SongList1 来做歌曲的容器,为什么不用 Java 自带的 ArrayList 等等?因为 Java 自带的已经实现了迭代器功能了,我们这里自定义其实就是在模仿自带的容器的实现。NetEaseMusic1 类是充当网易云音乐客户端,在 listenToMusicByLoop() 方法中,我们可以看到是获取了歌单 songList,然后一个一个遍历,这是没有使用迭代器的代码。
下面看一下使用迭代器的代码是怎么样的。
用迭代器实现遍历
public class IteratorTest { public static void main(String[] args) { NetEaseMusic2 netEaseMusic2 = new NetEaseMusic2(); netEaseMusic2.listenToMusicByLoop(); } } /** * 网易云音乐 */ class NetEaseMusic2{ private IList2 songList; public NetEaseMusic2() { songList = new SongList2(3); songList.add(new Song("让我留在你身边", "陈奕迅")); songList.add(new Song("你曾是少年", "SHE")); songList.add(new Song("Perfect", "Ed Sheeran")); } /** * 列表循环 */ public void listenToMusicByLoop() { IIterator iterator = songList.iterator(); while (iterator.hasNext()) { System.out.println("听歌:" + ((ISong)iterator.next()).getSongInfo()); } } } /** * 容器接口 */ interface IList2 { IIterator iterator(); void add(Object object); Object get(int index); int size(); } /** * 歌单 */ class SongList2 implements IList2 { private ISong[] songs; private int index; private int size; public SongList2(int size) { songs = new ISong[size]; index = 0; size = 0; } @Override public IIterator iterator() { return new IteratorImpl(this); } @Override public void add(Object object) { songs[index++] = (ISong) object; size ++; } @Override public Object get(int index) { if (index < size) { return songs[index]; } return null; } @Override public int size() { return size; } } /** * 迭代器 */ interface IIterator { Object next(); boolean hasNext(); } /** * 迭代器实现类 */ class IteratorImpl implements IIterator { private IList2 list; private int index; public IteratorImpl(IList2 list) { this.list = list; this.index = 0; } @Override public Object next() { return list.get(index++); } @Override public boolean hasNext() { if (index < list.size()) { return true; } return false; } } 打印结果: 听歌:让我留在你身边--陈奕迅 听歌:你曾是少年--SHE 听歌:Perfect--Ed Sheeran
代码中我们自定义了一个迭代器接口 IIterator 和迭代器具体实现类 IteratorImpl,有关键的 2 个方法,hasNext() 判断是否有存在下一个元素,next() 获取下一个元素。而 IList2 接口则比 IList1 接口多了一个获取迭代器的方法 iterator(),这让网易云音乐在遍历歌单的时候,不用直接使用 songList 来遍历,而可以通过 songList.iterator() 获取迭代器来实现遍历的过程。NetEaseMusic2.listenToMusicByLoop() 这个方法里面就直接获取迭代器来遍历了。