转载请注意出处:http://blog.csdn.net/zcm101
设计模拟人生游戏
今天开始,我们LazyCoder准备着手开发一款模拟人生游戏,首先从设计人物开始,我们设想我们设计的人物可以讲话,吃东西,睡觉,他们的样子也都不一样。我们想到了继承,于是有了第一个类Person,之后我们再设计各种各样的人,家族里有很多人,有Father,Mother,Brother……他们的样子长得不一样,于是我们为每个人物设计一个类,他们都继承Person,并实现各自的display方法,display就交给美工们来做吧
。不会美工的童鞋举手,我第一个举手。
下面是我们第一个版本的代码:
代码语言:javascript复制package com.lazycode.v1;
public abstract class Person {
public void sleep(){
System.out.println("Person sleep");
}
public void eat(){
System.out.println("Person eat");
}
public void speak(){
System.out.println("Person speak");
}
public abstract void display();
}
代码语言:javascript复制package com.lazycode.v1;
public class Father extends Person {
@Override
public void display() {
System.out.println("Father display");
}
}
代码语言:javascript复制package com.lazycode.v1;
public class Mother extends Person {
@Override
public void display() {
System.out.println("Mother display");
}
}
代码语言:javascript复制package com.lazycode.v1;
public class Brother extends Person {
@Override
public void display() {
System.out.println("Brother display");
}
}
大家每天吃饱了睡,睡醒了吃,聊聊天,愉快的一天就过去了……
问题来了
随着模拟人生业务的发展,我们开始进军海外,于是,设计了个远房亲戚,来自美国的Sister,而我们现有的Person类,只支持讲中文。额—— 在Sister里覆盖下speak方法吧,让她讲英语。从此我们将暗无天日!随着人物的增多,我们每天都重复着为每个人加入讲英语的方法,而且是同一段代码,我要疯了,这可不是懒程序员的风格!而且每新建个人物时,我都得想一下,他应该讲中文还是讲英文呢,这我真的要疯了!!看来得改进我们的Person类了。
怎么办怎么办……
Person里加个国籍,在speak方法里加判断,根据国据,判断讲的语言? 疯了吧你!万一出现一个牛人,会八种语言怎么办?全世界上千种语言,你打算每加入一种新的语言就改下Person类吗,每改动一次Person类,对原有的代码就增加出风险的机率。再想想~~~
春天来了
我们发现所有中国人都讲汉语,所有美国人都讲英语,之后还会出现讲法语、德语的,我们可以每种语言都提炼成一个类,将speak提出成接口LanSpeak。一组语言类就这么出来了。
代码语言:javascript复制package com.lazycode.v2;
public interface LanSpeak {
public void speak();
}
代码语言:javascript复制package com.lazycode.v2;
public class ChiSpeak implements LanSpeak {
@Override
public void speak() {
System.out.println("Speak Chinese");
}
}
代码语言:javascript复制package com.lazycode.v2;
public class EngSpeak implements LanSpeak {
@Override
public void speak() {
System.out.println("Speak English");
}
}
那么如何将Person类和LanSpeak接口联系起来呢?
我们再思考,Father见到Sister后,Sister不会讲中文,我们得让Father讲英文,也就是说Father在不同的场景中,能够中英文切换,试着将LanSpeak变成Person类里的一个属性(你一定听过面向接口编程,对的,我们在Person类里用到的时LanSpeak接口,而不是具体的ChiSpeak,EngSpeak。),在运行过程中,设置LanSpeak为不同的LanSpeak实现类,不就可以讲不同的语言吗。看下我们重新设计出来的Person类:
代码语言:javascript复制package com.lazycode.v2;
public abstract class Person {
public LanSpeak lanSpeak;
public void sleep(){
System.out.println("Person sleep");
}
public void eat(){
System.out.println("Person eat");
}
public void speak(){
//输出具体讲话的人
System.out.println(this.getClass().getName() ":");
lanSpeak.speak();
}
public abstract void display();
public LanSpeak getLanSpeak() {
return lanSpeak;
}
public void setLanSpeak(LanSpeak lanSpeak) {
this.lanSpeak = lanSpeak;
}
}
我们加入了个lanSpeak属性,set,get,在speak中, 不在是直接输出讲话信息了,而是 委托给了lanSpeak,要讲什么语言由他来决定。
再来看具体的人物,他们在被创建的时候,我们就默认给他设置一种语言,Father讲中文,USASister讲英文,所以在构造方法里需传入LanSpeak实现类。
代码语言:javascript复制package com.lazycode.v2;
public class Father extends Person {
public Father(LanSpeak lanSpeak){
this.lanSpeak = lanSpeak;
}
@Override
public void display() {
System.out.println("Father display");
}
}
代码语言:javascript复制package com.lazycode.v2;
public class USASister extends Person {
public USASister(LanSpeak lanSpeak) {
this.lanSpeak = lanSpeak;
}
@Override
public void display() {
System.out.println("USASister display");
}
}
好了,代码改造得差不多了,来看看怎么让Father和Mother讲中文,然后和USASister讲英文
代码语言:javascript复制package com.lazycode.v2;
public class Main {
public static void main(String[] args) {
//初始化语言
ChiSpeak chiSpeak = new ChiSpeak();
EngSpeak engSpeak = new EngSpeak();
//初始化人物
Father father = new Father(chiSpeak);
Mother mother = new Mother(chiSpeak);
USASister usaSister = new USASister(engSpeak);
//father和mother交谈
father.speak();
mother.speak();
//来自美国的妹妹插话了
usaSister.speak();
//让father讲英文
father.setLanSpeak(engSpeak);
father.speak();
}
}
输出:
com.lazycode.v2.Father: Speak Chinese com.lazycode.v2.Mother: Speak Chinese com.lazycode.v2.USASister: Speak English com.lazycode.v2.Father: Speak English
策略模式就这么出来了,下面来看些理论上的东西吧。
概念
策略模式(Strategy):它定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法的变化不会影响到使用算法的客户。(原文:The Strategy Pattern defines a family of algorithms,encapsulates each one,and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.)
LanSpeak就是我们抽象出来的一组算法的接口,这组算法(ChiSpeak, EngSpeak)在程序运行的时候可以互相替换。
优点:很明显,扩展性很好;还有另一个隐性优点,这组语言类,脱离了Person,可以在其它地方用,比如广播。
缺点:很明显,扩展多了类就要爆棚了,而且客户端需要知道所有的算法,需要知道目前共实现了几种语言类。不过想比扩展性优点来说,这些还是可以容忍的。
策略模式一般会有三个角色:抽象策略角色(LanSpeak),具体策略角色(ChiSpeak),上下文(Person)
补充
回头再看看我们的代码,其实我们的Father类可以用v1包里的代码,不用做修改,因为我们的Person类里已经提供了SetLanSpeak方法了,每个人物都可以动态设置语言,而不用初始化就设置语言。我个人还是觉得这种写法会好点,我们现在只遇到了speak一组算法,以后可能会遇到move族算法,人物可以移动,但是每个人移动的方法不一样,有的是跑的,有的是走的,有的坐轮椅,有的骑自行车……还会有各种各样的算法族,难道每加一种算法族,所有的类的构造函数都改一遍?你这是要疯吧!!
项目实践
策略模式可以说是在项目中应用最多的模式之一,举一个最常见的例子,现在随便找个java项目,看看分层结构,是不是都会有一层service,一层dao,service里调用dao从而访问数据库。想一下,是不是有策略模式的影子了。
假设我们的项目有一个dao接口,叫CommonDao,有最基本的增删改查方法。我们再实现两个dao,分别是HibernateDaoImpl,JdbcDaoImpl,从名字就知道有什么区别吧。假如我们有一个PersonService,里面有个commonDao接口,我们通过Spring将HibernateDaoImpl注入到PersonService中。注意这个注入的过程,其实就是策略模式的体现,我们使用hibernateDaoImpl注入到PersonService,其实就是告诉service,我要使用hibernate来访问数据库这种策略。我们可以通过Spring的注入配置,来实现不同的策略。
假设哪天出现了个新的持久化框架,比如叫Lazycoder,那我们就可以实现LazycoderDaoImpl,然后将它通过Spring配置注入到PersonService中。
就做了两件事,增加一个策略,将这个策略代替旧的策略。So easy, 妈妈再也不用担心我的学习了。
思考
1. 随着业务需要,我们加入了德语,如何扩展?
2. 在补充里提到了给Person加入move动作,怎么重新设计Person类?