java设计模式之策略模式及项目中的应用

2020-11-24 10:38:54 浏览数 (1)

转载请注意出处: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类?

0 人点赞