阅读 253

重识设计模式-策略模式(Strategy Pattern)

本文已同步发表到我的技术微信公众号,扫一扫文章底部的二维码或在微信搜索 “程序员驿站”即可关注,不定期更新优质技术文章。同时,也欢迎加入QQ技术群(群号:650306310)一起交流学习!

策略模式在代码编写过程中有经常使用到,比如,JAVA AWT 中的 LayoutManager,Android属性动画源码中的Interpolator(插值器)等,下面我将介绍策略模式相关知识点,希望对大家能够有帮助。

定义

策略模式定义了一系列的算法,并将每一个算法封装起来,使每种算法之间可以相互替换。策略模式主要解决在有多种算法相似的情况下,使用if...else所带来的复杂和难以维护的问题。通常是在一个系统有许多许多类,而区分它们的只是他们直接的行为时使用。

角色

策略模式会涉及到三个角色,分别为环境(Context)角色、抽象策略(Strategy)角色、具体策略(ConcreteStrategy)角色。

环境(Context)角色: 持有一个Strategy的引用;
抽象策略(Strategy)角色: 这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口;
具体策略(ConcreteStrategy)角色: 包装了相关的算法或行为。

案例回放

某院线公司需要开发一套影院售票系统,在该系统中需要为不同类型的用户提供不同的电影票打折方式,具体打折方案如下:
(1) 学生凭学生证可享受票价8折优惠;
(2) 年龄在10周岁及以下的儿童可享受每张票减免10元的优惠( 原始票价需大于等于20元) ;
(3) 影院VIP用户除享受票价半价优惠外还可进行积分,积分累计到一定额度可换取电影院赠送的奖品。

Note:该系统在将来可能还要根据需要引入新的打折方式。

为了实现打折算法的复用,并能够灵活地向系统中增加新的打折方式,这里我们使用策略模式作为电影院打折方案的基础结构再好不过了,使用策略模式之后基本结构如下:

以上结构图中MovieTicket充当环境类角色,Discount充当抽象策略角色,StudentDiscount、ChildrenDiscount 和VIPDiscount充当具体策略角色。下面是各个类的完整代码:

MovieTicket.java

/**
 * Created by chendx on 2019/3/15
 * @since 1.0
 * 电影票类:环境类
 */
public class MovieTicket {
    private double price;
    /** 维持一个对抽象折扣类的引用 */
    private Discount discount;

    public void setPrice(double price) {
        this.price = price;
    }

    /**
     * 注入一个折扣类对象
     */
    public void setDiscount(Discount discount) {
        this.discount = discount;
    }

    public double getPrice() {
        //调用折扣类的折扣价计算方法
        return discount.calculate(this.price);
    }
}
复制代码

Discount.java

/**
 * Created by chendx on 2019/3/15
 * @since 1.0
 * 折扣类:抽象策略类
 */
public interface Discount {
    double calculate(double price);
}
复制代码

StudentDiscount.java

/**
 * Created by chendx on 2019/3/15
 * @since 1.0
 * 学生票折扣类:具体策略类
 */
public class StudentDiscount implements Discount {
    public double calculate(double price) {
        System.out.println("学生票:");
        return price * 0.8;
    }
}
复制代码

ChildrenDiscount.java

/**
 * Created by chendx on 2019/3/15
 * @since 1.0
 * 儿童票折扣类:具体策略类
 */
public class ChildrenDiscount implements Discount {
    public double calculate(double price) {
        System.out.println("儿童票:");
        return price - 10;
    }
}
复制代码

VIPDiscount.java

/**
 * Created by chendx on 2019/3/15
 * @since 1.0
 * VIP会员票折扣类:具体策略类
 */
public class VIPDiscount implements Discount {
    public double calculate(double price) {
        System.out.println("VIP票:");
        System.out.println("增加积分!");
        return price * 0.5;
    }
}
复制代码

编写客户端测试代码:

Client.java

/**
 * Created by chendx on 2019/3/15
 * @since 1.0
 * 客户端测试代码
 */
public class Client {
    public static void main(String args[]) {
        double originalPrice = 60.0;
        double currentPrice;

        MovieTicket mt = new MovieTicket();
        mt.setPrice(originalPrice);

        System.out.println("原始价为:" + originalPrice);
        System.out.println("---------------------------------");

        Discount discount = new StudentDiscount();
//        Discount discount = new ChildrenDiscount();
//        Discount discount = new VIPDiscount();
        
        mt.setDiscount(discount); //注入折扣对象
        currentPrice = mt.getPrice();
        System.out.println("折后价为:" + currentPrice);
    }
}
复制代码

编译并运行程序,输出结果如下:

原始价为:60.0
---------------------------------
学生票:
折后价为:48.0
复制代码

如果需要更换具体策略类,例如将学生票改为儿童票,只需将具体策略类StudentDiscount改为ChildrenDiscount即可:

Client.java

/**
 * Created by chendx on 2019/3/15
 * @since 1.0
 * 客户端测试代码
 */
public class Client {
    public static void main(String args[]) {
        double originalPrice = 60.0;
        double currentPrice;

        MovieTicket mt = new MovieTicket();
        mt.setPrice(originalPrice);

        System.out.println("原始价为:" + originalPrice);
        System.out.println("---------------------------------");

//        Discount discount = new StudentDiscount();
        Discount discount = new ChildrenDiscount();
//        Discount discount = new VIPDiscount();
        
        mt.setDiscount(discount); //注入折扣对象
        currentPrice = mt.getPrice();
        System.out.println("折后价为:" + currentPrice);
    }
}
复制代码

重新运行客户端程序,输出结果如下:

原始价为:60.0
---------------------------------
儿童票:
折后价为:50.0
复制代码

如果需要增加新的打折方式,原有代码均无须修改,只要增加一个新的折扣类作为抽象折扣类的子类,实现在抽象折扣类中声明的打折方法,将原有具体折扣类类名改为新增折扣类类名即可,完全符合“开闭原则”,

典型应用

我们经常使用到属性动画插值器就是策略模式的典型应用之一,这里为了节省篇幅,就不在分析属性动画插值器在源码中的整个调用流程,属性动画插值器类图如下:

优点

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

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

缺点

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

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

重点

策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。

特点

运行时策略的唯一性: 运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同的策略实现中切换,但是同时只能使用一个。

平等性: 策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法,大家的地位是完全一样的,正因为这个平等性,才能实现算法之间可以相互替换。所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的。

使用场景

1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。

2、一个系统需要动态地在几种算法中选择一种。

3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

关注我的技术公众号"程序员驿站",每天都有优质技术文章推送,微信扫一扫下方二维码即可关注:

image