设计模式-第一篇(策略模式)

139 阅读3分钟

设计原则

  1. 找出应用中变化的部分,剥离出来,不要和其他部分耦合在一起。
  2. 针对接口编程,而不是针对实现编程
  3. 多用组合,少用继承

假如我们要设计一个小鸭子的游戏,这些小鸭子可以飞,可以叫,可以游泳,我们这么来定义。

public abstract class Duck {

    public abstract void display();

    public void swim() {
        System.out.println("i can swimming!");
    }

    public void quack() {
        System.out.println("quack quack.");
    }

    public void fly() {
        System.out.println("i can fly!");
    }
}

通过抽象类的定义,我们可以实现不同的有共同特征的鸭子,比如红色的鸭子。

public class RedDuck extends Duck {
    @Override
    public void display() {
        System.out.println("i'm a red duck!");
    }
}

那么问题来了,并不是所有的鸭子都会飞,比如玩具鸭子就不会飞,那怎么办?能想到的最简单的方法就是,在子类里面覆盖掉fly方法,让它不会飞。

public class ToyDuck extends Duck {
    @Override
    public void display() {
        System.out.println("i'm a toy duck!");
    }

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

单一只有这一个类是没有问题的,但是如果有100种鸭子都不会飞,我们不可能要在100个子类中把继承来的fly方法都复写掉,那样就太蠢了。问题的根本原因是:飞行虽然是鸭子的共同特征,但是飞行动作已经分离成2个具体的表现,会飞or不会飞。

既然继承已经不能满足我们的需要,我们该如何去满足我们的需求呢?我们先梳理下我们的需要。首先,飞行是所有鸭子都有的特征,需要所有鸭子都有。其次飞行具体分成了会飞和不会飞,我们想要会飞的鸭子都有可以飞行的方法,不会飞的鸭子都有不会飞的方法,但是我们又不希望手动的在每个子类里实现。因为会飞这个动作可以实现一次,然后关联给所有会飞的鸭子。不会飞这个动作实现一次,然后关联给所有不会飞的鸭子。

设计原则一:把程序中变动的部分剥离出来,不要和其他部分耦合在一起

根据设计原则一,飞行这个特征就是变动的地方,我们把它从Duck里剥离出来,然后把它定义成一个接口,有两个子类实现它,一个会飞,一个不会飞。

public interface Flyable {
    void performanceFly();
}
public class CanFly implements Flyable {
    @Override
    public void performanceFly() {
        System.out.println("i can fly!");
    }
}
public class FlyNoWay implements Flyable {
    @Override
    public void performanceFly() {
        System.out.println("i can't fly!");
    }
}

然后把会飞这个特征作为Duck类的属性放到Duck类中,然后给Duck类定义一个会飞的方法。

public abstract class Duck {

    public Flyable flyable;

    public abstract void display();

    public void swim() {
        System.out.println("i can swimming!");
    }

    public void quack() {
        System.out.println("quack quack.");
    }

    public void fly() {
        flyable.performanceFly();
    }
}

这么做的好处是,所有继承Duck的鸭子还是都有飞行这个特征,但具体是否能飞,取决于Flyable的具体实现,我们可以按需求给不同的鸭子不同的能力。同时,我们把Flyable接口放在Duck类的好处是,在父类中,我只定义了飞行的动作,具体能不能飞,是”委托“给各个子类实现的,这也就是:要针对接口编程,不针对实现编程。

public class ToyDuck extends Duck {
    public ToyDuck() {
        flyable = new FlyNoWay();
    }
    @Override
    public void display() {
        System.out.println("i'm a toy duck!");
    }
}

测试代码如下:

public class TestToyDuck {
    public static void main(String[] args) {
        Duck duck = new ToyDuck();
        duck.fly();
    }
}

打印结果:i can't fly!

上述实现的缺点是:在实例化子类鸭子的时候,能不能飞是写死的,不能动态改变,我们该怎么改掉呢,请看:

public abstract class Duck {

    public Flyable flyable;

    public abstract void display();

    public void fly() {
        flyable.performanceFly();
    }

    public Flyable getFlyable() {
        return flyable;
    }

    public void setFlyable(Flyable flyable) {
        this.flyable = flyable;
    }
}

在Duck鸭子中设置getter/setter方法,然后在实例化子类后,可以动态的改变。

public class TestToyDuck {
    public static void main(String[] args) {
        Duck duck = new ToyDuck();
        duck.fly();
        duck.setFlyable(new CanFly());
        duck.fly();
    }
}

打印结果:

i can't fly! i can fly!

这样,我们就能非常灵活的实现我们的需求,再次新建会飞的鸭子只要在鸭子实例化后set一个会飞的实现类就可以了。