您现在的位置是:首页 >其他 >设计模式之策略模式网站首页其他
设计模式之策略模式
1、场景推导
King上班的公司做了一套相当成功的模拟鸭子游戏:SimUDuck。
游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫。
此系统的内部设计使用了标准的OO技术,设计了一个鸭子超类,并让各种鸭子继承此超类
我们看一下UML 类图
abstract class Duck {
public void quack() {
System.out.println("呱呱叫");
}
public void swim() {
System.out.println("游泳");
}
// 因为每一种鸭子的外观都不同,所以display()方法是抽象的
public abstract void display();
}
// 野鸭子
class MallardDuck extends Duck {
@Override
public void display() {
System.out.println("外观是绿头");
}
}
//红头鸭
class RedHeadDuck extends Duck {
@Override
public void display() {
System.out.println("外观是红头");
}
}
public class StrategyApp{
public static void main(String[] args) {
Duck duck = new MallardDuck();
duck.quack();
duck.swim();
duck.display();
System.out.println("=========================================");
duck = new RedHeadDuck();
duck.quack();
duck.swim();
duck.display();
/**
* 呱呱叫
* 游泳
* 外观是绿头
* =========================================
* 呱呱叫
* 游泳
* 外观是红头
*/
}
}
程序没啥毛病,过了一段时间…
主管们决定,此模拟程序需要会飞的鸭子
来将竞争者抛在后头。当然,在这个时候,King的经理拍胸脯告诉主管们,
King只需要一个星期就可以搞定。“毕竟,King是一个OO程序员…这有什么困难??”
King认为,只需要在Duck类中加上fly()方法,然后所有鸭子都会继承fly()。
“这是我大显身手,展示OO才华的时候了!” King上来就是梭哈搞定,打完收工~
abstract class Duck {
public void quack() {
System.out.println("呱呱叫");
}
public void swim() {
System.out.println("游泳");
}
public void fly() {
System.out.println("真的飞起来了!");
}
// 因为每一种鸭子的外观都不同,所以display()方法是抽象的
public abstract void display();
}
//野鸭子
class MallardDuck extends Duck {
@Override
public void display() {
System.out.println("外观是绿头");
}
}
//红头鸭
class RedHeadDuck extends Duck {
@Override
public void display() {
System.out.println("外观是红头");
}
}
//橡皮鸭
class RubberDuck extends Duck {
// 橡皮鸭子不会呱呱叫,所以把quack()的定义覆盖成“吱吱叫”
@Override
public void quack() {
System.out.println("吱吱叫");
}
@Override
public void display() {
System.out.println("外观是橡皮鸭");
}
}
public class StrategyApp {
public static void main(String[] args) {
//向上转型
Duck duck = new MallardDuck();
duck.quack();
duck.swim();
duck.fly();
duck.display();
System.out.println("=========================================");
duck = new RedHeadDuck();
duck.quack();
duck.swim();
duck.fly();
duck.display();
System.out.println("=========================================");
duck = new RubberDuck();
duck.quack();
duck.swim();
duck.fly(); // 橡皮鸭飞起来了!?
duck.display();
}
/**
* 呱呱叫
* 游泳
* 真的飞起来了!
* 外观是绿头
* =========================================
* 呱呱叫
* 游泳
* 真的飞起来了!
* 外观是红头
* =========================================
* 吱吱叫
* 游泳
* 真的飞起来了! //橡皮鸭飞起来了,这合理吗?
* 外观是橡皮鸭
*
* 进程已结束,退出代码0
*/
}
问题似乎解决了
噢? 是吗?真的是这样吗?
King没有想到,在众多的Duck子类中,有了一个RubberDuck(橡皮鸭)
king忽略了一件事,并非Duck的所有子类都会飞。king在Duck超类中加上新的行为, 会使得某些并不适合该行为的子类也具有该行为。
现在可好了!SimUDuck程序中有了 一个无生命的会飞的东西。
针对于上面的问题,King的解决方法是这样的:我可以把橡皮鸭类中的fly()方法覆盖掉,就好像覆盖quack()的做法一样
abstract class Duck {
public void quack() {
System.out.println("呱呱叫");
}
public void swim() {
System.out.println("游泳");
}
public void fly() {
System.out.println("真的飞起来了!");
}
// 因为每一种鸭子的外观都不同,所以display()方法是抽象的
public abstract void display();
}
//野鸭子
class MallardDuck extends Duck {
@Override
public void display() {
System.out.println("外观是绿头");
}
}
//红头鸭
class RedHeadDuck extends Duck {
@Override
public void display() {
System.out.println("外观是红头");
}
}
//橡皮鸭
class RubberDuck extends Duck {
// 橡皮鸭子不会呱呱叫,所以把quack()的定义覆盖成“吱吱叫”
@Override
public void quack() {
System.out.println("吱吱叫");
}
@Override
public void display() {
System.out.println("外观是橡皮鸭");
}
// 橡皮鸭子不会飞,所以把fly()覆盖为什么事都不做,或者抛出一个UnsupportedOperationException
@Override
public void fly() {
throw new UnsupportedOperationException("you can you up, no can no bb!");
}
}
public class StrategyApp {
public static void main(String[] args) {
Duck duck = new MallardDuck();
duck.quack();
duck.swim();
duck.fly();
duck.display();
System.out.println("=========================================");
duck = new RedHeadDuck();
duck.quack();
duck.swim();
duck.fly();
duck.display();
System.out.println("=========================================");
duck = new RubberDuck();
duck.quack();
duck.swim();
duck.fly();
duck.display();
}
/**
* 呱呱叫
* 游泳
* 真的飞起来了!
* 外观是绿头
* =========================================
* 呱呱叫
* 游泳
* 真的飞起来了!
* 外观是红头
* =========================================
* 吱吱叫
* 游泳
* Exception in thread "main" java.lang.UnsupportedOperationException: you can you up, no can no bb!
* at com.hh.demo.p_strategy.c.RubberDuck.fly(Test.java:47)
* at com.hh.demo.p_strategy.c.Test.main(Test.java:73)
*
* 进程已结束,退出代码1
*/
}
这样做,橡皮鸭就不会让人意外地飞起来了! 如果客户端非要让一个橡皮鸭飞,只会收到一个运行时异常
!
可是,如果以后加入木鸭(WoodenDuck),又会如何??木头假鸭,不会飞也不会叫… King只能表示要疯了!!!
class WoodenDuck extends Duck {
@Override
public void quack() {
// 覆盖,变成什么事都不做
}
@Override
public void display() {
System.out.println("外观是木鸭");
}
@Override
public void fly() {
// 覆盖,变成什么事都不做
}
}
我们来看看利用继承来提供Duck的行为,会导致:
-
很难知道所有鸭子的全部行为。
-
改变会牵一发而动全身,造成其他鸭子不想要的改变。
-
更糟糕的是,每当有新鸭子 子类出现,King就要被迫检查并可能覆盖fly()或quack()方法…
这简直是无穷无尽的噩梦!
我们需要一个更清晰的方法,让“某些”(而不是全部)鸭子类型可飞或可叫
通过前面的学习,我们知道:把主要功能定义在父类中,把扩展功能定义在接口中。
从目前这个状况来看,“游泳“是鸭子的主要功能,飞”和“呱呱叫”成为了鸭子的扩展功能,因为并不是所有的鸭子都会“飞”和“呱呱叫”,但是所有的鸭子都会游泳吧,这时杠精又说了,橡皮鸭和木鸭没有生命,怎么会游泳呢?
我直接好家伙!!!
解决方案
King的解决之道是:
我可以把fly()从超类中取出来,放进一个“Flyable接口”中。这么一来,只有会飞的鸭子才实现此接口。
同样的方式,也可以用来设计一个“Quackable”接口,因为不是所有的鸭子都会叫。
看一下UML 类图
abstract class Duck {
public void swim() {
System.out.println("游泳");
}
public abstract void display();
}
//飞
interface Flyable {
public void fly();
}
//叫
interface Quackable {
public void quack();
}
//野鸭子
class MallardDuck extends Duck implements Flyable, Quackable {
@Override
public void quack() {
System.out.println("嘎嘎叫");
}
@Override
public void fly() {
System.out.println("飞起来了");
}
@Override
public void display() {
System.out.println("外观是绿头鸭");
}
}
//红头鸭
class RedheadDuck extends Duck implements Flyable, Quackable {
@Override
public void quack() {
System.out.println("嘎嘎叫");
}
@Override
public void fly() {
System.out.println("飞起来了");
}
@Override
public void display() {
System.out.println("外观是红头鸭");
}
}
//橡皮鸭
class RubberDuck extends Duck implements Quackable {
@Override
public void quack() {
System.out.println("嘎嘎叫");
}
@Override
public void display() {
System.out.println("外观是橡皮鸭");
}
}
//木鸭子
class WoodenDuck extends Duck {
@Override
public void display() {
System.out.println("外观是木头假鸭");
}
}
public class StrategyApp {
public static void main(String[] args) {
MallardDuck duck = new MallardDuck();
duck.quack();
duck.swim();
duck.fly();
duck.display();
System.out.println("=========================================");
RedheadDuck duck2= new RedheadDuck();
duck2.quack();
duck2.fly();
duck2.swim();
duck2.display();
System.out.println("=========================================");
RubberDuck duck3 = new RubberDuck();
duck3.quack();
duck3.swim();
duck3.display();
}
/**
* 嘎嘎叫
* 游泳
* 飞起来了
* 外观是绿头鸭
* =========================================
* 嘎嘎叫
* 飞起来了
* 游泳
* 外观是红头鸭
* =========================================
* 嘎嘎叫
* 游泳
* 外观是橡皮鸭
*
* 进程已结束,退出代码0
*/
}
我们知道,并非“所有”的子类都具有飞和呱呱叫的行为,所以继承并不是恰当的解决方式。
虽然Flyable与Quackable可以解决“一部分”问题(不会再有会飞的橡皮鸭),但是却造成代码无法复用(fly和quack),这只能算是从一个恶梦跳进另一个恶梦。
甚至,在会飞的鸭子中,飞行的动作可能还有多种变化…
也许你会说,自jdk1.8开始,接口有默认实现。
那么对于48种鸭子的子类,共有12种飞行的方式,又该怎么说? 接口的默认实现选择哪一种飞行方式都不行!
毁灭吧!!!!
这个时候,策略模式登场了
有一个设计原则: 封装变化原则
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
这样的概念很简单,几乎是每个设计模式背后的精神所在。所有的模式都提供了一套方法,让“系统中的某部分改变不会影响其他部分”。
好,该是把鸭子的行为从Duck类中取出的时候了!
我们知道Duck类内的fly()和quack()会随着鸭子的不同而改变。
为了要把这两个行为从Duck类中分开,我们将把它们从Duck类中取出来,建立一组新类来代表每个行为。
在“针对接口编程,而不是针对实现编程”的原则指导下(注意这里所说的接口指的是超类或实际的接口),
我们定义两个接口: FlyBehavior,QuackBehavior,并且让所有的行为类都实现这两个接口中的其中之一
//飞行行为
interface FlyBehavior {
void fly();
}
//用翅膀飞
class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("I'm flying!!");
}
}
//不会飞行
class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("I can't fly");
}
}
//乘火箭飞行
class FlyRockedPowered implements FlyBehavior {
@Override
public void fly() {
System.out.println("I'm flying with a rocket!");
}
}
--------------------------------------------------------------------------------------
//叫的行为
interface QuackBehavior {
void quack();
}
//呱呱叫
class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("Quack");
}
}
//没有声音的叫
class MuteQuack implements QuackBehavior {
@Override
public void quack() {
System.out.println("<< Silence >>");
}
}
//吱吱叫
class Squeak implements QuackBehavior {
@Override
public void quack() {
System.out.println("Squeak");
}
}
//鸭子的抽象类
abstract class Duck {
//组合
protected FlyBehavior flyBehavior;
protected QuackBehavior quackBehavior;
//执行 叫
public void performQuack() {
quackBehavior.quack();
}
//执行 飞
public void performFly() {
flyBehavior.fly();
}
//游泳
public void swim() {
System.out.println("All ducks float, even wooden duck!");
}
//形态
public abstract void display();
public void setFlyBehavior(FlyBehavior fb) {
this.flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
this.quackBehavior = qb;
}
}
//野鸭子
class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
@Override
public void display() {
System.out.println("I'm a real Mallard duck");
}
}
//模型鸭子
class ModelDuck extends Duck {
public ModelDuck() {
flyBehavior = new FlyNoWay(); // 一开始,鸭模型是不会飞的
quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("I'm a model duck");
}
}
// ==============================================================
public class StrategyApp {
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.performQuack();
mallard.performFly();
System.out.println("=====================");
Duck model = new ModelDuck();
model.performFly();
model.performQuack();
model.setFlyBehavior(new FlyRockedPowered());
model.performFly();
}
/**
* Quack
* I'm flying!!
* =====================
* I can't fly
* Quack
* I'm flying with a rocket!
*
* 进程已结束,退出代码0
*/
}
// ==============================================
//唐老鸭
class Tang extends Duck {
public Tang() {
flyBehavior = new FlyWithWings();
quackBehavior = new MuteQuack();
}
@Override
public void display() {
System.out.println("外观是唐老鸭");
}
}
2、策略模式基本介绍
策略模式(Strategy Pattern)中,定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户