简析设计模式之策略模式

首先,我们从鸭子说起,鸭子都会叫和游泳,但不同的鸭子有不同的外观:
先写一个父类:

public abstract class Duck {
    public void quack(){
        System.out.println("I'm duck, I can quack!");
    }
    public void swim(){
        System.out.println("I'm duck, I can swim!");
    }
    public abstract void display();
}

绿头鸭:

public class MallardDuck extends Duck {

    @Override
    public void display() {
        System.out.println("I'm green head duck");
    }

}

红头鸭:

public class RedHeadDuck extends Duck {

    @Override
    public void display() {
        System.out.println("I'm red head duck");
    }

}

但是,有了新的需求,我们想让鸭子飞:在父类写个fly()方法,所有的子类都继承该方法,但是,有的鸭子不会飞的,例如橡胶鸭子。这显然不是我们想要的。或许你会说,我们可以在子类中覆盖该方法,让它什么都不做,但是,如果有越来越多的其它类型的鸭子需要添加进来那又该如何?显然,会有越来越多重复的代码在子类中出现,而且如果父类稍微有点改动都会涉及到子类。继承,显然不是我们推崇的。

那么你会说,用接口来抽离出鸭子飞行和叫法的行为又怎样?

是的,这样是解决了一部分问题,起码不会出现会飞的假鸭子,但是,如果很多鸭子是会飞行以及叫法一样的,是不是又造成代码无法复用的问题?接口不具备实现功能,所以无法达到代码的复用。

但是,问题总是得解决的。我们只能从涉及原则中寻找答案了:
把可能变化的代码抽离并封装起来,好让其它部分不受影响。这是一个很简单的概念,也几乎是每个设计模式背后的精神所在。

好的,是时候对duck进行改造了,记住上面的原则:把可能变化的代码抽离并封装起来,这里的quack()和fly()是会随着鸭子的不同而改变的。
先把fly和quack抽离出来:

public interface FlyBehavior {
    void fly();
}
public interface QuackBehavior {
    void quack();
}

接下来,需要飞行或不需要飞行的都实现FlyBehavior接口,叫或不叫以及叫法不同的实现QuackBehavior 接口:

public class FlyWithWings implements FlyBehavior {

    @Override
    public void fly() {
        System.out.println("I can fly");
    }

}
public class FlyNoWay implements FlyBehavior {

    @Override
    public void fly() {
        System.out.println("I can't fly");

    }

}

类似地,quackBehavior同样如此实现。

如此一来,变化的部分已经和duck完全无关了,但是我们接下来要做的就是把他们联系起来,让不同的鸭子飞或不飞,叫或不叫:

public abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

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

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

    public void swim(){
        System.out.println("I'm duck, I can swim!");
    }
    public abstract void display();
}

这样,就把具体实现都留给了具体行为的实现类,现在,我们来看看子类的改造:

public class MallardDuck extends Duck {

    public MallardDuck() {
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }

    @Override
    public void display() {
        System.out.println("I'm green head duck");
    }
}

最后来个测试类看看效果:

public class Main {

    public static void main(String[] args) {
        Duck greenDuck = new GreenHeadDuck();
        greenDuck.disPlay();
        greenDuck.performFly();
        greenDuck.performQuack();

        Duck redDuck = new RedHeadDuck();
        redDuck.disPlay();
        redDuck.performFly();
        redDuck.performQuack();
    }
}

在此,我们需要记住一个原则:针对接口编程,而不是针对实现编程。由始至终,我们的程序都是尽量地遵循这个原则的。

为了使这个应用更加地弹性,我们还可以在Duck中添加set方法,动态地改变鸭子的行为,万一它哪天会飞了呢?

public void setFlyBehavior(FlyBehavior fb){
        this.flyBehavior = fb;
    }

    public void setQuackBehavior(QuackBehavior qb){
        this.quackBehavior = qb;
    }

Main方法里:

Duck redDuck = new RedHeadDuck();
        redDuck.disPlay();
        redDuck.performFly();
        redDuck.performQuack();
        redDuck.setFlyBehavior(new FlyWithSwings());
        redDuck.performFly();

其实,这就是另外一个设计原则:少用继承,多用组合。如同本例,当你将两个以上的类结合使用,就是组合。鸭子的行为不是继承过来的,而是适当地与行为对象组合而来的。

软件开发总是要花大量的时间在维护与升级上,而好的设计模式往往能帮我们减少大量的时间的消费。多用组合,少用继承。

最后,也是时候揭晓谜底了,这就是策略模式:
所谓策略模式就是定义了算法簇,分别封装起来,让它们之间可以互相替换。此模式让算法的变化独立于使用算法的客户。

猜你喜欢

转载自blog.csdn.net/lks1139230294/article/details/76039745
今日推荐