设计模式 策略模式(strategy Pattern)详细解读

一、设计思想演进

1.1 预想场景

我们有一个会员业务,在售卖产品时价格是根据会员的类型来进行折扣,比如:普通会员不打折,VIP会员打8折,超级VIP会员7折。
现在做一个价格模块的折扣计算功能,针对不同会员类型,进行折扣计算,代码如下:

public class PriceCenter {
    /**
     * 对原价进行折扣计算
     * @param price 原价
     * @param memberType 会员类型
     * @return 计算折扣后价格
     */
    public BigDecimal discount(BigDecimal price, String memberType){
        if ("普通会员".equals(memberType)) {
            System.out.println("普通会员没有折扣");
        }else if ("VIP会员".equals(memberType)) {
            System.out.println("vip会员打9折!");
            price = price.multiply(new BigDecimal("0.9")).setScale(2, RoundingMode.HALF_UP);
        }else if("超级VIP会员".equals(memberType)){
            System.out.println("超级VIP会员打5折!");
            price = price.multiply(new BigDecimal("0.7")).setScale(2,RoundingMode.HALF_UP);
        }
        return price;
    }
}

一开始代码逻辑很简单,但我们把每种会员的折扣算法都放在了一个方法里,随着业务的增加代码会越来越多,会十分臃肿。

1.2 优化

下面我们进行优化,把每种会员的折扣算法单独封装成一个方法,代码如下:

public class PriceCenter {
    /**
     * 对原价进行折扣计算
     *
     * @param price      原价
     * @param memberType 会员类型
     * @return 计算折扣后价格
     */
    public BigDecimal discount(BigDecimal price, String memberType) {
        if ("普通会员".equals(memberType)) {
            price = memberNormalDiscount(price);
        } else if ("VIP会员".equals(memberType)) {
            price = memberVIPDiscount(price);
        } else if ("超级VIP会员".equals(memberType)) {
            price = memberSuperVIPDiscount(price);
        }
        return price;
    }

    private BigDecimal memberNormalDiscount(BigDecimal price) {
        System.out.println("普通会员没有折扣");
        return price;
    }

    private BigDecimal memberVIPDiscount(BigDecimal price) {
        System.out.println("vip会员打9折!");
        price = price.multiply(new BigDecimal("0.9")).setScale(2, RoundingMode.HALF_UP);
        return price;
    }

    private BigDecimal memberSuperVIPDiscount(BigDecimal price) {
        System.out.println("超级VIP会员打8折!");
        price = price.multiply(new BigDecimal("0.8")).setScale(2, RoundingMode.HALF_UP);
        return price;
    }
}

当我们想要修改某个算法的逻辑时只要,修改单独的方法就可以了。
上面看似让代码变得优雅了,但还是存在一些问题:
问题一:每新增一个会员类型的时候,首先要添加一个该种客户类型的报价算法方法,然后在方法discount中再加一个else if的分支,这违反了设计原则之一的开闭原则(open-closed-principle).

开闭原则:
对于扩展是开放的(Open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。
对于修改是关闭的(Closed for modification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。

问题二:有的时候我们需要做额外的活动针对不同的节日,每个else if中的逻辑要做很大改动,活动过去之后又要恢复,如果刚入职的新手就会想直接改动原来else if种代码,活动过去之后在改动回去,这样其实很不好。

思考:每次执行discount方法时其实只会执行其中一个分支的代码,是否有什么办法可以每次动态变化方法中的内容,于是我们就想到是否可以执行的时候调用固定的类的方法,具体的执行逻辑是否可以通过当一个参数传到这个类中。

为了解决上面的问题,这个时候策略模式就应运而生了

二、什么是策略设计模式?

2.1 概念

其思想是针对一组算法,将每一种算法都封装到具有共同接口的独立的类中,从而是它们可以相互替换。策略模式可以让算法在不影响客户端的情况下发生变化。

2.2 类型

行为型设计模式

2.3 UML结构

在这里插入图片描述

2.4 三类角色

  • 环境(Context):也叫上下文,它持有一个Strategy的引用。
  • 抽象策略(Strategy):这是一个抽象角色,通常由一个接口或抽象类实现。用来约束一系列具体的策略。
  • 具体策略(ConcreteStrategy):具体的策略实现。策略即算法。

2.5 一般通用实现*

策略接口:

//策略接口
public interface IStrategy {
    //定义的抽象算法方法 来约束具体的算法实现方法
    public void algorithmMethod();
}

具体的策略实现:

// 具体的策略实现A
public class ConcreteStrategyA implements IStrategy {
    //具体的算法实现
    @Override
    public void algorithmMethod() {
        System.out.println("我是具体的算法A");
    }
}
// 具体的策略实现B
public class ConcreteStrategyB implements IStrategy {
    //具体的算法实现
    @Override
    public void algorithmMethod() {
        System.out.println("我是具体的算法B");
    }
}

策略上下文

/**
 * 策略上下文
 */
public class StrategyContext {
    //持有一个策略实现的引用
    private IStrategy strategy;
    //使用构造器注入具体的策略类
    public StrategyContext(IStrategy strategy) {
        this.strategy = strategy;
    }

    //也可以通过set方法注入具体策略
    public void setStrategy(IStrategy strategy) {
        this.strategy = strategy;
    }

    public void contextMethod(){
        //调用策略实现的方法
        strategy.algorithmMethod();
    }
}

三、根据设计模式改造业务

折扣策略接口

public interface IDiscountStrategy {
    BigDecimal discount(BigDecimal price);
}

普通会员折扣策略

public class MemberNormalDiscount implements IDiscountStrategy {
    @Override
    public BigDecimal discount(BigDecimal price) {
        System.out.println("普通会员没有折扣");
        return price;
    }
}

VIP会员折扣策略

public class MemberVIPDiscount implements IDiscountStrategy {
    @Override
    public BigDecimal discount(BigDecimal price) {
        System.out.println("vip会员打9折!");
        price = price.multiply(new BigDecimal("0.9")).setScale(2, RoundingMode.HALF_UP);
        return price;
    }
}

超级VIP会员折扣策略

public class MemberSuperVIPDiscount implements IDiscountStrategy {
    @Override
    public BigDecimal discount(BigDecimal price) {
        System.out.println("超级VIP会员打8折!");
        price = price.multiply(new BigDecimal("0.8")).setScale(2, RoundingMode.HALF_UP);
        return price;
    }
}

折扣上下文

public class DiscountContext {

    private IDiscountStrategy discountStrategy;

    public DiscountContext(IDiscountStrategy strategy) {
        this.discountStrategy = strategy;
    }

    public void setDiscountStrategy(IDiscountStrategy iDiscountStrategy) {
        this.discountStrategy = iDiscountStrategy;
    }

    public BigDecimal discount(BigDecimal price){
        return discountStrategy.discount(price);
    }
}

客户端

public class Client {
    public static void main(String[] args) {

        BigDecimal price1 = new BigDecimal(1000);
        MemberNormalDiscount memberNormalDiscount = new MemberNormalDiscount();
        DiscountContext discountContext = new DiscountContext(memberNormalDiscount);
        price1 = discountContext.discount(price1);
        System.out.println(price1);

        BigDecimal price2 = new BigDecimal(1000);
        discountContext.setDiscountStrategy(new MemberSuperVIPDiscount());
        price2 = discountContext.discount(price2);
        System.out.println(price2);
    }
}

同时测试两种算法,结果如下:

普通会员没有折扣
1000
超级VIP会员打8折!
800.00

下面我们来想一想,如果这时候推出了一个员工内部会员,买东西可享5折怎么实现?
我们只要新增一个内部会员折扣策略就可以了,代码如下:

public class MemberInnerDiscount implements IDiscountStrategy {
    @Override
    public BigDecimal discount(BigDecimal price) {
        System.out.println("内部会员打5折!");
        price = price.multiply(new BigDecimal("0.5")).setScale(2, RoundingMode.HALF_UP);
        return price;
    }
}
public class Client {
    public static void main(String[] args) {
        BigDecimal price1 = new BigDecimal(1000);
        MemberNormalDiscount memberNormalDiscount = new MemberNormalDiscount();
        DiscountContext discountContext = new DiscountContext(memberNormalDiscount);
        price1 = discountContext.discount(price1);
        System.out.println(price1);

        BigDecimal price2 = new BigDecimal(1000);
        discountContext.setDiscountStrategy(new MemberSuperVIPDiscount());
        price2 = discountContext.discount(price2);
        System.out.println(price2);

        BigDecimal price3 = new BigDecimal(1000);
        discountContext.setDiscountStrategy(new MemberInnerDiscount());
        price3 = discountContext.discount(price3);
        System.out.println(price3);
    }
}

四、if else如何解决?

在策略模式使用过程中我们发现,策略模式其实是通过context来解决执行具体逻辑可以根据需要随时变化,也就是需要什么算法就让context持有。但客户端中if else并没有减少
我们将上面代码改造的更贴近一些现实情况

public class Client {
    public static void main(String[] args) {
        //会员类型
        String memberType = "VIP";
        //原价
        BigDecimal price = new BigDecimal(1000);
        DiscountContext discountContext = new DiscountContext();
        if ("normal".equals(memberType)) {
            MemberNormalDiscount memberNormalDiscount = new MemberNormalDiscount();
            discountContext.setDiscountStrategy(new MemberNormalDiscount());
            price = discountContext.discount(price);
        } else if ("VIP".equals(memberType)) {
            BigDecimal price2 = new BigDecimal(1000);
            discountContext.setDiscountStrategy(new MemberSuperVIPDiscount());
            price = discountContext.discount(price);
        }else if ("superVIP".equals(memberType)) {
            discountContext.setDiscountStrategy(new MemberInnerDiscount());
            price = discountContext.discount(price);
        }
        System.out.println(price);
    }
}

4.1使用工厂方法

工厂方法

/**
 * 会员折扣策略工厂
 */
public class MemberDiscountFactory {
    private static final Map<String, IDiscountStrategy> map = new HashMap<>();
    static {
        map.put("normal", new MemberNormalDiscount());
        map.put("VIP", new MemberVIPDiscount());
        map.put("superVIP", new MemberSuperVIPDiscount());
    }
    public static IDiscountStrategy getDiscountStrategy(String memberType) {
        return map.get(memberType);
    }
}

客户端

public class Client1 {
    public static void main(String[] args) {
        //这里使用最简单方式表示类型
        String memberType = "VIP";
        //原价
        BigDecimal price = new BigDecimal(1000);
        //使用工厂获取具体策略
        IDiscountStrategy iDiscountStrategy = MemberDiscountFactory.getDiscountStrategy(memberType);
        DiscountContext discountContext = new DiscountContext(iDiscountStrategy);
        BigDecimal result = discountContext.discount(price);
        System.out.println("打折后的价格为"+result);
    }
}

发现代码优化后简洁了很多,工厂方法只是一种解决方式,其主要的思路就是将各个策略放到容器中去来方便获取。大家可以发散思路,比如枚举,反射等等

思考:策略的选择我们交给了客户端,及时使用了工厂方法对客户端也会觉得过于麻烦,我们是否可以将具体选择哪些策略的权利交给Context上下文,这样在折扣选择的部分也可以彻底和客户端解耦

五、Context在策略模式中的作用

5.1 作用: 让客户端和具体策略完全解耦

在没有context的情况下, client直接持有策略接口, 如果具体策略改变, 需要更改client的代码, 但是如果让context持有IStrategy
那就不需要更改client

client在整个处理流程中只要知道他调了一个策略就行了, 不必要知道具体策略是什么, 甚至策略的选择都让策略上下文去维护。
有很多策略模式的实现, 都是在context中维护了strategy list, 然后在里面通过参数去选取对应的策略, 这块"选取对应策略"放在context中比放在client中更合适

5.2 再次改造

Context上下文

public class DiscountContext1 {

    String memberType;

    private IDiscountStrategy discountStrategy;

    public DiscountContext1(String memberType) {
        this.memberType = memberType;
        this.discountStrategy = MemberDiscountFactory.getMedalService(memberType);
    }

    public String getMemberType() {
        return memberType;
    }

    public void setMemberType(String memberType) {
        this.memberType = memberType;
    }

    public IDiscountStrategy getDiscountStrategy() {
        return discountStrategy;
    }

    public BigDecimal discount(BigDecimal price){
        return discountStrategy.discount(price);
    }
}

客户端

public class Client2 {
    public static void main(String[] args) {
        String memberType = "VIP";
        //原价
        BigDecimal price = new BigDecimal(1000);
        DiscountContext1 discountContext = new DiscountContext1(memberType);
        BigDecimal result = discountContext.discount(price);
        System.out.println("打折后的价格为"+result);
    }
}

5.3 上下文当做参数传入到具体策略中

具体的策略对象可以从上下文中获取所需数据,可以将上下文当做参数传入到具体策略中,具体策略通过回调上下文中的方法来获取其所需要的数据。
**假设情景:**要求每个策略能展示会员类型,而且支持以后一些公用字段新增
修改策略接口

public interface IDiscountStrategy1 {
    BigDecimal discount(DiscountContext2 discountContext2);
}

修改一个具体策略

public class MemberNormalDiscount1 implements IDiscountStrategy1 {
    @Override
    public BigDecimal discount(DiscountContext2 discountContext2) {
        String memberType = discountContext2.getMemberType();
        BigDecimal price = discountContext2.getPrice();
        System.out.println(memberType + "没有折扣");
        return price;
    }
}

修改Factory

public class MemberDiscountFactory1 {
    private static final Map<String, IDiscountStrategy1> map = new HashMap<>();
    static {
        map.put("normal", new MemberNormalDiscount1());
    }
    public static IDiscountStrategy1 getDiscountStrategy(String memberType) {
        return map.get(memberType);
    }
}

修改Context

public class DiscountContext2 {

    private String memberType;

    private BigDecimal price;

    private IDiscountStrategy1 discountStrategy;

    public DiscountContext2(String memberType) {
        this.memberType = memberType;
        this.discountStrategy = MemberDiscountFactory1.getDiscountStrategy(memberType);
    }

    public String getMemberType() {
        return memberType;
    }

    public void setMemberType(String memberType) {
        this.memberType = memberType;
    }

    public BigDecimal getPrice() {
        return price;
    }

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

    public IDiscountStrategy1 getDiscountStrategy() {
        return discountStrategy;
    }

    public BigDecimal discount(BigDecimal price){
        this.price = price;
        return discountStrategy.discount(this);
    }
}

client

public class Client3 {
    public static void main(String[] args) {
        //这里使用最简单方式表示类型
        String memberType = "VIP";
        //原价
        BigDecimal price = new BigDecimal(1000);
        DiscountContext2 discountContext = new DiscountContext2(memberType);
        BigDecimal result = discountContext.discount(price);
        System.out.println("打折后的价格为"+result);
    }
}

测试结果

normal没有折扣
打折后的价格为1000

六、策略模式在JDK中有哪些应用?

6.1 比较器Comparator

在Java的集合框架中,经常需要通过构造方法传入一个比较器Comparator,或者创建比较器传入Collections的静态方法中作为方法参数,进行比较排序等,使用的是策略模式。

在该比较架构中,Comparator就是一个抽象的策略;一个类实现该结构,并实现里面的compare方法,该类成为具体策略类;Collections类就是环境角色,他将集合的比较封装成静态方法对外提供api。

6.2 ThreadPoolExecutor中的四种拒绝策略

在创建线程池时,需要传入拒绝策略,当创建新线程使当前运行的线程数超过maximumPoolSize时,将会使用传入的拒绝策略进行处理。

  • AbortPolicy:直接抛出异常。
  • CallerRunsPolicy:只用调用者所在线程来运行任务。
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
  • DiscardPolicy:不处理,丢弃掉。

这里使用的就是策略模式。

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值