Java设计模式实现烹饪助手程序
一. 程序设计目标和使用说明
1、程序设计目标
该程序在eclipse4.6.3版本中完成,用的是jdk1.7。
该程序的设计目的是为了学习java设计模式,应用其中的几个模式编写一个程序,在编写程序的过程中亲身实践相应设计模式,学习体会。
该程序的设计目标是完成一个采购食材的基本流程。程序根据用户选择的菜单,给出需要购买的食材,并计算费用,用户付款后,通知超市工作人员接单,用户只需取货即可,使食材推荐、采购、付款一键完成,为用户节省了大量时间。
该程序的角色包括用户、超市经理和超市工作人员。程序用到了抽象工厂模式、装饰器模式、策略模式和观察者模式。
2、使用说明
①界面展示
如图1所示,程序界面包括六个部分:
1、 最上面是收费策略提示区,展示了优惠信息。
2、 菜单选择板,包含用户可选菜单,可选择多个菜单。
3、 已选菜品展示板,根据用户选择的菜单,展示需要购买的食材和价格。
4、 折扣展示板,展示用户享受的折扣、总额、打折后的实付金额。
如图2所示,当订单金额不满50元时,显示无折扣。
如图3所示,当订单金额满足折扣条件时,会显示相应的折扣。
5、 下单按钮,用户点击下单。
6、 订单信息,当用户点击下单按钮后,展示订单信息。
如图4所示,用户点击下单,会在订单按钮下方展示当前订单信息。
②程序展示
如图5所示,在程序设计中有五个包。
1.cookHelper包。如图6所示,CookHelper.java中包含main方法:
在该包中导入前三个包,进行综合调用,实现该程序的各项功能。
2.drinkType 包。如图7所示,中间文件包括:
在drinkType包中,利用装饰器模式,为用户订单添加饮料,动态地扩展现有菜单内容,其中ChooseDrink是一个作为装饰类的抽象类,Cola、Juice和MilkTea是实现了该接口的具体类,当商家需要添加饮料的种类时,直接添加一个实现ChooseDrink接口的具体类,然后在相应的方法中调用即可。
3.foodType 包。如图8所示,中间文件包括:
利用抽象工厂模式将菜品的创建过程进行优化,建立多个工厂类,其中Food是一个抽象工厂,ChineseFoodA、ChineseFoodB、ThaiFoodA、ThaiFoodB是该工厂的实现类,CookFood提供了调用工厂类的接口,CookChineseFoodA、CookChineseFoodB、CookThaiFoodA、CookThaiFoodB是该接口的具体实现。如果需要增加菜单,可通过添加新的工厂类实现。
4.payMoney包,如图9所示,中间文件包括:
利用策略模式,对折扣类别进行划分。并且在程序运行的时候,调用相关方法,使用相关算法进行计算,得出用户实付金额。其中Context是策略模式的抽象类,提供折扣的接口,DiscountA、DiscountB、DiscountC是折扣的具体类,定义了不同的折扣类型。当商家需要添加新的折扣时,直接添加一个新的类,并实现Context接口即可。
5.superMarket包,如图10所示,中间文件包括:
在superMarket包中,是利用观察者模式实现超市的订单管理。经理作为subject,超市工作人员作为Observer,订阅信息。在上述文件中,ManagerSubject为经理的抽象类,是抽象主题角色,Manager表示具体的经理,即被观察者;WorkerObserve表示观察者抽象类,是抽象观察者对象,WorkerA、WorkerB、WorkerC表示超市工作人员,即观察者。在信息改变的时候,由经理通知所有的超市工作人员,以便所有的超市工作人员得到最新的信息,在业务方面不会出错。
二. 模板及其描述
本程序中综合运用了抽象工厂模式,装饰器模式,策略模式和观察者模式。下面就四个模式分别进行说明。
① 抽象工厂模式(Abstract Factory):
工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
普通工厂模式存在一个问题就是,类的创建依赖工厂,如果想扩展程序,必须对工厂类进行修改,违背了闭包原则。而抽象工厂模式可以很好地解决这一问题。
抽象工厂模式创建多个工厂类,当需要增加新功能时,直接增加新的工厂类即可,不需要修改之前的代码。
如图11所示,例如发送邮件和发送消息时,有一个邮件工厂和消息工厂,用于提供邮件和消息,同时提供了一个发送的接口,由发送邮件和发送消息的类来实现该接口展示了抽象工厂模式各部分以及它们之间的关系,参照该示例,我们可以用于设计本次菜单的创建工作:
在此程序中,对菜单运用了抽象工厂模式。
具体的菜品为一个实现类:
public interface Food {public String cook();
}
在实现类中,拥有一个共同的方法,即烹饪cook()。
后面有四种不同的菜分别实现此接口,并返回对应菜品需要购买的食材和单价,其中一个实现类如下所示:
public class ChineseFoodA implements Food{@Overridepublic String cook() {return "青椒5元+猪肉15元";}
}
烹饪菜品为一个工厂类,提供一个烹饪的实际操作接口,并返回一个菜品类型:
public interface CookFood {public Food cookFood();
}
工厂类的实现类有四个,实现了烹饪食材的接口,并返回对应的菜品。其中一个如下所示:
public class CookChineseFoodA implements CookFood{public Food cookFood() {return new ChineseFoodA();}
}
抽象工厂模式很好地实现了面向接口编程,菜单作为一个工厂,当需要在菜单中添加新的菜品时,直接添加一个工厂类和一个实现类即可。
② 装饰器模式(Decorator):
装饰模式能够实现动态的为对象添加功能,是从一个对象外部来给对象添加功能。它基于对象组合的方式,可以很灵活的给对象添加所需要的功能。装饰器模式的本质就是动态组合。动态是手段,组合才是目的。
装饰模式是通过把复杂的功能简单化,分散化,然后再运行期间,根据需要来动态组合的这样一个模式。
如图12所示,展示了抽象工厂模式各部分以及它们之间的关系,其中Source类是被装饰类,Decorator类是一个装饰类,可以为Source类动态的添加一些功能:
在此程序中,对饮料运用了装饰器模式。
在本程序中,用户选择的菜品Food是被装饰类,而在drinkType包中的ChooseDrink是一个作为装饰类的抽象类,实现了被装饰类的方法,如下所示:
public abstract class ChooseDrink implements Food{public String cook(){return null;}
}
后面有三种不同的饮料分别实现此接口,并返回对应饮料需要购买的单价,其中一个实现类如下所示:
public class Cola extends ChooseDrink{public String cook(){return "可乐5元 ";}
}
装饰器模式使得用户可以动态地在菜单中添加或删除饮料,当需要添加新品种的饮料是,直接添加一个装饰类的实现类即可。
③ 策略模式(Strategy Pattern):
策略模式中体现了两个非常基本的面向对象设计的原则:1、封装变化的概念;2、编程中使用接口,而不是对接口实现。
策略模式属于对象行为型设计模式,主要是定义一系列的算法,把这些算法一个个封装成拥有共同接口的单独的类,并且使它们之间可以互换。策略模式使这些算法在客户端调用它们的时候能够互不影响地变化。
策略模式带来的好处是,它将算法的使用和算法本身分离,即将变化的具体算法封装了起来,降低了代码的耦合度,系统业务策略的更变仅需少量修改。
如图13所示,为策略模式的结构,要使算法拥有共同的接口,就要实现一个接口或者一个抽象类出来才行。这样结构的轮廓也就出来了,可用简单的类图来表示它们之间的关系:
策略模式由三个角色组成:
1) 算法使用环境角色:算法被引用到这里和一些其它的与环境有关的操作一起来完成任务;
2) 抽象策略角色:规定了所有具体策略角色所需的接口。在java它通常由接口或者抽象类来实现;
3) 具体策略角色:实现了抽象策略角色定义的接口。
在此程序中,对折扣的计算运用了策略模式。
其中,算法使用环境角色为包含main方法的CookHelper,其中使用策略模式的代码如下所示:
Context context;
if (totalMoney >= 50 && totalMoney < 100) {context = new Context(new DiscountA());t2.setText(context.sale()+"\n总额:"+totalMoney+"元\n实付:"+(totalMoney-5)+"元");
} else if (totalMoney >= 100) {context = new Context(new DiscountB());t2.setText(context.sale()+"\n总额:"+totalMoney+"元\n实付:"+(totalMoney-15)+"元");
} else {t2.setText("无折扣\n总额:"+totalMoney+"元\n实付:"+totalMoney+"元");
}
在Discount接口中定义了抽象角色,其中定义了一个sale方法,返回一个字符串类型,当用户选好菜单后,该方法根据总价格计算用户享受的折扣,并计算用户的实付金额,代码如下所示:
public interface Discount {public String sale();
}
在代码中,具体策略角色有三个,分别实现了抽象策略角色的接口,表示不同种类的折扣。当超市需要添加新的折扣策略时,直接添加具体策略角色类,并实现抽象角色类的接口即可,其中一个具体角色类的代码如下所示:
public class DiscountA implements Discount{@Overridepublic String sale() {return "满50减5";}
}
④ 观察者模式(Observer Pattern):
观察者模式处理的是类与类之间的关系,不涉及到继承,它的工作过程是,当一个对象变化时,其它依赖该对象的对象都会收到通知,并随着变化,作出相应的反应。因此,在设计一组依赖的对象与它们所依赖的对象之间一致(同步)的交流模型时,观察者模式很有用。它可以使依赖对象的状态与它们所依赖的对象的状态保持同步。这组依赖的对象指的是观察者(Observer),它们所依赖的对象 称为主题(Subject)。为了实现观察者(Observer)的状态与主题(Subject)保持同步,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己。
由于给定主题的观察者链表需要动态的变化,因此一个主题不能维护一个静态的观察者链表。因此关注于主题状态的任何对象都需要明确地注册自己为主体的一个观察者。主题状态发生的变化,都需要通知所有的以注册的观察者。从主题接到通知以后,每一个观察者查询主题,使自己的状态与主题的同步。因此一个主题扮演着 发布者的角色,发布信息到所有的以订阅的观察者。
因此,主题和它的观察者之间包含了一对多的关系。当主题的实例的状态发生变化时,所有的依赖于它的观察者都会得到通知并更新自己。每一个观察者对象需要向主题注册,当主题的状态发生变化的时候得到通知。一个观察者可以注册或者订 阅多个主题。
在观察者模式里有如下的角色:
1) 抽象主题角色:抽象主题角色定义了一个接口,把所有的观察者对象的引用保存在一个列表里,每个主题都可以有任何数量的观察者。主题提供一个接口可以加上或者撤销观察者。
2) 抽象观察者角色:抽象观察者角色为所有的具体观察者定义一个接口,在得到通知时更新自己。
3) 具体主题角色:又称为具体被观察者角色,保存对具体观察者对象有用的内部状态;在这种内部状态改变时给其观察者发出一个通知。
4) 具体观察者角色:保存一个指向具体主题对象的引用;和一个与主题的状态相符的状态。具体观察者角色实现抽象观察者角色所要求的更新自己的接口,以便使本身的状态与主题的状态自恰。
如图14所示为观察者模式的类图结构,MySubject类就是我们的主对象,即具体的主题角色,Observer1和Observer2是依赖于MySubject的对象,用于观察MySubject的动态,当MySubject变化时,Observer1和Observer2必然变化。AbstractSubject类为抽象主题角色,定义着需要监控的对象列表,可以对其进行修改:增加或删除被监控对象,且当MySubject变化时,负责通知在列表内存在的对象:
在此程序中,对超市运用了观察者模式。
在本设计中,超市经理需要接受订单,然后通知所有超市工作人员,使超市工作人员配齐用户用户的订单,该工作方式很适合使用观察者模式。
如图15所示为本设计中实现观察者模式的各个接口和类,
其中ManagerSubject为经理的抽象类,是抽象主题对象,在该接口中定义着一些方法,可用于添加或删除观察者,通知观察者以及一些自身的操作,代码部分如下所示:
public interface ManagerSubject {/*增加观察者*/ public void add(WorkerObserver workerObserver); /*删除观察者*/ public void del(WorkerObserver workerObserver); /*通知所有的观察者*/ public List<String> notifyObservers(); /*自身的操作*/ public void operation();
}
Manager类表示经理,是抽象主题对象ManagerSubject的具体实现,在该类中,定义了监控对象的列表,实现了ManagerSubject类中增加观察者、删除观察者、通知所有观察者的具体方法,以及一些自身的操作。代码部分如下所示:
public class Manager implements ManagerSubject{private Vector<WorkerObserver> vector = new Vector<WorkerObserver>();@Overridepublic void add(WorkerObserver workerObserver) {// TODO Auto-generated method stubvector.add(workerObserver);}@Overridepublic void del(WorkerObserver workerObserver) {// TODO Auto-generated method stubvector.remove(workerObserver);}@Overridepublic List<String> notifyObservers() {// TODO Auto-generated method stubList<String> aList = new ArrayList<String>();Enumeration<WorkerObserver> enumo = vector.elements(); while(enumo.hasMoreElements()){ aList.add(enumo.nextElement().update("")); }return aList;}@Overridepublic void operation() {// TODO Auto-generated method stub}
}
WorkerObserver为工作人员的抽象类,是抽象观察者角色,它为所有的具体观察者定义一个接口,在得到通知时更新自己,代码部分如下所示:
public interface WorkerObserver {public String update(String newOrder);
}
WorkerA和WorkerB表示工作人员,它实现类抽象观察者角色中的update方法,该方法接收一个字符串,表示订单的具体信息,在方法结束时返回一个字符串,表示该工作人员已经接收到了该订单。其中WorkerA的代码部分如下所示:
public class WorkerA implements WorkerObserver{@Overridepublic String update(String newOrder) {// TODO Auto-generated method stubreturn "工作人员A收到订单" + newOrder;}
}
以上及为本程序中具体运用的四个模式的说明,有对模式的理论说明还有针对本程序的实际讲解。由于篇幅所限制,不便把所有的代码贴上,具体请参看程序代码。
三. 总结
首先,设计模式是建立在某一门语言的基础之上的,设计模式是程序大牛对编程方式的总结,所以,要想很好地使用设计模式,必须要能十分熟练地使用这门语言进行编程。在本次设计中,我使用的是基于java语言的设计模式,java是一门面向对象的语言,能灵活的运用抽象类,接口,抽象方法,静态方法等等,所以,要想用好设计模式,必须首先对java语言的使用有一个深入的了解,并且十分熟练。因此,学习设计模式需要扎实的语言功底。
在初学设计模式并尝试编写代码时,我觉得很疑惑,因为很多时候一个类和几个方法就可以解决的问题,我们为什么要编写接口和多个类,这样是否提高了程序的复杂性,使得我们所写的程序不易于理解?但是,当多次使用设计模式,将多个设计模式用到一个项目中,并且项目达到一定规模时,我理解了设计模式给我们带来的极大便利之处。使用设计模式,我们就可以在自己进行程序设计、类的编写时可以去复用程序大牛所设定的设计模式,为解决自己的问题找到更好的方法避免走弯路,直接利用大牛的设计模式可以节省设计解决方案的时间,GOF提出的23种设计模式涉及的范围很广,很多时候我们都可以直接找到一个适合的设计模式。
当然,我们学设计模式,是为了学习如何合理的组织我们的代码,如何解耦,如何真正的达到对修改封闭对扩展开放的效果,而不是去背诵那些类的继承模式,不能一味复用,而牺牲了代码的性能,应该根据实际情况,总结出一个对自己的程序最有效的方法。
只有掌握住设计模式的核心思想,才能正确、灵活的应用设计模式,否则再怎么使用设计模式,也不过是生搬硬套。总之,学习设计模式对于一个面向对象的程序设计人员来说是十分必要的,而且在学习的过程中我们也能发现很多编程的乐趣。