设计模式之策略模式
在了解了设计模式的概念后,下面我们对设计模式中的“策略模式”进行详细讲解。
诞生缘由
对象的某些行为方式,多种多样,而不同的对象可能又拥有相同的行为方式。为了适应对象行为方式的多变性与复用性,“策略模式”诞生了,
它将对象与对象的行为方式相互独立,任何对象都可以使用已经封装好的行为方式,从而达到代码的复用性。
当某一种行为方式需要更改的时候,它的变化是独立于调用它的对象,不需要更改对象类的代码;
当有新的对象需要新的行为方式的时候,可以在不改动原来已有的行为的前提下为新对象扩展新的行为方式,该行为方式也可以被其他任意对象使用。
啊....哦.....好像不太明白?别担心,这纯属正常。
这么说吧,人的行走方式,小宝宝的时候行走是靠“爬”;刚会走路时候,行走是两条腿“跌跌撞撞,还时不时摔个跟头”;青少年的时候,行走是“稳定,随风奔跑”;
年老后,行走是“与蜗牛同行,弯腰驼背亦感累”。都是行走,但是一个人不同阶段,他的行走方式是不一样。
“策略模式”就是要解决人们拥有可变性的行为。
大部分宝宝都是靠爬,如果创建一批对象,假设都是宝宝阶段,为每个对象都设置“爬”的行走方式,那代码的重复率就很高。
“策略模式”就是要解决这一群“爬”的行为,让这个行为的代码进行复用。
在“策略模式”中,以上的行为方式,我们称之为“算法”。下面给出策略模式的定义:
定义
为对象定义一系列行为(算法),并对算法进行封装,对象可以随意切换自己的行为方式(切换策略),策略模式让算法的变化独立于调用该算法的对象。
通关文牒
或许你现在对策略模式有一些初步的认识了,那就带上通关文牒当一次唐僧吧!不取经不罢休,要做一个知其然而知其所以然的“佛”。本次关卡如下:
- 如何抽取算法来适应多变性?
- 如何封装算法使得代码复用?
背景
A公司打算上市一款叫“欢乐汪汪队”的游戏,目前该游戏只有2名成员,斑点汪与泰迪汪。
斑点汪与泰迪汪都属于狗狗类,具有狗狗的一些特性,都会叫,会摇尾巴,还有自己独特的外观。
此系统使用了Java标准的OO技术,对所有的狗狗进行了超类的封装,代码如下:
/**
* 所有狗狗的父类
*/
public abstract class Dog {
public void bark(){
System.out.println("汪汪汪");
}
public void wagging(){
System.out.println("摇尾巴");
}
// 外观,由于不同的狗狗,外观不一样,所以需要子类自行去实现
public abstract void display();
}
/**
* 斑点汪
*/
public class SpeckleDog extends Dog {
@Override
public void display() {
System.out.println("我是斑点汪,外观是浑身有斑点");
}
}
/**
* 泰迪汪
*/
public class PoodleDog extends Dog {
@Override
public void display() {
System.out.println("我是泰迪汪,外观是小巧可爱长不大");
}
}
增加需求
现在,公司产品决定给狗狗加上狗刨式游泳的功能,用于游戏中进行狗狗游泳比拼。这还不简单??键盘伺候!!!
我们只需要在所以狗狗的父类中加一个游泳的方法就可以了,这样继承了该类的所有子类都具备了游泳的功能。
/**
* 所有狗狗的父类
*/
public abstract class Dog {
public void bark(){
System.out.println("汪汪汪");
}
public void wagging(){
System.out.println("摇尾巴");
}
// 外观,由于不同的狗狗,外观不一样,所以需要子类自行去实现
public abstract void display();
// 本次新增方法,游泳
public void swim(){
System.out.println("狗刨式游泳");
}
}
完美解决,就在大家游戏开始进行游泳比拼的时候,一个用户的汪汪竟然挂掉了,检查发现,公司不知道何时上了一只柯基汪,该狗狗由于腿短屁股大,不能进行游泳,下水没几下就会挂掉。
问题引出
接下来该怎么办呢?要不让柯基汪覆盖父类的swim方法,变成在岸边呆着,不参加游泳,给其他狗狗鼓掌加油就好。
貌似没问题,可是如果后期公司又上橡皮狗,会游泳,会叫但是不会摇尾巴。该怎么办呢?
橡皮狗,会游泳,会叫但是不会摇尾巴;电子狗会叫,会摇尾巴,但是不会游泳
大家第一反应是不是想到了Java的接口,我们可以把游泳这个行为设计成“Swimable接口”,
同样也本可以把摇尾巴这个动作设计成“Waggingable接口”,因为游戏里的所有狗狗不一定都会摇尾巴。
欲哭无泪
不改不知道啊,这真是一个超级笨的主意,这么一来,重复的代码很多。因为接口没有具体的实现方法,无法达到代码复用的目的,
就是说如果想要修改一下游泳,你必须进到每个实现类里面去修改,如果你认为覆盖几个方法就算差劲,
那么对于30个狗狗让你都要修改一下游泳这个行为,你又怎么处理?
我们了解到了,并非所有狗狗都会游泳与摇尾巴,所以继承并不是适当的解决方式。改成接口,可以解决“一部分”问题(不会出现会游泳的柯基汪),但是却造成代码无法复用。
这只能算是刚出了狼山又入了虎口吧。
撸起袖子加油干
别担心,方法总比困难多。我们已经知道继承并不是很好的处理方式,因为狗狗的功能是变化的,那我们把不变的功能抽离到父类里面去肯定是没问题的,对于变化的功能,我们发现接口并不是很好的处理方式,因为接口无法达到代码复用的目的,那我们就想办法让它被复用起来。
设计模式的精神:把不变的行为进行混合,把变化的行为进行独立。
独立变化的行为
我们先解决上面接口的代码无法复用的问题,针对游泳与摇尾巴,我们建立两个接口Swimable与Waggingable,并且为其定义实现类。
游泳行为的接口
/**
* 游泳行为的接口
*/
public interface Swimable {
void swim();
}
以下是几种游泳方式的具体实现:
/**
* 狗刨式游泳
*/
public class SwimDoggyPaddle implements Swimable {
@Override
public void swim() {
System.out.println("狗刨式游泳");
}
}
/**
* 自由泳式的游泳
*/
public class SwimFreeStyle implements Swimable {
@Override
public void swim() {
System.out.println("自由泳式的游泳");
}
}
/**
* 不会游泳的狗狗
*/
public class SwimNoWay implements Swimable {
@Override
public void swim() {
System.out.println("我不会游泳,我就站在水边为你们加油鼓掌");
}
}
摇尾巴的接口:
/**
* 摇尾巴行为的接口
*/
public interface Waggingable {
void wagging();
}
以下是摇尾巴行为的具体实现
/**
* 左右摇尾巴
*/
public class WaggingLeftAndRight implements Waggingable {
@Override
public void wagging() {
System.out.println("左右摇摆自己的尾巴");
}
}
/**
* 不会摇尾巴的狗狗
*/
public class WaggingNoWay implements Waggingable {
@Override
public void wagging() {
System.out.println("我不会摇尾巴");
}
}
行为已经有了,具体的实现代码也有了,不过好像和我们的狗狗还没有任何关系,我们怎么让代码复用起来呢?修改父类:
混合不变的行为
/**
* 所有狗狗的父类
*/
public abstract class Dog {
// 声明两个成员变量
private Waggingable waggingable; // 摇尾巴行为
private Swimable swimable; // 游泳行为
public void bark(){
System.out.println("汪汪汪");
}
// 调用接口的摇尾巴行为
public void wagging(){
waggingable.wagging();
}
// 外观,由于不同的狗狗,外观不一样,所以需要子类自行去实现
public abstract void display();
// 调用接口的游泳行为
public void swim(){
swimable.swim();
}
}
看看斑点汪的处理:
/**
* 斑点汪
*/
public class SpeckleDog extends Dog {
public SpeckleDog(){
this.swimable = new SwimDoggyPaddle(); // 狗刨式的游泳
this.waggingable = new WaggingLeftAndRight(); // 左右摇摆尾巴
}
@Override
public void display() {
System.out.println("我是斑点汪,浑身有斑点");
}
}
柯基汪的处理:
/**
* 柯基汪
*/
public class CokeyDog extends Dog {
public CokeyDog(){
this.swimable = new SwimNoWay(); // 不会游泳
this.waggingable = new WaggingLeftAndRight(); // 左右摇摆尾巴
}
@Override
public void display() {
System.out.println("我是柯基汪,腿短屁股大");
}
}
好了,现在任凭公司产品怎么变,哪天想把尾巴试用转圈圈的方式摇起来都没问题,这种天马行空之旅就交给公司吧!我们已经处理好了后期的应变之策。
总结
案例解析
本例中游泳和摇尾巴的方式,我们可以理解为不同的算法,将其封装后,任何对象(狗)都可以使用和替换这些算法。当需要其他方式的游泳或者摇尾巴
的行为时,我们只需要新增或者修改算法类,而不用去动到使用者本身的代码。
适用场景
“策略模式”适用于对象的行为经常会发生未知的变化,这时候需要将这个行为抽离出来进行封装,让它的改变不影响调用它的对象本身。
比如超市做优惠活动,五一节可能打五折,国庆节打八折,春节既打折还是送礼品,
这样的优惠活动的折扣算法经常会发生变化,可以采用策略模式,不同节日采用不同的策略。
优缺点
- 优点:很好地适应了对象行为方式的多变,算法的变化独立于调用它的对象,大大系统的降低了耦合;对算法进行封装,使用算法时采用组合的方式,大大提高了代码的复用性。
- 缺点:策略模式侧重于策略的切换,对算法的延伸性不是很友好,假设需要2种比较类似的算法,为了不影响之前的算法,需要新增一个新的算法。当侧重于算法本身的延伸性时,可以考虑设计模式中的“模板方法模式”。
本文的“策略模式”只是设计模式的入门,仅仅这一种模式就可以让我们清晰地认识到学好设计模式的重要性,加油,努力让自己写出更加优秀的代码。
Q.E.D.
Comments | 1 条评论