什么是策略模式
从简单的模拟鸭子做起
做一个模拟鸭子的游戏:SimDuck, 有各种鸭子,能游戏戏水能瓜瓜叫.
实现: 设计一个鸭子的超类
public abstract class Duck{
public void quack(){
// 嘎嘎, 超类实现
}
public void swim(){
// 游泳, 超类实现
}
abstract void display(); // 不同鸭子外观不一样
}
各种鸭子子类实现
public class MallardDuck extends Duck{
public void display(){
// 绿头
}
}
public class RedheadDuck extends Duck{
public void display(){
// 红头
}
}
想让鸭子飞
方案一: 利用继承来提供Duck的行为
- 在超类Duck类中添加上fly()方法
- 但是, 子类不会飞的鸭子也继承了fly()方法
- 解决: 在子类中将fly()方法覆盖掉
- 缺点:
- 代码在多个子类重复
- 运行时行为不易改变
- 改变牵一发动全身
- 每当有新的子类鸭,就要检查是否覆盖fly()等方法
public abstract class Duck{
...
public vode fly(){
// 飞, 超类实现
}
...
}
public class DecoyDuck extends Duck{
...
public void fly(){
// 覆盖, 不会飞
}
...
}
方案二: 利用接口
- 将fly()取出来, 放进一个 [Flyable接口]中, 会飞的鸭子才实现此接口
- 解决了有些鸭子不会飞
- 缺点:
- 但是每个会飞的子类都要实现fly(),导致重复代码变多
- 并且无法复用
public interface Flyable{
void fly();
}
public class MallardDuck extends Duck implements Flyable{
...
public void fly(){
// 飞, 子类实现
}
...
}
设计原则: 找出应用中可能需要变化之处,把它们独立出来(封装起来,按需扩充),不要和那些不需要变化的代码混在一起
方案三: 取出Duck类中变化的部分 fly, 建立一组新类代表行为
设计原则: 针对接口编程,而不是针对实现编程
- 利用接口代表每个独立出来的行为, 如 FlyBehavior接口, fly行为的实现必须实现该接口
- 而鸭子类不负责独自实现fly行为了,由其fly行为类实现(实现的代码由鸭子类转到了行为类)
- 添加新的行为不会影响到既有的行为类,也不会影响使用到飞行行为的鸭子类
public interface FlyBehavior{
void fly();
}
/**
下面是两个fly行为的不同实现类
*/
public class FlyWithWings implements FlyBehavior{
void fly(){
// 用翅膀飞, 实现了所有用翅膀飞的鸭子的的动作
}
}
public class FlyNoWay implements FlyBehavior{
void fly(){
// 什么都不做,不会飞,实现了所有不会飞的鸭子的的动作
}
}
整合鸭子的行为
public interface FlyBehavior{
void fly();
}
public abstract class Duck implements FlyBehavior{
FlyBehavior flyBehavior; // 面向接口的实例变量,会利用多态方式在运行时引用正确的行为类型,每只鸭子都会引用实现FlyBehavior 接口的对象
...
public void performFly(){
flyBehavior.fly(); // 不亲自处理飞行行为,而是委托给flyBehavior对象
}
...
}
public class FlyWithWings implements FlyBehavior{
void fly(){
// 用翅膀飞, 实现了所有用翅膀飞的鸭子的的动作
}
}
public class RedheadDuck extends Duck{
public RedheadDuck(){
flyBehavior = new FlyWithWings(); // RedheadDuck继承Duck类,所以具有flyBehavior 实例变量
}
...
}
- 想要进行fly行为, Duck对象只要叫flyBehavior对象去fly就行了, 这部分代码中, 不在乎FlyBehavior对象到底是什么,只关心该对象如何fly就行了.
- 但是此时, 初始化变量flyBehavior 的做法不够弹性
- 实例变量是一个接口类型,能够运行时,通过多态指定不同的FlyBehavior实现类给它
- 在Duck类添加[setter方法],就可以在子类中调用设定鸭子的行为了
设计原则: 多用组合,少用继承
public abstract class Duck implements FlyBehavior{
FlyBehavior flyBehavior; // 面向接口的实例变量,会利用多态方式在运行时引用正确的行为类型,每只鸭子都会引用实现FlyBehavior 接口的对象
...
// 新加setter方法
public void setFlyBehavior(FlyBehavior fb){
flyBehavior = fb; //可以随时调用这个方法更改鸭子的行为
}
public void performFly(){
flyBehavior.fly(); // 不亲自处理飞行行为,而是委托给flyBehavior对象
}
...
}
public class RedheadDuck extends Duck{
public RedheadDuck(){
flyBehavior = new FlyWithWings(); // RedheadDuck继承Duck类,所以具有flyBehavior 实例变量
}
...
}
public class Test{
public static void main(String[] args){
Duck red = new RedheadDuck(); // 针对接口编程,声明接口实例变量
red.setFlyBehavior(new FlyWithWings()); //调用继承来的方法把翅膀飞行行为设定到红头鸭中
red.performFly();
}
}
策略模式
定义了算法族,分别封装起来,让它们之间互相替换, 让算法的变化独立于使用算法的客户
把鸭子的行为当成一族算法