设计模式--策略模式

策略模式

  • 定义

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

先不用着急理解定义,先看下面的例子

  • 栗子

假设我们有一个Car类(代表所有的汽车),我们知道汽车都可以发动、加速、刹车等,而现在的汽车种类有非常非常多,不同的品牌,使用不同的燃料等等,于是,为了提高代码的复用性,我们可以将所有汽车都具有的共性封装到Car类中,不同类型的车有各自的类,他们的个性就在各自的类中实现即可。代码如下:

Car类

public class Car{

    /**
     * 发动
     */
    public void start(){
        System.out.println("发动");
    }

    /**
     * 加速
     */
    public void accele(){
        System.out.println("加速");
    }

    /**
     * 刹车
     */
    public void brake(){
        System.out.println("刹车");
    }
}

宝马汽车类

/**
 * 奔驰 继承Car类
 */
public class BWMCar extends Car{
    
    /**
     * 查看汽车品牌
     */
    public void getBrand(){
        System.out.println("宝马");
    }

    /**
     * 补充燃料
     */
    public void refuel(){
        System.out.println("加油");
    }
}

特斯拉汽车类

/**
 * 特斯拉 继承Car
 */
public class TeslaCar extends Car{
    /**
     * 查看汽车品牌
     */
    public void getBrand(){
        System.out.println("特斯拉");
    }

    /**
     * 补充燃料
     */
    public void refuel(){
        System.out.println("充电");
    }
}

测试运行类

public class Test {
    public static void main(String[] args) {
        BWMCar bwm = new BWMCar();
        TeslaCar tesla = new TeslaCar();

        System.out.println("=======宝马=======");
        bwm.start();
        bwm.accele();
        bwm.brake();
        bwm.getBrand();
        bwm.refuel();

        System.out.println("========特斯拉========");
        tesla.start();
        tesla.accele();
        tesla.brake();
        tesla.getBrand();
        tesla.refuel();
        
    }
}

运行结果:

=======宝马=======
发动
加速
刹车
宝马
加油
========特斯拉========
发动
加速
刹车
特斯拉
充电

类图

image-20200922204013861

对于现在来讲这样做已经可以了,但是,开发过程中从来不缺新需求的提出。假设此时比亚迪的电动车也要添加上,如果我们继续按照上卖弄继承的方式,可以得到下面比亚迪电动车类。

public class BYDCar extends Car{
    /**
     * 查看汽车品牌
     */
    public void getBrand(){
        System.out.println("比亚迪");
    }

    /**
     * 补充燃料
     */
    public void refuel(){
        System.out.println("充电");
    }
}

我们可以看到,BYDCar 类和TeslaCar类除了getBrand() 的方法不一样外,refuel() 方法是一模一样的,那么我们是不是可以将这个共有的方法提取出来呢?

提取到Car中显然是不可以的,因为BWMCar 同时也继承的Car类,如果将refuel()【充电】方法提取到Car中,那么所有补充燃料方式不是充电的Car的子类都需要重写refuel()方法,当子类特别多的时候,将会是非常麻烦的事情,后期如果有变动,修改起来也会令人疯狂!

这时候你可能会有这样的想法,我们可以再设计出两个子类,一个作为汽油车的父类,一个作为电动车的父类,由这两个类继承Car,得到的类图如下所示

image-20200922204519584

看似这样是可以的,但是我们知道比亚迪也有传统的汽油车,那用继承该如何实现呢?每个品牌下都有很多型号的汽车,用继承全部实现可以吗?

不论是如何实现,我们从上面两次实现中都发现了继承的缺陷,显然,继承很难满足快速变化的需求,或者说继承不是最佳的实现方式!

分析上面类图可以发现,子类很多的方法都是相同的,只是具体的实现不同。是不是非常熟悉的感觉,没错,就是接口!我们接下来的解决方法就是使用接口来实现。

首先先抽离获取品牌的方法,我们知道,汽车的品牌有很多,同时每个品牌又有非常多的型号的汽车,所以品牌非常有必要抽离!

image-20200922205630936

我们抽离出一个接口Brand,所有的汽车品牌都需要实现这个接口,而具体的实现都是由具体的品牌自己确定。

同理,可以抽离出燃料补充接口:

image-20200922210408351

同时我们再Car类中添加这两种行为的属性,同时抽离这两种行为,并将其委托给具体的行为接口实现类去执行

image-20200922210910728

完整的类图如下:

image-20200922212707203

再这里使用的是组合来整合所有的行为,而不是继承,下面用代码来实现吧

Brand接口

/**
 * 品牌接口
 */
public interface Brand {

    void getBrand();
    
}

品牌实现类

/**
 * 宝马品牌实现类 继承品牌接口
 */
public class BWM implements Brand{
    
    public void getBrand(){
        System.out.println("宝马");
    }
}
public class Tesla implements Brand{
    
    public void getBrand(){
        System.out.println("特斯拉");
    }
}

public class BYD implements Brand{
    
    public void getBrand(){
        System.out.println("比亚迪");
    }
}

补充燃料接口

/**
 * 补充燃料接口
 */
public interface RefuelWay {
    
    void refuel();
}

补充燃料实现接口

/**
 * 加油补充燃料实现类
 */
public class Oil implements RefuelWay{
    
    public void refuel(){
        System.out.println("加油");
    }
}

public class Electricity implements RefuelWay{
    
    public void refuel(){
        System.out.println("充电");
    }
}

Car实现类

public class Car{

    /**
     * 展示品牌行为
     */
    private Brand brand;

    /**
     * 补充燃料方式行为
     */
    private RefuelWay refuelWay;

    /**
     * 构造方法中设置两种行为
     */
    public Car(Brand brand,RefuelWay refuelWay){
        this.brand = brand;
        this.refuelWay = refuelWay;
    }

    /**
     * 展示品牌 委托给品牌具体实现类
     */
    public void getBrand(){
        brand.getBrand();
    }

    /**
     * 补充燃料 委托给补充燃料的具体实现类
     */
    public void refuel(){
        refuelWay.refuel();
    }

    /**
     * 发动
     */
    public void start(){
        System.out.println("发动");
    }

    /**
     * 加速
     */
    public void accele(){
        System.out.println("加速");
    }

    /**
     * 刹车
     */
    public void brake(){
        System.out.println("刹车");
    }

    /**
     * 展示方法 方便我们测试
     */
    public void display(){
        this.start();
        this.accele();
        this.brake();
        this.getBrand();
        this.refuel();
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        // 创建不同的品牌
        Brand bwm = new BWM();
        Brand tesla = new Tesla();
        Brand byd = new BYD();

        // 创建不同的燃料补充方式
        RefuelWay oil = new Oil();
        RefuelWay electricity = new Electricity();

        // 构造 宝马的汽油车
        System.out.println("------------构造 宝马的汽油车--------------");
        Car car1 = new Car(bwm,oil);
        car1.display();

        // 构造 宝马电动车
        System.out.println("------------构造 宝马电动车--------------");
        Car car2 = new Car(bwm, electricity);
        car2.display();

        // 构造 特斯拉电动车
        System.out.println("------------构造 特斯拉电动车--------------");
        Car car3 = new Car(tesla,electricity);
        car3.display();

        // 构造 比亚迪电动车
        System.out.println("------------构造 比亚迪电动车--------------");
        Car car4 = new Car(byd,electricity);
        car4.display();

        // 构造 比亚迪汽油车
        System.out.println("------------构造 比亚迪汽油车--------------");
        Car car5 = new Car(byd,oil);
        car5.display();
    }
}

输出:

------------构造 宝马的汽油车--------------
发动
加速
刹车
宝马
加油
------------构造 宝马电动车--------------
发动
加速
刹车
宝马
充电
------------构造 特斯拉电动车--------------
发动
加速
刹车
特斯拉
充电
------------构造 比亚迪电动车--------------
发动
加速
刹车
比亚迪
充电
------------构造 比亚迪汽油车--------------
发动
加速
刹车
比亚

对于一开始使用的继承,如果父类后期发生变化,那么对于子类的影响是非常大的,同时继承也非常的不灵活。而使用组合时,如果后期行为发生了变化,我们只需要切换这个行为的实现类即可,而Car中使用的时实现这个行为的接口,对其子类不会产生影响,如果需要添加新的行为,只需要添加一个新的行为接口,并将其委托给具体的实现类即可。

由此我们可以得出:实际开发中,我们应当多用组合,少用继承。

这里对展示品牌和补充燃料方式行为的封装就是使用的策略模式,再次阅读策略模式的定义,应当容易理解了。

posted @ 2020-09-22 21:52  UtilMan  阅读(146)  评论(0编辑  收藏  举报