学习Java设计模式——策略模式(Strategy Pattern)


以下通过概念、需求场景、需求分析、代码设计及实践、UML类图展示,一步步给大家介绍 “策略模式”


一、策略模式概念

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

二、需求场景

老板下达任务,今日之内设计一个游戏,模拟各类鸭子的行为。鸭子有好几个种类,而且每种鸭子的外观、游泳技能、飞行技能、叫声都不一样。

具体要求如下

1、鸭子种类:野鸭(Mallard duck)、红头鸭(Red Head duck)、橡皮鸭(Rubber duck)、诱饵鸭(Decoy Duck)

3、野鸭的外观是绿头, 红头鸭的外观是红头,橡皮鸭的外观是橡皮, 诱饵鸭的外观是木头

2、所有鸭子都会游泳

3、野鸭、红头鸭会飞,但橡皮鸭、诱饵鸭不会飞

4、野鸭、红头鸭的叫声是“Quack Quack ...”,而橡皮鸭的叫声是“Squeak Squeak....”,诱饵鸭不会叫

三、需求分析

【tips:设计原则描述请见文章末尾】

方法一:Duck中提供display、swim、fly、quack方法, 子类继承

分析:所有的子类都会有父类的行为,这和需求是不符的,且鸭子的行为在子类中会根据需求不断地被重写,代码无法复用,此方法不可行。

方法二:根据设计原则一,将变化和不会变化的部分分开,由于Duck类内的fly和quack会随着鸭子的不同而改变,我们可以取出fly和quack行为,分别建立新类,每组类实现各自的动作。

分析:鸭子的fly和quack行为被放在分开的类中,那么我们再对这两个行为进行分析:

1、鸭子的fly和quack行为分别可能有多种情况,根据设计原则二,我们还可以封装FlyBehaviorQuackBehavior接口,具体的行为编写在实现了这两个接口的类中

2、然后在Duck父类中包含设定fly和quack行为的方法(performFlyperformQuack),这样,Duck父类就不再需要知道行为的实现细节,而子类将可以使用这两个接口所表示的行为

3、为了让这两个行为可以动态地改变,并且可以在运行时动态地改变子类鸭子的飞行行为(setFlyBehaviorsetQuackBehavior

嗯,这是个好方法,很完美地利用了策略模式!下面我们就用这个方法来实践一下

四、代码设计及实践

根据以上对方法二的分析,下面进行代码设计及实践:

1、首先设计两个接口,FlyBehavior、QuackBehavior,还有它们对应的类,负责实现具体的行为

展示具体代码:
FlyBehavior接口及其实现

public interface FlyBehavior {
    public void fly();
}

public class FlyRocketPowered implements FlyBehavior {
    public void fly() {
        System.out.println("I'm flying with a rocket");
    }
}

public class FlyWithWings implements FlyBehavior {
    public void fly() {
        System.out.println("I'm flying!!");
    }
}

public class FlyNoWay implements FlyBehavior {
    public void fly() {
        System.out.println("I can't fly");
    }
}

FlyBehavior接口及其实现

public interface QuackBehavior {
    public void quack();
}

public class MuteQuack implements QuackBehavior {
    public void quack() {
        System.out.println("<< Silence >>");
    }
}

public class Quack implements QuackBehavior {
    public void quack() {
        System.out.println("Quack");
    }
}

public class FakeQuack implements QuackBehavior {
    public void quack() {
        System.out.println("Qwak");
    }
}

public class Squeak implements QuackBehavior {
    public void quack() {
        System.out.println("Squeak");
    }
}

2、在Duck父类中加入“FlyBehavior”和“QuackBehavior”两个实例变量,并加入“performFly”“performQuack”方法,然后在子类构造器调用具体的实现,这里用MallardDuck进行举例。

Duck,java

public abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

    public Duck() {
    }

    public void performFly() {
        flyBehavior.fly();
    }

    public void performQuack() {
        quackBehavior.quack();
    }

    abstract void display();

    public void swim() {
        System.out.println("All ducks float, even decoys!");
    }

}

MallardDuck.java

public class MallardDuck extends Duck {

    public MallardDuck() {
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }

    public void display() {
        System.out.println("I'm a real Mallard duck");
    }
}

使用 MiniDuckSimulator.java 测试一下MallardDuck

public class MiniDuckSimulator {
 
    public void MiniDuckSimulator() {
        MallardDuck mallard = new MallardDuck();
        mallard.performFly();
        mallard.performQuack();

    }
}

3、为了让这两个行为可以动态地改变,在鸭子子类中动态设定方法(setFlyBehaviorsetQuackBehavior),而不是在构造器内实例化。此后,我们可以随时调用这两个方法来改变鸭子的行为

在Duck.java中加入两个新方法

public void setFlyBehavior(FlyBehavior fb) {
        flyBehavior = fb;
    }

    public void setQuackBehavior(QuackBehavior qb) {
        quackBehavior = qb;
    }

制造一个新的鸭子类型:模型鸭(ModelDuck.java)

public class ModelDuck extends Duck {
    public ModelDuck() {
        flyBehavior = new FlyNoWay();
        quackBehavior = new Quack();
    }

    public void display() {
        System.out.println("I'm a model duck");
    }
}

使用 MiniDuckSimulator.java 测试一下ModelDuck

public class MiniDuckSimulator {
 
    public void MiniDuckSimulator() {
        Duck model = new ModelDuck();
        model.performFly();
        model.setFlyBehavior(new FlyRocketPowered());//改变飞行行为
        model.performFly();
    }
}

五、UML类图展示

最后,我们使用UML类图来看看整体的格局


FlyBehavior接口及其实现
QuackBehavior接口及其实现
Duck继承关系

本设计模式参考的3个设计原则:

设计原则一:在应用中找出可能需要变化的地方,把它们独立封装起来,以便对其他部分不造成影响。

说明:比如,如果每次来一个新的需求,某块的代码都会发生变化,那么这部分的代码就要被抽取独立出来,和其他较为稳定的代码进行区分。

设计原则二:针对接口编程,而不是针对实现编程。

说明:这里的“接口”不是单独指定接口类型,而是一个概念,即 “超类型”,即抽象类或接口等。针对接口编程的关键在于多态,使用“超类型”去声明变量时,只要是实现了此超类型的类对应的对象,都可以指定给这个变量。

设计原则三:多用组合,少用继承

说明:使用组合建立系统具有很大的弹性,不仅可将行为算法族封装成类,更可以在运行时动态地改变行为

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