美文网首页
Head First设计模式学习笔记一策略模式

Head First设计模式学习笔记一策略模式

作者: 巾二 | 来源:发表于2018-08-26 21:17 被阅读0次

    假如我们现在有一个鸭子,鸭子会呱呱叫,也会游泳,但是每个鸭子的外观不相同(有白颜色的,有绿色的),那么你会怎么设计这个鸭子呢?

    我们第一肯定是想到设计一个鸭子超类,这个超类包括swim()和quack()两个方法,还有一个抽象的dispaly()方法。

    public abstract class Duck {
        public void swim(){
            System.out.println("I can swimming");
        }
        
        public void quack(){
            System.out.println("quack quack");
        }
    
        public abstract void display();
    }
    

    白色的鸭子继承Duck类,并实现display()方法

    public class WhiteDuck extends Duck {
        public void display() {
            System.out.println("I am a white duck!");
        }
    }
    

    绿色鸭子也继承Duck类,实现自己的display()方法

    public class GreenDuck extends Duck {
        public void display() {
            System.out.println("I am a green duck!");
        }
    }
    

    如果这个时候我们加了一个需求,要求要鸭子会飞,那么你又会怎么设计呢?你是不是首先想到在Duck类上加上一个fly()方法,像下面这样

    public abstract class Duck {
    
        public void swim(){
            System.out.println("I am swimming");
        }
    
        public void quack(){
            System.out.println("quack quack");
        }
    
        public abstract void display();
    
        public void fly(){
            System.out.println("I can fly");
        }
    }
    

    但是如果并不是所有的鸭子都会飞,比如橡皮鸭子不会飞。这时候你会想是不是想可以在橡皮鸭中覆盖掉fly()方法,让fly()方法啥也不做,像下面这样

    public class RubberDuck extends Duck{
        public void display() {
            System.out.println("I am a rubber duck");
        }
    
        @Override
        public void fly() {
            
        }
    }
    

    虽然上面这个方法可以暂时解决这个问题,但是如果这个时候加入了一个木头鸭子,它既不会呱呱叫,也不会飞,那这个时候你是不是就会想到在木头鸭子里覆盖掉fly()方法和quack()方法。但这样带来的问题就是如果有成千上百个鸭子,每次都要检查quack()方法和fly()方法,这简直是无穷无尽的噩梦。

    那如果把fly()方法和quack()方法抽出来呢,放到一个Flyable接口和一个Quackable接口当中。让会飞的鸭子实现Flyable接口,会呱呱叫的鸭子实现Quackable()接口。
    Flyable接口

    public interface Flyable {
        void fly();
    }
    

    WhiteDuck类

    public class WhiteDuck extends Duck implements Flyable{
        public void display() {
            System.out.println("I am a white duck");
        }
    
        public void fly() {
            System.out.println("I am fly with wing");
        }
    }
    

    假如现在有个火箭鸭,它能以火箭的动力飞行,我们可以这样

    public class RocketDuck extends Duck implements Flyable{
        public void fly() {
            System.out.println("I can fly with rocket");
        }
    
        public void display() {
            System.out.println("I am a rocket duck");
        }
    }
    

    这样看好像没什么问题,每个种类的鸭子都可以选择性的实现自己想实现的接口。但是你忽视了一个非常大的问题,假如鸭子一共就有三种飞行方法(用翅膀飞、不会飞、以火箭动力去飞),这个时候如果你有几十种鸭子的话,就会造成大量的代码冗余,如果有的鸭子要修改一下飞行行为,就要对这些鸭子的fly()方法逐一的修改。

    那么我们再来看看这种情况策略模式怎么去做的呢?
    策略模式会将代码中变化的部分抽取出来封装(比如fly,quack),以便以后可以轻易的改动或扩充此部分,而不影响不需要变化的其他部分。其实这种思想是每个设计模式背后的精神所在。所有的设计模式都提供了一套方法让“系统中的某部分改变不会影响其他部分”。

    我们利用接口代表每个行为,比方说,FlyBehavior与QuackBehavior,而行为的每个实现都将实现其中的一个接口。所以这次鸭子类不会负责实现Flyable与Quackable接口,反而是由我们制造的一组其他类专门负责实现FlyBehavior与QuackBehavior,这种就称为“行为类”。所以实际的行为实现不会绑死在鸭子的子类当中。

    FlyBehavior接口

    public interface FlyBehavior {
        void fly();
    }
    

    FlyWithWing类,用翅膀飞

    public class FlyWithWing implements FlyBehavior {
        public void fly() {
            System.out.println("I can fly with wing");
        }
    }
    

    FlyWithRocket类,以火箭的动力飞

    public class FlyWithRocket implements  FlyBehavior {
        public void fly() {
            System.out.println("I can fly with rocket");
        }
    }
    

    FlyNoWay类,不会飞

    public class FlyNoWay implements FlyBehavior {
        public void fly() {
            System.out.println("I can not fly");
        }
    }
    

    QuackBehavior接口同理,这里就不贴代码了。

    我们看到这样的设计可以使相同的行为代码能被复用,即使我们再新增一些行为也不会影响既有的行为类,也不会影响到使用飞行行为的鸭子类。

    那么我们怎样将行为类和和鸭子类进行整合呢?
    首先,我们再Duck类中加入两个变量,分别为"flyBehavior"与"quackBehavior",申明为接口类型而不是具体的实现,每个鸭子都会动态的设置这些变量以在运行时引用正确的行为类型。我们再以两个相似的方法performFly()和performQuack()取代Duck类中的fly()方法和quack()方法。

    改过之后的Duck类

    public abstract class Duck {
        FlyBehavior flyBehavior;
    
        public void performFly(){
            flyBehavior.fly();
        }
        
        public abstract void display();
    }
    

    我们看到要在Duck类中,我们要实现飞行的行为,只需要flyBehavior去飞就行了,在Duck类中,我们不在乎flyBehavior接口的对象到底是什么,我们只要关心该对象如何进行飞行就行。

    接下来我们通过RocketDuck看一下我们怎样去设置flyBehavior变量

    public class RocketDuck extends Duck {
        public RocketDuck() {
            flyBehavior = new FlyWithRocket();
        }
    
        public void display() {
            System.out.println("I am a rocket duck");
        }
    }
    

    我们看到,RocketDuck类中的默认构造方法会设置flyBehavior变量为一个FlyWithRocket对象,当调用performFly()方法时就会去调用FlyWithRocket对象的fly()方法。我们写一个Main方法去执行看一下:

    public class Main {
        public static void main(String[] args) {
            Duck duck = new RocketDuck();
            duck.performFly();
        }
    }
    输出:
    I can fly with rocket
    

    我们还可以在Duck类中通过setter方法类设定鸭子的行为类,以达到随时改变鸭子行为的效果。

    public abstract class Duck {
        FlyBehavior flyBehavior;
    
        public void setFlyBehavior(FlyBehavior flyBehavior) {
            this.flyBehavior = flyBehavior;
        }
    
        public void performFly(){
            flyBehavior.fly();
        }
    
        public abstract void display();
    }
    

    假设现在有一个模型鸭ModelDuck,我们就可以这样

    public class ModelDuck extends Duck {
        public ModelDuck() {
            flyBehavior = new FlyWithWing();
        }
    
        public void display() {
            System.out.println("I am a model duck");
        }
    }
    

    再运行看一下:

    public class Main {
        public static void main(String[] args) {
            Duck duck = new ModelDuck();
            duck.performFly();
            duck.setFlyBehavior(new FlyNoWay());
            duck.performFly();
        }
    }
    输出:
    I can fly with wing
    I can not fly
    

    我们能看到通过setter方法可以动态的区改变鸭子的行为。

    那么假设现在我们的鸭子会讲话,但是每种鸭子会讲的语言不一样,有的会讲英文,有的会讲中文,还有的会讲汉语,这个时候我们应该怎么做呢?

    首先我们可以写一个SpeakBehavior接口,它有一个speak()方法。

    public interface SpeakBehavior {
        void speak();
    }
    

    原后会讲汉语的鸭子和会讲英语的鸭子分别实现SpeakBehavior接口

    public class SpeakChinese implements SpeakBehavior {
        public void speak() {
            System.out.println("I can speak chinese");
        }
    }
    
    public class SpeakEnglish implements SpeakBehavior {
        public void speak() {
            System.out.println("I can speak english");
        }
    }
    

    原后在Duck类中加入speakBehavior变量

    public abstract class Duck {
        FlyBehavior flyBehavior;
        SpeakBehavior speakBehavior;
    
        public void setSpeakBehavior(SpeakBehavior speakBehavior) {
            this.speakBehavior = speakBehavior;
        }
        
        public void performBehavior(){
            speakBehavior.speak();
        }
    
        public void setFlyBehavior(FlyBehavior flyBehavior) {
            this.flyBehavior = flyBehavior;
        }
    
        public void performFly(){
            flyBehavior.fly();
        }
    
        public abstract void display();
    }
    

    在RocketDuck类中添加说话的行为

    public class RocketDuck extends Duck {
        public RocketDuck() {
            flyBehavior = new FlyWithRocket();
            speakBehavior = new SpeakChinese();
        }
    
        public void display() {
            System.out.println("I am a rocket duck");
        }
    }
    

    我们运行看一下

    public class Main {
        public static void main(String[] args) {
            Duck duck = new RocketDuck();
            duck.performFly();
            duck.performSpeak();
            duck.setFlyBehavior(new FlyNoWay());
            duck.performFly();
            duck.setSpeakBehavior(new SpeakEnglish());
            duck.performSpeak();
        }
    }
    输出:
    I can fly with rocket
    I can speak chinese
    I can not fly
    I can speak english
    

    我们看到新加的行为不会影响到老的行为的运行。

    总结
    每一个鸭子都有一个FlyBehavior和一个SpeakBehavior,好将飞行和讲话委托给他们处理。当你将两个类结合起来使用,如同本例一般,这就是组合(composition)。这种做法和“继承”不同的地方在于,鸭子的行为不是继承来的,而是和适当的行为对象“组合”来的。这是一个很重要的技巧。其实是使用了我们的第三个设计原则:
    多用组合,少用继承

    相关文章

      网友评论

          本文标题:Head First设计模式学习笔记一策略模式

          本文链接:https://www.haomeiwen.com/subject/mdooiftx.html