学习Java设计模式——策略模式(Strategy Pattern)


以下通过概念、需求场景、需求分析、代码设计及实践、UML类图展示,一步步给大家介绍 “策略模式”


一、策略模式概念

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

二、需求场景

老板下达任务,今日之内设计一个游戏,模拟各类鸭子的行为。鸭子有好几个种类,而且每种鸭子的外观、游泳技能、飞行技能、叫声都不一样。

具体要求如下

1、鸭子种类:野鸭(Mallard duck)、红头鸭(Red Head duck)、橡皮鸭(Rubber duck)、诱饵鸭(Decoy Duck)

3、野鸭的外观是绿头, 红头鸭的外观是红头,橡皮鸭的外观是橡皮, 诱饵鸭的外观是木头

2、所有鸭子都会游泳

3、野鸭、红头鸭会飞,但橡皮鸭、诱饵鸭不会飞

4、野鸭、红头鸭的叫声是“Quack Quack ...”,而橡皮鸭的叫声是“Squeak Squeak....”,诱饵鸭不会叫

三、需求分析

【tips:设计原则描述请见文章末尾】

方法一:Duck中提供display、swim、fly、quack方法, 子类继承

分析:所有的子类都会有父类的行为,这和需求是不符的,且鸭子的行为在子类中会根据需求不断地被重写,代码无法复用,此方法不可行。

方法二:根据设计原则一,将变化和不会变化的部分分开,由于Duck类内的fly和quack会随着鸭子的不同而改变,我们可以取出fly和quack行为,分别建立新类,每组类实现各自的动作。

分析:鸭子的fly和quack行为被放在分开的类中,那么我们再对这两个行为进行分析:

1、鸭子的fly和quack行为分别可能有多种情况,根据设计原则二,我们还可以封装FlyBehaviorQuackBehavior接口,具体的行为编写在实现了这两个接口的类中

2、然后在Duck父类中包含设定fly和quack行为的方法(performFlyperformQuack),这样,Duck父类就不再需要知道行为的实现细节,而子类将可以使用这两个接口所表示的行为

3、为了让这两个行为可以动态地改变,并且可以在运行时动态地改变子类鸭子的飞行行为(setFlyBehaviorsetQuackBehavior

嗯,这是个好方法,很完美地利用了策略模式!下面我们就用这个方法来实践一下

四、代码设计及实践

根据以上对方法二的分析,下面进行代码设计及实践:

1、首先设计两个接口,FlyBehavior、QuackBehavior,还有它们对应的类,负责实现具体的行为

展示具体代码:
FlyBehavior接口及其实现

public interface FlyBehavior {
    public void fly();
}

public class FlyRocketPowered implements FlyBehavior {
    public void fly() {
        System.out.println("I'm flying with a rocket");
    }
}

public class FlyWithWings implements FlyBehavior {
    public void fly() {
        System.out.println("I'm flying!!");
    }
}

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

FlyBehavior接口及其实现

public interface QuackBehavior {
    public void quack();
}

public class MuteQuack implements QuackBehavior {
    public void quack() {
        System.out.println("<< Silence >>");
    }
}

public class Quack implements QuackBehavior {
    public void quack() {
        System.out.println("Quack");
    }
}

public class FakeQuack implements QuackBehavior {
    public void quack() {
        System.out.println("Qwak");
    }
}

public class Squeak implements QuackBehavior {
    public void quack() {
        System.out.println("Squeak");
    }
}

2、在Duck父类中加入“FlyBehavior”和“QuackBehavior”两个实例变量,并加入“performFly”“performQuack”方法,然后在子类构造器调用具体的实现,这里用MallardDuck进行举例。

Duck,java

public abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

    public Duck() {
    }

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

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

    abstract void display();

    public void swim() {
        System.out.println("All ducks float, even decoys!");
    }

}

MallardDuck.java

public class MallardDuck extends Duck {

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

    public void display() {
        System.out.println("I'm a real Mallard duck");
    }
}

使用 MiniDuckSimulator.java 测试一下MallardDuck

public class MiniDuckSimulator {
 
    public void MiniDuckSimulator() {
        MallardDuck mallard = new MallardDuck();
        mallard.performFly();
        mallard.performQuack();

    }
}

3、为了让这两个行为可以动态地改变,在鸭子子类中动态设定方法(setFlyBehaviorsetQuackBehavior),而不是在构造器内实例化。此后,我们可以随时调用这两个方法来改变鸭子的行为

在Duck.java中加入两个新方法

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

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

制造一个新的鸭子类型:模型鸭(ModelDuck.java)

public class ModelDuck extends Duck {
    public ModelDuck() {
        flyBehavior = new FlyNoWay();
        quackBehavior = new Quack();
    }

    public void display() {
        System.out.println("I'm a model duck");
    }
}

使用 MiniDuckSimulator.java 测试一下ModelDuck

public class MiniDuckSimulator {
 
    public void MiniDuckSimulator() {
        Duck model = new ModelDuck();
        model.performFly();
        model.setFlyBehavior(new FlyRocketPowered());//改变飞行行为
        model.performFly();
    }
}

五、UML类图展示

最后,我们使用UML类图来看看整体的格局


FlyBehavior接口及其实现
QuackBehavior接口及其实现
Duck继承关系

本设计模式参考的3个设计原则:

设计原则一:在应用中找出可能需要变化的地方,把它们独立封装起来,以便对其他部分不造成影响。

说明:比如,如果每次来一个新的需求,某块的代码都会发生变化,那么这部分的代码就要被抽取独立出来,和其他较为稳定的代码进行区分。

设计原则二:针对接口编程,而不是针对实现编程。

说明:这里的“接口”不是单独指定接口类型,而是一个概念,即 “超类型”,即抽象类或接口等。针对接口编程的关键在于多态,使用“超类型”去声明变量时,只要是实现了此超类型的类对应的对象,都可以指定给这个变量。

设计原则三:多用组合,少用继承

说明:使用组合建立系统具有很大的弹性,不仅可将行为算法族封装成类,更可以在运行时动态地改变行为

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。