【设计模式】策略模式

一、前言

  笔者在看JDK源码的同时也穿插着看设计模式,之前有涉猎设计模式,但是没有进行总结和提炼,现在再读一遍设计模式,感觉受益匪浅,也特此进行记录。下面设计模式系列是以《Head First 设计模式》书为参考。有兴趣的读者可以购买,讲解得浅显易懂。

二、策略模式

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

  乍一看,概念比较难懂和生涩,可以用另外一种方式说明,对于不同的策略,对象会有不同的行为,这些策略是独立的,并且可以替换彼此,下面通过实际的示例来帮助理解这一模式。

三、示例说明

  设计一个鸭子游戏,游戏中有各种鸭子,鸭子有各种行为,如呱呱叫、游泳等,同时不同的鸭子的外观不相同,这时候,很容易设计如下的类图。

  说明:Duck为抽象类,表示鸭子类,所有的鸭子都有呱呱叫和游泳的行为,所以在Duck抽象类中实现,而display则是抽象的,因为不同的鸭子有不同的外观,具体子类给出实现。MallardDuck、RedheadDuck是呱呱叫(与Duck实现的相同),两者的外观不相同。RubberDuck是吱吱叫(需要重写Duck的quack方法),外观也不相同。

  3.1 v1.0

  根据类图,代码如下

  Duck类  

package com.hust.grid.leesf.strategy;

public abstract class Duck {
    public void quack() {
        System.out.println("quack...");
    }
    
    public void swim() {
        System.out.println("swim...");
    }
    
    public abstract void display();
}
View Code

  MallardDuck类  

package com.hust.grid.leesf.strategy;

public class MallardDuck extends Duck {
    @Override
    public void display() {
        System.out.println("I am a MallardDuck");
    }
}
View Code

  RedheadDuck类 

package com.hust.grid.leesf.strategy;

public class RedheadDuck extends Duck {
    @Override
    public void display() {
        System.out.println("I am a RedheadDuck");
    }
}
View Code

  RubberDuck类

package com.hust.grid.leesf.strategy;

public class RubberDuck extends Duck {
    @Override
    public void display() {
        System.out.println("I am a RubberDuck");
    }
    
    @Override
    public void quack() {
        System.out.println("squeak...");
    }
    
    @Override
    public void fly() {
        
    }
}
View Code

  Main类(用作测试)  

package com.hust.grid.leesf.strategy;

public class Main {
    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();
        System.out.println("------------------");
        duck = new RubberDuck();
        duck.quack();
        duck.swim();
        duck.display();
    }
}
View Code

  运行结果:  

quack...
swim...
I am a MallardDuck
------------------
quack...
swim...
I am a RedheadDuck
------------------
squeak...
swim...
I am a RubberDuck

  可以看到,上面的编写的代码可以运行良好,Rubber类重写了quack,为吱吱叫,但是,若此时提出一个新的需求,给某些鸭子添加飞的行为,因为有些可以飞(会飞的行为),一种不太好的解决办法是在Duck超类中添加fly方法,这样,每个子类都将会有fly方法,达到了代码复用的目的,但是,对与RubberDuck而言,它不会飞。则可以将RubberDuck类的fly覆盖,什么都不做

  3.2 v2.0

  v2.0,有些鸭子会飞(有飞的行为),修改代码如下

  Duck类  

package com.hust.grid.leesf.strategy;

public abstract class Duck {
    public void quack() {
        System.out.println("quack...");
    }
    
    public void swim() {
        System.out.println("swim...");
    }
    
    public abstract void display();
    
    public void fly() {
        System.out.println("fly...");
    }
}
View Code

  RubberDuck类 

package com.hust.grid.leesf.strategy;

public class RubberDuck extends Duck {
    @Override
    public void display() {
        System.out.println("I am a RubberDuck");
    }
    
    @Override
    public void quack() {
        System.out.println("squack...");
    }
    
    @Override
    public void fly() {
        
    }
}
View Code

  Main类(用于测试)  

package com.hust.grid.leesf.strategy;

public class Main {
    public static void main(String[] args) {
        Duck duck = new MallardDuck();
        duck.quack();
        duck.swim();
        duck.display();
        duck.fly();
        System.out.println("------------------");
        
        duck = new RedheadDuck();
        duck.quack();
        duck.swim();
        duck.display();
        duck.fly();
        System.out.println("------------------");
        
        duck = new RubberDuck();
        duck.quack();
        duck.swim();
        duck.display();
        duck.fly();
    }
}
View Code

  运行结果:  

quack...
swim...
I am a MallardDuck
fly...
------------------
quack...
swim...
I am a RedheadDuck
fly...
------------------
squack...
swim...
I am a RubberDuck

  RubberDuck重写了Duck类fly方法,使其什么都不做,这样可以认为RubberDuck不具有飞的行为。

  不足:当新增子类时,若子类也不具有飞的行为,则又要像RubberDuck一样进行处理,即每当新增子类时,都需要检查是否需要覆盖quack方法和fly方法,这显然是很麻烦的。有没有一种更好的办法,可以很好的解决这个问题呢?

  3.3 v3.0

  分离鸭子行为中变化的与不变化的部分(设计原则:找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起),这样,每次有新的需求时,都会使某方面的代码发生变化,可以把这部分的代码抽离出来,和其他的稳定代码有所区别。方便进行改动或者扩充,而不影响不需要变化的部分。变化的部分包括飞行行为和呱呱叫行为。

  1. 抽出鸭子飞行行为

  2. 抽出鸭子呱呱叫行为

  设计的类图如下图所示

  

  

  说明:对于飞行行为和呱呱叫行为都已经从鸭子对象本身独立出来了。这样,可以很轻易让鸭子对象有其他的行为。Duck对象中有FlyBehavior和QuackBehavior两个实例变量。

  再次修改后的代码如下

  Duck类 

package com.hust.grid.leesf.strategy;

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 performQuack() {
        quackBehavior.quack();
    }
    
    public void performFly() {
        flyBehavior.fly();
    }
    
    public void swim() {
        System.out.println("swim...");
    }
    
    public abstract void display();
}
View Code

  MallardDuck类 

package com.hust.grid.leesf.strategy;

public class MallardDuck extends Duck {
    public void display() {
        System.out.println("I am a MallardDuck");
    }
}
View Code

  RedheadDuck类  

package com.hust.grid.leesf.strategy;

public class RedheadDuck extends Duck {
    @Override
    public void display() {
        System.out.println("I am a RedheadDuck");
    }
}
View Code

  RubberDuck类  

package com.hust.grid.leesf.strategy;

public class RubberDuck extends Duck {
    @Override
    public void display() {
        System.out.println("I am a RubberDuck");
    }
}
View Code

  FlyBehavior类  

package com.hust.grid.leesf.strategy;

public interface FlyBehavior {
    void fly();
}
View Code

  FlyWithWings类  

package com.hust.grid.leesf.strategy;

public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("fly with wings");
    }
}
View Code

  FlyNoWay类 

package com.hust.grid.leesf.strategy;

public class FlyNoWay implements FlyBehavior {
    @Override
    public void fly() {
        
    }
}
View Code

  QuackBehavior类 

package com.hust.grid.leesf.strategy;

public interface QuackBehavior {
    void quack();
}
View Code

  Quack类  

package com.hust.grid.leesf.strategy;

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

  Squeak类  

package com.hust.grid.leesf.strategy;

public class Squeak implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("squeak...");
    }
}
View Code

  MuteQuack类

package com.hust.grid.leesf.strategy;

public class MuteQuack implements QuackBehavior {
    @Override 
    public void quack() {
        
    }
}
View Code

  Main类 

package com.hust.grid.leesf.strategy;

public class Main {
    public static void main(String[] args) {
        Duck duck = new MallardDuck();
        FlyBehavior flyBehavior = new FlyWithWings();
        duck.setFlyBehavior(flyBehavior);
        QuackBehavior quackBehavior = new Quack();
        duck.setQuackBehavior(quackBehavior);
        duck.performQuack();
        duck.performFly();
        duck.swim();
        duck.display();
        System.out.println("------------------");        
    }
}
View Code

  运行结果: 

quack...
fly with wings
swim...
I am a MallardDuck
------------------

  可以看到,这个版本的鸭子游戏已经比较理想,此时,可以很容易的添加子类,并且不用改变之前稳定的代码,在运行过程中可以动态改变对象的行为(通过setFlyBehavior和setQuackBehavior实现)。如添加一个ModelDuck类,继承自Duck类,FlyRocketPower,实现FlyBehavior接口,代码如下

  ModelDuck类  

package com.hust.grid.leesf.strategy;

public class ModelDuck extends Duck {
    @Override
    public void display() {
        System.out.println("I am a ModelDuck");
    }    
}
View Code

  FlyRocketPower类 

package com.hust.grid.leesf.strategy;

public class FlyRocketPower implements FlyBehavior {
    @Override 
    public void fly() {
        System.out.println("fly with a rocket");
    }
}
View Code

  Main类(用作测试) 

package com.hust.grid.leesf.strategy;

public class Main {
    public static void main(String[] args) {
        Duck duck = new MallardDuck();
        FlyBehavior flyBehavior = new FlyWithWings();
        duck.setFlyBehavior(flyBehavior);
        QuackBehavior quackBehavior = new Quack();
        duck.setQuackBehavior(quackBehavior);
        duck.performQuack();
        duck.performFly();
        duck.swim();
        duck.display();
        System.out.println("------------------");    
        
        duck = new ModelDuck();
        flyBehavior = new FlyRocketPower();
        quackBehavior = new MuteQuack();
        duck.setFlyBehavior(flyBehavior);
        duck.setQuackBehavior(quackBehavior);
        duck.performQuack();
        duck.performFly();
        duck.swim();
        duck.display();
        System.out.println("------------------");    
        
    }
}
View Code

  运行结果  

quack...
fly with wings
swim...
I am a MallardDuck
------------------
fly with a rocket
swim...
I am a ModelDuck
------------------

四、总结

  策略模式的应用很广泛,在游戏中有应用,如角色可以更换不同的武器,不同的武器有不同的效果,就可以使用策略模式来完成。所有源代码已经上传至github,欢迎fork,谢谢各位园友的观看~

 

posted @ 2016-05-31 22:14  leesf  阅读(680)  评论(0编辑  收藏  举报