策略模式是什么?
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
抽象策略角色: 策略类,通常由一个接口或者抽象类实现。
具体策略角色:包装了相关的算法和行为。
环境角色:持有一个策略类的引用,最终给客户端调用。
策略模式的优点:
策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码转移到父类里面,从而避免重复的代码。
策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。
使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
策略模式的缺点:
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
策略模式造成很多的策略类,每个具体策略类都会产生一个新类。
以下关于策略模式模拟一个鸭子行为的实际操作:
首先我们要有一个父类
public abstract class Duck {
// 先创建Duck的构造方法
public Duck(){
};
// 创建一个face方法
public abstract void face();
// 获取飞行的行为
FlyBehavior flyBehavior;
// 获取鸣叫的行为
QuackBehavior quackBehavior;
// 执行飞的方法
public void performFly(){
// 我们把具体的实现 委托给行为类
flyBehavior.fly();
}
// 执行鸣叫的方法
public void performQuack(){
// 委托给行为类
quackBehavior.quack();
}
public void swim(){
System.out.println("游泳");
}
// 为flyBehavior 和 quackBehavior 创建get和set方法
public FlyBehavior getFlyBehavior() {
return flyBehavior;
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public QuackBehavior getQuackBehavior() {
return quackBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
}
我们还要拥有他们的行为类 (飞行和鸣叫的行为)并且声明为接口类型。
public interface FlyBehavior {
//创建一个飞行的方法(行为)
public void fly();
}
public interface QuackBehavior {
//创建一个鸣叫的方法
public void quack();
}
我们需要去实现他们的行为,创建一个用翅膀飞行的类,并实现FlyBehavior 接口。
public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("我会飞!");
}
}
同时再创建一个不会飞的类,也实现FlyBehavior 接口。
public class FlyNoWay implements FlyBehavior{
public void fly() {
System.out.println("我不会飞!");
}
}
接下来,我们实现他们的鸣叫类,实现QuackBehavior 接口。
public class Quack implements QuackBehavior {
public void quack() {
System.out.println("嘎嘎嘎叫!");
}
}
创造一个不会鸣叫的类,也实现QuackBehavior 接口。
public class MuteQuack implements QuackBehavior {
public void quack() {
System.out.println("不会叫");
}
}
再创造一个会唱歌的类,也实现QuackBehavior 接口。
public class SingQuack implements QuackBehavior{
public void quack() {
System.out.println("但是会唱歌!");
}
}
接下来,我们要创造两只鸭子。
创造一只绿头鸭并实现它的父类Duck类。
public class MallardDuck extends Duck{
public MallardDuck(){
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
说明 MallardDuck 类继承了 Duck 所以具有 quackBehavior 和 flyBehavior 实例变量
public void face() {
System.out.println("绿头鸭");
}
}
接下来我们在创造一个模型鸭用作对比。
public class ModelDuck extends Duck {
public ModelDuck(){
quackBehavior = new MuteQuack(); // 调用不会鸣叫的实现类
flyBehavior = new FlyNoWay(); //同时也不会飞
}
public void face() {
System.out.println("只是一个模型!");
}
}
现在我们开始创造一个main方法对上面两只鸭子进行测试。
先测试一下绿头鸭
public static void main(String[] args){
Duck mallardDuck = new MallardDuck();
mallardDuck.performFly();
mallardDuck.performQuack();
mallardDuck.face();
}
上面操作 会调用MallardDuck 继承来的 performFyl 和 performQuack 方法,进而委托给 FlyBehavior 和 QuackBehavior 对象处理 。也就是说,这里调用继承来的quackBehavior引用对象的quack()方法和flyBehavior引用对象的fly()方法。则face ()调用的是MallardDuck类的face()方法。
再来看一下运行的结果。
接下来我们在对模型鸭进行测试。
public static void main(String[] args){
Duck modelDuck = new ModelDuck();
modelDuck.face();
modelDuck.performFly();
modelDuck.performQuack();
modelDuck.setQuackBehavior(new SingQuack());
modelDuck.performQuack();
}
如果我们这里直接调用performQuack 则这只模型鸭不会叫。
换个方式我们调用继承的set方法,并把 SingQuack对象设置到quackBehavior中。则模型鸭会唱歌了。再次调用perfomQuack 则是SingQuack的具体实现。
看一下运行结果。
画一个类图整理其中的关系。
设计原则1: 多用组合,少用继承。
设计原则2: 针对接口编程,而不是针对实现类。
设计原则3: 找出应用经常变化的部分,把它们独立出来。不要和那边不会变化的放在一起。使它们更容易维护和扩展。