当前位置: 首页 > article >正文

设计模式-策略模式-200

优点:用来消除 if-else、switch 等多重判断的代码,消除 if-else、switch 多重判断 可以有效应对代码的复杂性。
缺点:会增加类的数量,有的时候没必要为了消除几个if-else而增加很多类,尤其是那些类型又长又臭的
原始代码:

class Example {
    public static void main(String[] args) {
        discount("1",20D);
    }
    public static Double discount(String type, Double price) {
        // 策略模式
        if (Objects.equals(type, "1")) {
            return price * 0.95;
        }
        else if (Objects.equals(type, "2")){
            return price * 0.9;
        }
        return price;
    }
}

存在2个弊端:
1.if-else过多
2.不符合开闭原则。
开闭原则是对于扩展是开放的,但是对于修改是封闭的,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。
策略模式优化大概是三个步骤:

  1. 定义抽象策略接口,因为业务使用接口而不是具体的实现类的话,便可以灵活的替换不同的策略;
  2. 定义具体策略实现类,实现自抽象策略接口,其内部封装具体的业务实现;
  3. 定义策略工厂,封装创建策略实现(算法),对客户端屏蔽具体的创建细节
    优化方法1:

interface DiscountStrategy {
    Double discount(Double price);
}
class DiscountStrategy1 implements DiscountStrategy{
    @Override
    public Double discount(Double price) {
        return price * 0.95;
    }
}

class DiscountStrategy2 implements DiscountStrategy{
    @Override
    public Double discount(Double price) {
        return price * 0.9;
    }
}
class DiscountStrategyFactory {
    private static final Map<String, DiscountStrategy> STRATEGY_MAP = new HashMap<>();
    static {
        STRATEGY_MAP.put("1", new DiscountStrategy1());
        STRATEGY_MAP.put("2", new DiscountStrategy2());
    }
    public static DiscountStrategy chooseStrategy(String type) {
        return STRATEGY_MAP.get(type);
    }
    public static void main(String[] args) {
        DiscountStrategy discountStrategy = chooseStrategy("1");
        System.out.println("Discount Price = "+discountStrategy.discount(10D));
    }
}

运行结果:
在这里插入图片描述
如果新增一个优惠策略,只需要新增一个策略算法实现类即可。但是,添加一个策略算法实现,意味着需要改动策略工厂中的代码,还是不符合开闭原则

如何完整实现符合开闭原则的策略模式,需要借助 Spring 的帮助,详细案例请继续往下看。

策略模式结合Spring

可以看到,比对上面的示例代码,有两处明显的变化:

  1. 抽象策略接口中,新定义了 mark() 接口,此接口用来标示算法的唯一性;
  2. 具体策略实现类,使用 @Component 修饰,将对象本身交由 Spring 进行管理
@Component
interface DiscountStrategy {
    Double discount(Double price);
    String mark();
}
@Component
class DiscountStrategy1 implements DiscountStrategy{
    @Override
    public Double discount(Double price) {
        return price * 0.95;
    }
    @Override
    public String mark() {
        return "1";
    }
}
@Component
class DiscountStrategy2 implements DiscountStrategy{
    @Override
    public Double discount(Double price) {
        return price * 0.9;
    }
    @Override
    public String mark() {
        return "2";
    }
}
@Component
class DiscountStrategyFactory implements InitializingBean {
    @Autowired
    private ApplicationContext applicationContext;

    private static final Map<String, DiscountStrategy> STRATEGY_MAP = new HashMap<>();

    public  DiscountStrategy chooseStrategy(String type) {
        return STRATEGY_MAP.get(type);
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        Map<String, DiscountStrategy> beansOfType = applicationContext.getBeansOfType(DiscountStrategy.class);

//        v.mark()为策略对应的key,value为策略对象
        beansOfType.forEach((k, v) -> STRATEGY_MAP.put(v.mark(), v));
    }


}
@SpringBootTest
class Main {
    @Autowired DiscountStrategyFactory discountStrategyFactory;
    @Test
    void contextLoads() {
        DiscountStrategy discountStrategy = discountStrategyFactory.chooseStrategy("1");
        System.out.println("Discount Price = "+ discountStrategy.discount(10D));
    }
}

通过 InitializingBean 接口实现中调用 IOC 容器查找对应策略实现,随后将策略实现 mark() 方法返回值作为 key, 策略实现本身作为 value 添加到 Map 容器中等待客户端的调用。就是把Interface的所有实现类都弄到Map里面了,通过forEach方法!(前提是实现类有@Component,没有的话是注册不上的!!)
这里使用的 SpringBoot 测试类,注入策略工厂 Bean,通过策略工厂选择出具体的策略算法类,继而通过算法获取到优惠后的价格。小插曲:如果不想把策略工厂注入 Spring 也可以,实现方法有很多。

总结下本小节,我们通过和 Spring 结合的方式,通过策略设计模式对文初的代码块进行了两块优化:应对代码的复杂性,让其满足开闭原则。更具体一些呢就是 通过抽象策略算法类减少代码的复杂性,继而通过 Spring 的一些特性同时满足了开闭原则,现在来了新需求只要添加新的策略类即可,健壮易扩展。

策略模式的本质依然是对代码设计解耦合,通过三类角色贯穿整个策略模式:抽象策略接口策略工厂以及具体的策略实现类。通过细粒度的策略实现类避免了主体代码量过多,减少了设计中的复杂性

出现 if-else 的代码,一定要使用策略模式优化么?
如果 if-else 判断分支不多并且是固定的,后续不会出现新的分支,那我们完全 可以通过抽函数的方式降低程序复杂性;不要想法设法去除 if-else 语句,存在即合理。而且,使用策略模式会导致类增多,没有必要为了少量的判断分支引入策略模式。

扩展

当业务使用越来越多的情况下,重复定义 DiscountStrategy 以及 DiscountStrategyFactory 会增加系统冗余代码量。
可以考虑将这两个基础类抽象出来,作为基础组件库中的通用组件,供所有系统下的业务使用,从而避免代码冗余。
定义抽象策略处理接口,添加有返回值和无返回值接口。

package org.opengoofy.congomall.springboot.starter.designpattern.strategy;

/**
 * 策略执行抽象
 */
public interface AbstractExecuteStrategy<REQUEST, RESPONSE> {

    /**
     * 执行策略标识
     */
    String mark();

    /**
     * 执行策略
     *
     * @param requestParam 执行策略入参
     */
    default void execute(REQUEST requestParam) {

    }

    /**
     * 执行策略,带返回值
     *
     * @param requestParam 执行策略入参
     * @return 执行策略后返回值
     */
    default RESPONSE executeResp(REQUEST requestParam) {
        return null;
    }
}

添加策略选择器,通过订阅 Spring 初始化事件执行扫描所有策略模式接口执行器,并根据 mark 方法定义标识添加到 abstractExecuteStrategyMap 容器中。
客户端在实际业务中使用 AbstractStrategyChoose#choose 即可完成策略模式实现。

package org.opengoofy.congomall.springboot.starter.designpattern.strategy;

import org.opengoofy.congomall.springboot.starter.base.ApplicationContextHolder;
import org.opengoofy.congomall.springboot.starter.base.init.ApplicationInitializingEvent;
import org.opengoofy.congomall.springboot.starter.convention.exception.ServiceException;
import org.springframework.context.ApplicationListener;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * 策略选择器
 */
public class AbstractStrategyChoose implements ApplicationListener<ApplicationInitializingEvent> {

    /**
     * 执行策略集合
     */
    private final Map<String, AbstractExecuteStrategy> abstractExecuteStrategyMap = new HashMap<>();

    /**
     * 根据 mark 查询具体策略
     *
     * @param mark 策略标识
     * @return 实际执行策略
     */
    public AbstractExecuteStrategy choose(String mark) {
        return Optional.ofNullable(abstractExecuteStrategyMap.get(mark)).orElseThrow(() -> new ServiceException(String.format("[%s] 策略未定义", mark)));
    }

    /**
     * 根据 mark 查询具体策略并执行
     *
     * @param mark         策略标识
     * @param requestParam 执行策略入参
     * @param <REQUEST>    执行策略入参范型
     */
    public <REQUEST> void chooseAndExecute(String mark, REQUEST requestParam) {
        AbstractExecuteStrategy executeStrategy = choose(mark);
        executeStrategy.execute(requestParam);
    }

    /**
     * 根据 mark 查询具体策略并执行,带返回结果
     *
     * @param mark         策略标识
     * @param requestParam 执行策略入参
     * @param <REQUEST>    执行策略入参范型
     * @param <RESPONSE>   执行策略出参范型
     * @return
     */
    public <REQUEST, RESPONSE> RESPONSE chooseAndExecuteResp(String mark, REQUEST requestParam) {
        AbstractExecuteStrategy executeStrategy = choose(mark);
        return (RESPONSE) executeStrategy.executeResp(requestParam);
    }

    @Override
    public void onApplicationEvent(ApplicationInitializingEvent event) {
        Map<String, AbstractExecuteStrategy> actual = ApplicationContextHolder.getBeansOfType(AbstractExecuteStrategy.class);
        actual.forEach((beanName, bean) -> {
            AbstractExecuteStrategy beanExist = abstractExecuteStrategyMap.get(bean.mark());
            if (beanExist != null) {
                throw new ServiceException(String.format("[%s] Duplicate execution policy", bean.mark()));
            }
            abstractExecuteStrategyMap.put(bean.mark(), bean);
        });
    }
}

这两个实现已经被放置在基础组件库中,如果业务需要使用策略模式,则无需重新定义。


http://www.kler.cn/a/327564.html

相关文章:

  • 机器学习3
  • 网页作业9
  • Cherno OpenGL(28 ~ 33)
  • Docker: ubuntu系统下Docker的安装
  • Java连接MySQL(测试build path功能)
  • 【Pytorch】IPython库中的display函数
  • python全栈学习记录(二十)类的属性传递与绑定方法
  • Leetcode 3303. Find the Occurrence of First Almost Equal Substring
  • 【分布式微服务云原生】 RPC协议:超越HTTP的远程通信艺术
  • 基于Springboot+Vue的c语言学习辅导网站的设计与实现 (含源码数据库)
  • 中间件:SpringBoot集成Redis
  • 【Python|接口自动化测试】使用requests库发送HTTP请求
  • Django连接Azure服务器里的gpt-4o并实现聊天功能
  • PHP程序如何实现限制一台电脑登录?
  • maven parent: 指定了项目的父 POM packaging: 指定打包类型为 POM。 modules: 列出了该项目包含的子模块,
  • 【开源免费】基于SpringBoot+Vue.JS校园资料分享平台(JAVA毕业设计)
  • opus基础简介(github)
  • 使用rsync+jenkins实现服务自动部署全流程
  • React 生命周期 - useEffect 介绍
  • WebGIS包括哪些技术栈?怎么学习?
  • 足球青训俱乐部后台:Spring Boot开发策略
  • 滚雪球学MySQL[11.1讲]:总结与展望
  • Spring Boot 点餐系统:简化您的订餐流程
  • 一个服务器可以搭建几个网站
  • vue结合element-ui实现列表拖拽变化位置,点击拖动图标拖动整个列表元素,使用tsx格式编写
  • SpringBootTest Mockito 虚实结合编写测试