设计模式之策略模式(Strategy Pattern)

策略模式,是我们接触到的第一个设计模式,也是较容易理解的一个模式。
我们可以给它下一个定义:
** 定义了算法族,分别封装起来,让它们之间可以互相转换,此模式让算法的独立于使用算法的客户。**
维基百科上的定义是:** a software design pattern that enables an algorithm's behavior to be selected at runtime. **
维基百科上的强调了算法行为是在运行时决定的,这正是策略模式很关键的一点。

引子

假设我们现在要设计一个鸭子类Duck类,然后让不同的鸭子继承于它。我们把目光聚焦到鸭子的行为上。如果我们要给鸭子增加一个行为“fly”,第一个想法,在抽象类duck里添加一个fly方法就可,其余鸭子继承实现这个方法。
但是这就出现了一个问题,并不是所有鸭子都会飞,我们反而让一些本不具备这个fly行为的鸭子也具有该行为。那怎么办呢?
利用继承来提供鸭子的行为,会导致下面这些后果:

  • 代码在多个子类中重复,如果两类不同鸭子需要同一种fly行为,我们就要在两个类里分别覆盖两次,这样万一维护起来是非常困难的
  • 很难知道所有鸭子的全部行为
  • 运行时的行为不容易改变
  • 改变会一发动全身,造成其他鸭子不想要改变

设计原则1

软件开发中,我们常常需要遵守的设计原则是:
** 把可能需要变化的地方独立出来,不要和那些不需要变化的代码混在一起 **
这样代码变化引起的不经意后果变少,系统变得更有弹性
实际就是尽量让系统中某部分的改变不影响其他部分的变化。

提取鸭子的的行为

根据设计原则,鸭子飞行的行为会发生变化,所以我们需要将fly行为单独提取出来。同理,我们提取出两个鸭子可能变化的行为fly和quack鸭叫。用两组类分别代表fly和quack行为。

设计原则2

那么我们如何那两组鸭子行为的类呢?这里引出第二个我们提出的设计原则:
** 面对接口编程,而不是面对实现编程 **
这样就可以实现在运行时改变鸭子的行为。
我们不会直接指定特定的行为给鸭子。而是声明两个接口FlyBehavior和QuackBehavior。我们制造的其他一系列的类专门来实现FlyBehavior和QuackBehavior,这组就成为行为类,或者算法类。
用行为类来实现接口而不是利用duck类来实现。

Paste_Image.png

实现鸭子的行为

根据设计原则2,可以让飞行和鸭叫行为的动作被其他对象复用,因为这些为行为已经与鸭子类无关了。
而且当我们新增一些行为的时候,不会影响到既有的行为类,也不会影响鸭子类。太棒了!

Paste_Image.png

** 很多同学都觉得这里用类来代表行为是不是觉得很奇怪。在大家默认里,类应该是代表某种东西的,类应该拥有状态与行为。这里,我们需要纠正这个观点,一个行为也可以具有各种属性和函数。类不仅仅是用来代表东西事物的。 **

整合实现我们设计的鸭子类

首先,在duck类中加入两个实例变量,分别声明为两个接口的类型,每个鸭子对象都会动态的设置这些变量以便在运行时引用正确的行为类型

Paste_Image.png

duck类的实现

package strategyPattern;

//抽象的Duck类,所有鸭子都继承
public abstract class Duck {
    
    //为行为接口类型声明两个引用变量,所有鸭子类都继承它们
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;
    
    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }

    public Duck() {
        
    }
    
    public abstract void display();
    
    public void performFly() {
        flyBehavior.fly();  //委托给fly行为类实现
    }
    
    public void performQuack() {
        quackBehavior.quack();  //委托给quack行为类实现
    }
    
    //swim是所有鸭子都共同拥有的方法,所以可以直接在在duck类中实现
    public void swim() {
        System.out.println("All ducks float, even decoys!");
    }
}

FlyBehavior接口的实现:

package strategyPattern;

//所有飞行行为类必须实现的接口
public interface FlyBehavior {
    public void fly();
}

两个fly行为实现类,继承至FlyBehavior接口

package strategyPattern;

public class FlyWithWings implements FlyBehavior {

    @Override
    public void fly() {
        // TODO Auto-generated method stub
        System.out.println("I'm flying with wings!");
    }

}

package strategyPattern;

public class FlyNoWay implements FlyBehavior {

    @Override
    public void fly() {
        // TODO Auto-generated method stub
        System.out.println("I can't fly!");
    }

}

Quack接口及其三个行为实现类:

package strategyPattern;

public class Quack implements QuackBehavior {

    @Override
    public void quack() {
        // TODO Auto-generated method stub
        System.out.println("Quack");
    }

}

package strategyPattern;

public class MuteQuack implements QuackBehavior {

    @Override
    public void quack() {
        // TODO Auto-generated method stub
        System.out.println("<<silence>>");
    }

}

package strategyPattern;

public class Squeak implements QuackBehavior {

    @Override
    public void quack() {
        // TODO Auto-generated method stub
        System.out.println("Squeak");
    }

}

package strategyPattern;

public interface QuackBehavior {
    public void quack();
}

一个MallardDuck类继承至Duck类:

package strategyPattern;

public class MallardDuck extends Duck {

    public MallardDuck() {
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }
    
    @Override
    public void display() {
        // TODO Auto-generated method stub
        System.out.println("I'm a real Mallard duck!");
    }

}

测试类

package strategyPattern;

public class MiniDuckSimulator {
    public static void main(String[] args) {
        Duck mallard = new MallardDuck();
        //这里调用mallardduck继承来的performFly方法,进而委托给该对象的quackBehavior对象处理
        //也就是最后是调用了继承来的quackBehavior引用对象的quack()方法
        mallard.performFly();
        mallard.performQuack();

    }
}

运行结果:

Paste_Image.png

在这里为了实现动态的改变鸭子的行为,我们可以新建一个flyrocketPowered行为类,然后动态的改变其行为:

package strategyPattern;

public class FlyRocketPowered implements FlyBehavior {

    @Override
    public void fly() {
        // TODO Auto-generated method stub
        System.out.println("I'm flying with a rocket ");
    }

}

package strategyPattern;

public class MiniDuckSimulator {
    public static void main(String[] args) {
        Duck mallard = new MallardDuck();
        //这里调用mallardduck继承来的performFly方法,进而委托给该对象的quackBehavior对象处理
        //也就是最后是调用了继承来的quackBehavior引用对象的quack()方法
        mallard.performFly();
        mallard.performQuack();
        
        
        Duck model = new ModelDuck();
        model.performFly();
        model.setFlyBehavior(new FlyRocketPowered());
        model.performFly();
    }
}

运行结果:

Paste_Image.png
Paste_Image.png

每一个鸭子都有一个FlyBehavior和一个quackBehavior,好将飞行和鸭叫委托给他们代为处理。
当你将两个类结合起来使用时,如同本例,这就是组合composition。这种做法和继承不同的地方在于,鸭子的行为不是继承来的而是和适当行为对象那个组合来的。
设计原则3:
** 多用组合 少用继承 **

策略模式总结

三个设计原则:

  • 封装变化,分开变化与不变
  • 多用组合,少用继承
  • 面向接口编程,而不是面对实现编程

策略模式:
** 定义了算法族,分别封装起来,让它们之间可以互相转换,此模式让算法的独立于使用算法的客户。**

Paste_Image.png

实现策略模式,我们需要对行为或算法实现各自的接口,具体的实现交给继承自这些接口的行为类,不需要在我们的主类鸭子中实现。主类鸭子声明两个接口的引用的实例变量,并设计set方法,这样就能在运行时动态的改变行为。实现独立和复用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 一直想把常见的设计模式系统地学习一遍,结果和大多数人一样,过了几天就没能坚持下去了。我发现学习这件事情急不得,往往...
    Neulana阅读 563评论 5 2
  • 设计模式 开题先说明一下,设计模式告诉我们如何组织类和对象以解决某种问题。让代码变得更加优雅是我们责无旁贷的任务 ...
    tanghuailong阅读 447评论 0 2
  • 客户需求 程序设计 1、直接利用继承如何? 将以上四种行为全部写到Duck这个基类中,然后子类重写飞和叫的行为。但...
    BlainPeng阅读 468评论 2 7
  • 昨夜重温金老的《天龙八部》,然后,失眠了。一直萦绕我心头的不是武功盖世气盖云天大义凛然的丐帮帮主乔峰,而是慕容复。...
    点伴阅读 783评论 5 5
  • 这星期有件开心的事,有位可爱的人说我的背很笔直!当时真的好开心,第一次有人这么说,这算是被表扬了吗,谢谢你 宿舍很...
    喜欢kitty的女孩阅读 268评论 0 0