首页 > 面试常考题 设计模式(二)-策略设计模式
头像
好未来-研发专家-高管
发布于 2021-07-06 12:58
+ 关注

面试常考题 设计模式(二)-策略设计模式

策略设计模式---透彻讲解

一. 什么是策略设计模式

设计模式有三种:创建型, 行为型, 结构型. 策略设计模式属于行为型. 为什么属于行为型呢? 来看看下面解释:

1.1 什么是策略呢?

什么是策略呢?
举个例子: 出行方式: 可以骑自行车, 摩托车, 开小汽车, 坐公交车, 坐火车, 轮船, 飞机等等. 这些出行方式都是出行的策略.
再来看商场搞促销: 打8折, 打7折, 满100减30, 购物满500返现50等等, 无论何种打折方式, 其根本都是算法, 这些算法就是一种策略. 策略之间是可以随机互换的. 比如同一件商场,今天可以打8折, 明天可以满100件30.

策略设计模式: 定义【一组】算法, 将【每个】算法进行包装, 并且他们之间可以随意【互换】, 来看一下UML图:

<image src="https&#58;&#47;&#47;p1&#45;juejin&#46;byteimg&#46;com&#47;tos&#45;cn&#45;i&#45;k3u1fbpfcp&#47;969a22456c9a449aa93a52ab1a262f2f&#126;tplv&#45;k3u1fbpfcp&#45;zoom&#45;1&#46;image" width="500px">

从上图可以看出, 定义一个策略设计模式需要4大步骤

  1. 策略接口类: 是对策略, 算法的抽象. 定义了每个策略和算法必须有的算法和属性.
  2. 策略实现类: 策略,算法的具体实现. 策略具体有几种类型的实现就定义几个策略类,并实现策略方法
  3. Context上下文类: Context上下文, 起到承上启下的作用. 屏蔽了上层模块对策略,算法的访问, 封装了可能存在的变化.
    到底当前要调用那个策略, 通过定义构造函数传参决定, 策略方法可以没有入参, 但必须有构造方法, 构造方法决定类的实际策略. 可扩展性强, 增加新的策略, 不需要动用老代码.
  4. 客户端类: 客户端调用Context上下文类, 并指定要调用策略的类. 所以, 就要求用户提前知道有哪些策略类.

以上就是策略设计模式实现的4步骤

二. 策略设计模式代码实现

我们就以商场促销为例, 使用策略设计模式来实现.
现在商场要促销, 商品促销方式有: 原价, 折扣(8折, 7折), 满减(满100减30, 满100减20)等. 具体的促销方式就是策略.
根据实现的4个步骤, 一步一步来实现

2.1 第一步: 定义商场促销的接口类

package com.lxl.www.designPatterns.strategy;

/**
 * 促销类
 */
public interface IPromotionStrategy {

    /**
     * 促销
     *
     * 入参是原价, 出参是促销价格
     * @return
     */
    Double promotion(PromotionVo promotionVo) throws Exception;
}

这里定义了一个促销策略的接口类. 并定义了促销方案.

2.2 第二步: 促销方案的实现类

促销方案一共有3种: 第一种:原价, 第二种: 打折 第三种: 满减
先来看看第一种: 原价

package com.lxl.www.designPatterns.strategy;

/**
 * 正常售卖价格
 */
public class CommonPromotionStrategy implements IPromotionStrategy {

    @Override
    public Double promotion(PromotionVo promotionVo) {
        return promotionVo.getOriginPrice();
    }
}

原价也就是正常售卖的价格. 所以, 没有任何计算逻辑

第二种, 打折

package com.lxl.www.designPatterns.strategy;

/**
 * 打折
 */
public class DiscountPromotionStrategy implements IPromotionStrategy {

    @Override
    public Double promotion(PromotionVo promotionVo) throws Exception {
        if (promotionVo.getDiscount() < 0 || promotionVo.getDiscount() >=1 ){
            throw new Exception("请输入正确的折扣");
        }

        return promotionVo.getOriginPrice() * promotionVo.getDiscount();
    }
}

打折有折扣, 那么折扣必须是0-1范围, 并且最后返回折后价

第三种. 满减

package com.lxl.www.designPatterns.strategy;

/**
 * 满减折扣
 */
public class FullReductionPromotionStrategy implements IPromotionStrategy {
    /**
     * 满减
     * @return
     * @throws Exception
     */
    @Override
    public Double promotion(PromotionVo promotionVo) throws Exception {
        if (promotionVo.getReduction() == null || promotionVo.getFull() == null) {
            throw new Exception("请检查满减参数");
        }
        Double price = promotionVo.getOriginPrice();
        Double canPromotionPrice=promotionVo.getOriginPrice();
        while(canPromotionPrice >= promotionVo.getFull()) {
            price = price - promotionVo.getReduction();
            canPromotionPrice -= promotionVo.getFull();
        }
        return price;
    }
}

这里实现 满减逻辑是每满100减30, 要是300就减90.

2.3 第三步: context商场促销类

我们商场里的商品到底是什么样的优惠方案呢? 有商场促销类决定

package com.lxl.www.designPatterns.strategy;

import org.springframework.beans.factory.annotation.Autowired;

/**
 * 商场促销
 */
public class MallPromotion {
    @Autowired
    private IPromotionStrategy promotion;

    public MallPromotion(IPromotionStrategy promotion) {
        this.promotion = promotion;
    }

    public Double activityPromotion(PromotionVo promotionVo) throws Exception {
        return this.promotion.promotion(promotionVo);
    }
}

促销类定义了一个构造函数, 用来指定当前的促销方案.

2.4 具体商品的实际促销方案

    public static void main(String[] args) throws Exception {
        PromotionVo promotionVo = new PromotionVo(300.0, 0.8, 100.0, 30.0);

        System.out.println("<br><br>==========正常售卖=========");
        MallPromotion mallPromotionContext1 = new MallPromotion(new CommonPromotionStrategy());
        System.out.println(mallPromotionContext1.activityPromotion(promotionVo));


        System.out.println("<br><br>==========8折=========");
        MallPromotion mallPromotionContext2 = new MallPromotion(new DiscountPromotionStrategy());
        System.out.println(mallPromotionContext2.activityPromotion(promotionVo));


        System.out.println("<br><br>==========满100减30=========");
        MallPromotion mallPromotionContext3 = new MallPromotion(new FullReductionPromotionStrategy());
        System.out.println(mallPromotionContext3.activityPromotion(promotionVo));


    }
}

我们想要什么样 促销方案都是可以的. 只需要调用商场促销类并传入促销策略即可.

三. 策略设计模式的应用

3.1 什么时候使用?

一个系统有许多类,而他们之间的区别是: 行为的不同. 这时候可以使用策略设计模式.

3.2 优点

  1. 策略模式提供了对 “开闭原则” 的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
  2. 算法可以互相切换, 无需知道算法的具体实现
  3. 使用策略模式可以避免多重条件选择语句。多重条件选择语句是硬编码,不易维护。
  4. 可扩展性更好, 可以灵活地增加新的算法或行为。也可灵活切换算法或者行为

3.3 缺点

  1. 使用策略设计模式会产生很多具体策略类, 客户端每次只能使用一个策略类,不支持使用一个策略类完成部分功能后再使用另一个策略类来完成剩余功能的情况。
  2. 所有的策略类需要对外暴露. 因为客户端只有知道有哪些策略, 才知道应该是用哪个.这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。

4. 使用场景

1、一个系统, 需要动态的在几个算法之间选择, 它们之间的区别仅仅是算法或者行为的不同,那么可以使用策略模式, 这样我们可以动态地让一个对象在许多行为中选择一种行为。
2、一个系统需要动态地在几种算法中选择一种。
3、一个对象有很多的行为,如果不用策略设计模式,这些行为就只好使用多重条件选择语句来实现。而使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句。

5. 注意事项

如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。

四. 策略设计模式结合工厂设计模式的应用

上面的商场促销活动. 最终策略都要暴露给客户端, 这对客户端来说不是特别友好. 我们可以结合简单工厂设计模式, 将策略进行封装.

package com.lxl.www.designPatterns.strategy;

import org.springframework.beans.factory.annotation.Autowired;

/**
 * 商场促销
 */
public class MallPromotionContext {
    @Autowired
    private IPromotionStrategy promotion;

    public MallPromotionContext(IPromotionStrategy promotion) {
        this.promotion = promotion;
    }

    public MallPromotionContext(Integer type) {
        switch (type) {
            case 1:
                this.promotion = new CommonPromotionStrategy();
                break;
            case 2:
                this.promotion = new DiscountPromotionStrategy();
                break;
            case 3:
                this.promotion = new FullReductionPromotionStrategy();
                break;
        }
    }

    public Double activityPromotion(PromotionVo promotionVo) throws Exception {
        return this.promotion.promotion(promotionVo);
    }

}

这样封装以后的好处就是, 客户端只需要知道自己要什么类型的促销策略, 而不用关注具体促销策略类型.
下面来看一下策略代码的实现

    public static void main(String[] args) throws Exception {
        PromotionVo promotionVo = new PromotionVo(300.0, 0.8, 100.0, 30.0);

        System.out.println("<br><br>==========正常售卖=========");
        MallPromotionContext mallPromotionContext1 = new MallPromotionContext(1);
        System.out.println(mallPromotionContext1.activityPromotion(promotionVo));


        System.out.println("<br><br>==========8折=========");
        MallPromotionContext mallPromotionContext2 = new MallPromotionContext(2);
        System.out.println(mallPromotionContext2.activityPromotion(promotionVo));


        System.out.println("<br><br>==========满100减30=========");
        MallPromotionContext mallPromotionContext3 = new MallPromotionContext(3);
        System.out.println(mallPromotionContext3.activityPromotion(promotionVo));

    }

在基本的策略设计模式中, 选择所用具体实现的职责有客户端对象承担, 并转给策略模式的Context对象, 并没有减轻客户端需要选择判断的压力.而策略设计模式与简单工厂设计模式的结合, 选择具体实现的则在有也由Context来承担, 这就大大的减轻了客户端的职责.

</image>

更多模拟面试

全部评论

(3) 回帖
加载中...
话题 回帖

推荐话题

相关热帖

近期热帖

近期精华帖

热门推荐