设计模式学习——策略模式

不管你在何处工作,构建些什么,用何种编程语言,在软件开发上,一直有条颠扑不破的真理:成长与改变

不管软件设计的多好,一段时间之后,总是需要成长与改变,否则软件就会死亡。对于此,首先有第一条设计原则:

封装变化

即将应用中需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。除此在外,我们还希望我们的代码易于维护和扩展,更加富有弹性,对于此,有第二条设计原则:

面向接口编程,而不是针对实现编程

 

基本所有的设计模式都符合上述两条设计原则,现在,我们将要在此结合例子讲述策略模式


策略模式

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

即定义一个算法接口,让具体的算法类实现该接口,在客户角色中仅依赖该接口来使用算法,并不关心算法的具体实现。

UML类图

其中可以看到我们对定义的理解:

  • 算法接口:通常为一个抽象类或者接口
  • 算法实现类:实现了算法接口的具体算法
  • 环境(客户):依赖于算法接口

例子

现在有名为鸭子的父类,有叫,游泳以及外观这三个方法,红头鸭和绿头鸭继承了父类,并重写了外观方法。

package strategy;

public class Duck {
    public void quack(){}

    public void swim(){}

    public void display(){}

}
package strategy;

public class MallardDuck extends Duck{
    public void display(){
        //外观是绿头鸭
    }
}
package strategy;

public class RedheadDuck extends Duck {
    public void display(){
        //外观是红头鸭
    }
}

如果现在我们想让鸭子飞,那么在父类中再实现飞的方法就行,但是,如果增加了子类橡皮鸭,橡皮鸭也会飞了(实际上这种玩具可不会飞)。那么我们可以通过覆盖掉子类中的这个方法来让橡皮鸭不会飞,那要是我们再增加了木工鸭,既不会飞也不会叫,是不是又要重新覆盖这些代码呢?再增加新类呢?这么看来的话,代码的复用性就很差了。

我们或许可以为这些行为提供一个接口,如为叫提供一个叫行为接口,游泳提供一个游泳行为接口,让鸭子们实现这些接口就行了。然而这么做的后果是,随着鸭子种类的增多,对接口中方法的实现将使我们疲于奔命。

这时候策略模式就显示出它的威力了。


首先,我们定义接口,以飞接口和叫接口为例。

package strategy;

/**
 * Strategy接口
 */
public interface FlyBehavior {
    void fly();
}
package strategy;
/**
 * Strategy接口
 */
public interface QuackBehaviour {
    void quack();
}

之后根据鸭子具体叫/飞的行为,定义具体实现类,如会飞,或者不会飞。

package strategy;

/**
 * 具体的会飞行为
 */
public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly(){
        System.out.println("I believe I can fly!");
    }
}
package strategy;

/**
 * 具体的不会飞行为
 */
public class FlyNoWay implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("I can't fly!");
    }
}

整合鸭子行为

package strategy;

/**
 * 将行为实现类加入到父类中
 * 将具体实现委托给行为实现
 */
public class Duck {
    QuackBehaviour quackBehaviour;

    FlyBehavior flyBehavior;

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

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

   // public void quack(){}

    public void swim(){}

    public void display(){}

}
package strategy;

/**
 * 绿头鸭
 */
public class MallardDuck extends Duck{

    public MallardDuck(){
        quackBehaviour = new Quack();
        flyBehavior = new FlyWithWings();
    }
    public void display(){
        //外观是绿头鸭
        System.out.println("I'm a real mallard duck");
    }
}

我们看到我们讲具体行为/算法 交给了算法的持有者,由持有者来运行,但是它并不关心算法的具体实现,即将实现委托给了具体的行为/算法实现。

动态的设定行为

实现set方法即可

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

第三条设计原则

在上面这个例子中,我们发现,继承并不是万能的,它有时候反而给代码的复用,维护造成了负面的作用。

那么该如何扩展我们的行为呢?

多用组合,少用继承

使用组合建立系统将具有很大的弹性,不仅可将算法族封装成类,更可以在运行时动态的改变行为,只要组合的行为对象符合正确的接口标准即可,同时,组合也用在了许许多多的设计模式之中,在接下来的设计模式中,我们讲体会到它的诸多优点与缺点。


源码在这里:我的github地址


猜你喜欢

转载自blog.csdn.net/lpckr94/article/details/81154626
今日推荐