java设计模式-策略模式(Strategy)

149 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情

概述

策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们可以相互替换,让算法独立于使用它的客户而独立变化。

  其实不要被晦涩难懂的定义所迷惑,策略设计模式实际上就是定义一个接口,只要实现该接口,并对接口的方法进行实现,那么不同的实现类就完成了不同的算法逻辑,而使用该接口的地方,则可以根据需要随意更改实现类,因为它们的接口一样。

  说白了,就是很多类实现了同一个接口,每个类对于该接口的方法有不同的实现。

image.png

策略模式的结构与实现

策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性,现在我们来分析其基本结构和实现方法。

模式的结构

策略模式的主要角色如下。

1、抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
2、具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
3、环境(Context)类:持有一个策略类的引用,最终给客户端调用。

案例

假设我们设计一个植物大战僵尸的的游戏,目前有两种僵尸,然后具有一些相同的属性,但是可能个别属性值不一样,如下: image.png 根据上图,我们设计代码如下:

根据需求设计功能

先将属性抽象出来

//定义各属性,将相同的放到抽象类中
abstract class AbstractZombie{
    //不一样的,我们设置一个抽象方法,让不同的僵尸进行复写
    public abstract void display();
    //一样的属性,直接进行实现处理
    public void attact(){
        System.out.println("咬.");
    }
    public void move(){
        System.out.println("一步一步移动.");
    }
}

实现普通僵尸

//普通僵尸
class NormalZombie extends AbstractZombie{
    @Override
    public void display() {
        System.out.println("我是普通僵尸.");
    }
}

实现旗手僵尸

//旗手僵尸
class FlagZombie extends AbstractZombie{
    @Override
    public void display() {
        System.out.println("我是旗手僵尸");
    }
}

测试

public static void main(String[] args) {
    AbstractZombie zombie=new NormalZombie();
    zombie.display();
    zombie.move();
    zombie.attact();
    System.out.println("-----------------------------");
    AbstractZombie zombie1=new FlagZombie();
    zombie1.display();
    zombie1.move();
    zombie1.attact();
}

执行结果

image.png

新的需求

如果后面我们进行版本升级,增加了一些僵尸,如下图

image.png 按照之前的逻辑,我们继续添加功能

增加大头僵尸

//大头僵尸移动不变,我们重写外观和攻击方式
class BigHeadZombie extends AbstractZombie{

    @Override
    public void display() {
        System.out.println("我是大头僵尸.");
    }
    @Override
    public void attact() {
        System.out.println("头撞.");
    }
}

假如后面又有一个新的僵尸,攻击方式也是头撞,继承大头僵尸

//新的僵尸
class NewZombie extends BigHeadZombie{
    @Override
    public void display() {
        System.out.println("我是新僵尸");
    }
    @Override
    public void attact() {
        super.attact();
    }
}

问题

假如后面再有其他新的僵尸,也和另外的僵尸有共同属性,这样各个僵尸类之间就会相互继承。最终形成的一个错综复杂的链式关系,对于这样的关系,很显然,我们系统是要严格禁止的,那么我们对他们的关系进行升级如下:

新的方案

image.png 根据上图,我们可以发现,僵尸的属性是不变的,变化的只是他们各个属性的值。因此我们进行如下设计

将属性写为接口

interface Moveable{
    void move();
}
interface Attactable{
    void attact();
}

抽象一个僵尸

abstract class Zomble{
    abstract public void display();
    Moveable moveable;
    Attactable attactable;
    abstract void move();
    abstract void attact();
    //初始化的时候,就给僵尸移动和攻击的能力
    public Zomble(Moveable moveable, Attactable attactable) {
        this.moveable = moveable;
        this.attactable = attactable;
    }

    public Moveable getMoveable() {
        return moveable;
    }

    public void setMoveable(Moveable moveable) {
        this.moveable = moveable;
    }

    public Attactable getAttactable() {
        return attactable;
    }

    public void setAttactable(Attactable attactable) {
        this.attactable = attactable;
    }
}

给不同属性赋值

移动的属性

class StepByStep implements Moveable{
    @Override
    public void move() {
        System.out.println("一步一步移动.");
    }
}

攻击的属性

class BitAttact implements Attactable{
    @Override
    public void attact() {
        System.out.println("撞击");
    }
}

实现普通僵尸

class NormalZomble extends Zomble{

    public NormalZomble() {
        super(new StepByStep(),new BitAttact());
    }
    
    public NormalZomble(Moveable moveable, Attactable attactable) {
        super(moveable, attactable);
    }

    @Override
    public void display() {
        System.out.println("我是普通僵尸.");
    }

    @Override
    void move() {
        moveable.move();
    }

    @Override
    void attact() {
        attactable.attact();
    }
}

新的僵尸增加

如果这时候僵尸增加,可能攻击和移动的方式不一样,我们就可以实现属性接口,定义不同的值即可,如下

给属性设置新的值

移动方式

class Dance implements Moveable{
    @Override
    public void move() {
        System.out.println("跳舞移动.");
    }
}

攻击方式

class HeadAttact implements Attactable{
    @Override
    public void attact() {
        System.out.println("用头撞击");
    }
}

实现新僵尸

class FlagZomble extends Zomble{
    public FlagZomble(){
        super(new Dance(),new HeadAttact());
    }
    public FlagZomble(Moveable moveable, Attactable attactable) {
        super(moveable, attactable);
    }
    @Override
    public void display() {
        System.out.println("我是旗手僵尸");
    }

    @Override
    void move() {
        moveable.move();
    }

    @Override
    void attact() {
        attactable.attact();
    }
}

测试

public static void main(String[] args) {
    Zomble zomble=new NormalZomble();
    zomble.display();
    zomble.move();
    zomble.attact();
    System.out.println("----------------");
    Zomble zomble1=new FlagZomble();
    zomble1.display();
    zomble1.move();
    zomble1.attact();
}

执行结果

image.png

总结

代码经过改造,我们发现,僵尸只要属性不变,属性增加任何值,只要我们进行具体属性实现,就可以给新僵尸调用,不用修改之前的代码,符合开闭原则。

策略模式的优点

  (1)策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。

  (2)使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。

策略模式的缺点

  (1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。

  (2)由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。