设计模式之策略模式

最近在读一本《Head First 设计模式》书,书中用生动形象的例子来介绍设计模式,个人认为还是很不错的书,接下来会利用书中的例子介绍一些常用的设计模式,本文主要是设计模式入门+策略模式介绍。
一.模拟鸭子应用
1.背景
公司开发了一款很火爆的模拟鸭子游戏(具体怎么火起来的就先不关心啦),游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫,开始时我们的设计时这样的:
我们设计了一个抽象类Duck,包含quick方法(呱呱叫),swim方法(游泳)和display方法,一些鸭子的具体实现类,如MallardDuck,RedheadDuck等鸭子类,重写display方法,这样就满足当前系统的需要了。
2.变动
由于鸭子游戏过于火爆,很多公司开始抄袭我们的创意,竞争压力过大,因此主管们认为我们需要创新,决定让我们的鸭子可以飞。so,苦逼的我们需要改代码了,我们会怎么做呢?我们初步打算在抽象类Duck中加入fly方法试试吧。我们把Duck中加入了fly方法,上线第二天后,一个可怕的现象发生了,很多游戏中的橡皮鸭子在地图上满天飞(橡皮鸭不应该会飞),这是怎么回事呢?我们忽略了一个事实,并非Duck所有的子类都会飞,我们在Duck类中加上新的行为,会使得某些并不适合该行为的子类也具有该行为。对代码所做的局部修改,影响层面可不只是局部。为了解决橡皮鸭乱飞的问题,我们会想到覆写橡皮鸭的fly方法,可是如果以后我加入诱饵鸭,或者其他木头鸭子呢?情况变的有些复杂起来了。
3.一个解决方案
这时我们会想,fly放到Duck里貌似不是一个好的解决办法,我们可以把fly从超类中取出来,放进一个"Flyable接口"中,这样一来,只有会飞的鸭子才实现这个接口,同样的我们也可以设计一个"Quackable接口",因为并不是所有的鸭子都会叫。这种方式看起来可以解决上面的问题了。不过这种方式有什么问题呢?使用接口的实现没办法进行代码复用,这样会导致重复的代码变的非常多,考虑我们有大量的鸭子实现类,修改起来会非常麻烦。
4.把问题归零......
现在我们知道使用继承并不能很好的解决问题,因为鸭子的行为在子类里不断地改变,并且让所有的子类都有这些行为是不恰当的。Flyable与Quackable接口一开始似乎还不错,但是Java接口不具有实现代码,所以继承接口无法达到代码的复用。这意味着,无论何时需要修改某个行为,必须得往下追踪并在每一个定义此行为的类中修改它,一不小心就会造成新的错误。
有一个设计原则恰好适用此状况。 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
换句话说,如果每次新的需求一来,都会使某方面的代码发生变化,那么你就可以确定,这部分的代码需要被抽出来,和其他稳定的代码有所区分。
5.重新设计鸭子游戏
了解了上面的设计原则,我们知道把"变化和不会变化的部分"分开,我们准备简历两组类,一个是fly相关的,一个是quack相关的,因为Duck类内的fly和quack会随着鸭子的不同而改变。重新设计鸭子游戏之前,我们再来了解一个设计原则:针对接口编程,而不是针对实现编程。这里我们定义两个接口,FlyBehavior和QuackBehavior接口,再定义一些具体的实现类,如FlyWithWings,FlyNoWay,Squeak等实现类。重新设计之后,我们的鸭子包含两个属性,就是我们刚刚定义的两个接口,FlyBehavior和QuackBehavior。增加两个方法,performQuack和performFly方法来实现呱呱叫和飞行动作,方法的实现则为调用两个属性的具体实现。下面来看看具体的代码实现:
public interface FlyBehavior {
     public void fly();
}
public class FlyWithWings implements FlyBehavior {
     @Override
     public void fly() {
          System. out.println("fly with wings" );
     }
}
public class FlyNoWay implements FlyBehavior{
     public void fly(){
          System. out.println("i can not fly" );
     }
}
public interface QuackBehavior {
     public void quack();
}
public class Quack implements QuackBehavior {
     @Override
     public void quack() {
          System. out.println("呱呱叫" );
     }
}
public class MuteQuack implements QuackBehavior {
     @Override
     public void quack() {
          System. out.println("i can not quack" );
     }
}

上面定义了两组接口,和两个具体的实现类,接下来看看我们的Duck类
public abstract class Duck {
     private FlyBehavior flyBehavior;
     private QuackBehavior quackBehavior;
     public void setFlyBehavior(FlyBehavior flyBehavior ) {
           this.flyBehavior = flyBehavior ;
     }
     public void setQuackBehavior(QuackBehavior quackBehavior) {
           this.quackBehavior = quackBehavior ;
     }
     
     public void swim(){
          System. out.println("i am swimming" );
     }
     public void performFly(){
           flyBehavior.fly();
     }
     public void performQuack(){
           quackBehavior.quack();
     }
     public abstract void display();
}

再定义一个Duck的实现类
public class MallardDuck extends Duck {
     public void display(){
          System. out.println("i am a real mallard duck" );
     }
}

接下来测试这段代码
public class MiniDuckTest {
     public static void main(String[] args) {
          Duck mallard = new MallardDuck();
           mallard.setFlyBehavior( new FlyWithWings());
           mallard.setQuackBehavior( new Quack());
           mallard.performFly();
           mallard.performQuack();
     }
}

二.策略模式
上面代码就使用了设计模式的策略模式,我们来看看策略模式的定义:
策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
策略模式的优缺点:
优点
1、可以动态的改变对象的行为
缺点
1、客户端必须知道所有的策略类,并自行决定使用哪一个策略类
2、策略模式将造成产生很多策略类
适用场景:
策略模式适用于经常变化的需求,如《大话设计模式 》中的示例:超市打折促销,超市经常有各种打折策略,经常变化,这种场景就很适合策略模式。再比如之前在介绍Lambda表达式中的例子"筛选苹果"需求,有时我们需要根据颜色筛选,有时我们需要根据重量筛选,再加上上面的鸭子游戏我们可以看到策略模式的功能是十分强大的。它能很好的将变化和不变的分离开,工作中如果有经常修改的部分我们可以考虑使用策略模式实现。

至少在在以下两种情况下,大家可以考虑使用策略模式:

  • 几个类的主要逻辑相同,只在部分逻辑的算法和行为上稍有区别的情况。
  • 有几种相似的行为,或者说算法,客户端需要动态地决定使用哪一种,那么可以使用策略模式,将这些算法封装起来供客户端调用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值