策略模式与工厂模式实践(代码片段)

聪聪不匆匆 聪聪不匆匆     2022-12-05     298

关键词:

hello,大家好,我是聪聪。


文章目录

1.介绍

策略模式是一种行为设计模式, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。

日常开发中,对于需要考虑各类场景、各类分支通用逻辑时,就需要考虑是否可以将if-elseswitch 逻辑替换成不同策略算法进行单独处理,提高代码的可读性、可维护性,避免代码混乱熵增。

这里简单介绍一下lambda替换策略模式的方式:

  • 对于Collections#sort() 的排序方法,使用何种排序策略来自于 java.util.Comparator#compare() 中定义。
  • javax.servlet.http.HttpServlet#ser­vice­()方法, 还有所有接受 Http­Servlet­RequestHttp­Servlet­Response对象作为参数的 do­XXX()方法。根据HttpServletRequest.getMethod 获取请求方式(GET、POST、PUT …),用以路由处理各类请求策略。

如何识别是否是策略模式:

  • 通常策略模式可以通过允许嵌套对象完成实际工作的方法
  • 允许将该对象替换为不同对象的设置器来识别。

2.策略模式结构

2.1 分支逻辑解释

现在我们举个日常开发示例:

让我们设计一个算费系统,当中有根据不同计费策略计算得到应收手续费。

以下伪代码是最常的if-else switch 分支逻辑:

    private static BigDecimal calculate(BigDecimal amount, String pricingModel) 
        //单笔固定收费1元
        if ("fixed".equals(pricingModel)) 
            return new BigDecimal("1.00");
        
        //百分比收费 2%
        if ("percent".equals(pricingModel)) 
            return amount.multiply(new BigDecimal("0.02"));
        
        //固定值+百分比, 1+2%
        if ("fixedAndPercentage".equals(pricingModel)) 
            return new BigDecimal("1.00").add(amount.multiply(new BigDecimal("0.02")));
        
        throw new IllegalArgumentException("暂不支持计费模式:" + pricingModel);
    

    public static void main(String[] args) 
        BigDecimal amount = new BigDecimal("100");
        //计算单笔固定收费
        System.out.println(calculate(amount,"fixed"));
        //计算百分比收费
        System.out.println(calculate(amount,"percent"));
        //计算固定值+百分比收费
        System.out.println(calculate(amount,"fixedAndPercentage"));
    

上述逻辑已经根据计费模式pricingModel 来判断所属计算分支。看是仍然有以下弊端:

  • 新增其他计费模式时,就需要修改calculate 方法,新增对应分支逻辑,导致整体方法持续增长、每次修改就绪全量回归。
  • 无法灵活适应变化的场景;各计费模式对应策略修改时,无法及时变更;
    • 单笔固定收费从1元变更为5元需要及时修改逻辑,进行hard coding
    • 出现营销活动时百分比计费需要灵活变更时,从2%修改为1%时同样需要修改逻辑,无法适应快速迭代的营销活动。

下面将上述场景进行重构。

2.2 策略模式设计

首先看看策略模式应该是一个怎样的架构方式。

在这里我们需要做以下事情:

  • 根据上述伪代码中if-else 逻辑进行拆分,抽象出一个同样的计费策略接口:PricingStrategy
  • 一个执行上下文,用来创建特定计费策略对象,并且执行时将其引用替换为相关联的计费策略。Context
  • 根据现有不同计费模式,实现计费策略接口进行不同计费模式的计算实现。

那直接上代码看看。

2.3 策略模式代码

2.3.1 通用策略接口及其各策略实现。

//通用的计费策略接口
interface PricingStrategy 
    BigDecimal calculate(BigDecimal amount);


//固定计费模式
class FixedPricingStrategy implements PricingStrategy 
    @Override
    public BigDecimal calculate(BigDecimal amount) 
        return new BigDecimal("1.00");
    


//固定百分比计费
class PercentPricingStrategy implements PricingStrategy 
    @Override
    public BigDecimal calculate(BigDecimal amount) 
        if (amount == null) 
            return BigDecimal.ZERO;
        
        return amount.multiply(new BigDecimal("0.02"));
    


//固定值+百分比计费
class FixedAndPercentagePricingStrategy implements PricingStrategy 
    @Override
    public BigDecimal calculate(BigDecimal amount) 
        return new BigDecimal("1.00").add(amount.multiply(new BigDecimal("0.02")));

代码解释:

  • 一个通用策略接口 PricingStrategy
  • 各种策略算法实现方式:
    • 固定值计费:FixedPricingStrategy
    • 百分比计费:PercentPricingStrategy
    • 固定值+百分比计费:FixedAndPercentagePricingStrategy

2.3.2 执行上下文信息

//执行上下文:
class Context 
    public Context() 

    

    //上下文会维护指向某个策略对象的引用
    private PricingStrategy pricingStrategy;

    // 上下文通常通过构造方法来设置策略
    public Context(PricingStrategy pricingStrategy) 
        this.pricingStrategy = pricingStrategy;
    

    //也可以通过设置方法来切换策略
    public void setPricingStrategy(PricingStrategy pricingStrategy) 
        this.pricingStrategy = pricingStrategy;
    

    // 上下文会将一些工作委派给策略对象,而不是自行实现不同版本的算法。
    public BigDecimal executeStrategy(BigDecimal amount) 
        return pricingStrategy.calculate(amount);
    

代码解释:

  • 空构造器用以初始化上下文
  • 策略参数构造器用以初始化策略接口对象
  • 维护一个策略接口对象,用以后续指向具体策略算法实现
  • 策略对象委派执行到真实实现方法。

2.3.3 客户端调用

public class ClientMain 

    @Test
    void testConstructorCreation() 
        //构造器方式指定策略
        Context context = new Context(new FixedPricingStrategy());
        BigDecimal fixedResult = context.executeStrategy(new BigDecimal("100"));
        Assertions.assertEquals(fixedResult, new BigDecimal("1"));

        //切换策略
        context.setPricingStrategy(new PercentPricingStrategy());
        BigDecimal percentResult = context.executeStrategy(new BigDecimal("100"));
        Assertions.assertEquals(percentResult, new BigDecimal("2"));
    

    @Test
    void testSetMethodCreation() 
        //set设置方法设置策略和切换
        Context context = new Context();

        //设置固定计费策略
        context.setPricingStrategy(new FixedPricingStrategy());
        BigDecimal fixedResult = context.executeStrategy(new BigDecimal("100"));
        Assertions.assertEquals(fixedResult, new BigDecimal("1.00"));

        //设置百分比计费策略
        context.setPricingStrategy(new PercentPricingStrategy());
        BigDecimal percentResult = context.executeStrategy(new BigDecimal("100"));
        Assertions.assertEquals(percentResult, new BigDecimal("2.00"));

        //设置固定值+百分比计费策略
        context.setPricingStrategy(new FixedAndPercentagePricingStrategy());
        BigDecimal fixedAndPercentResult = context.executeStrategy(new BigDecimal("100"));
        Assertions.assertEquals(fixedAndPercentResult, new BigDecimal("3.00"));
    

    @ParameterizedTest
    @MethodSource(value = "allStrategy")
    void testRouteStrategy(String strategy) 
        Context context = new Context();
        if ("fixed".equals(strategy)) 
            context.setPricingStrategy(new FixedPricingStrategy());
        
        if ("percent".equals(strategy)) 
            context.setPricingStrategy(new PercentPricingStrategy());
        
        if ("fixedAndPercentage".equals(strategy)) 
            context.setPricingStrategy(new FixedAndPercentagePricingStrategy());
        

        BigDecimal result = context.executeStrategy(new BigDecimal("100"));
        //打印结果
    

    static Object[] allStrategy() 
        return new Object[]"fixed", "percent", "fixedAndPercentage";
    

上述测试方法很清晰:

  • testConstructorCreation 通过构造器方式生成上下文,设定具体策略实现,同样后续可以通过set方法进行切换策略算法。
  • testSetMethodCreation 初始化空构造器方式生成上下文,通过set方法进行切换设置策略算法。
  • routeStrategy 初始化空构造器方式生成上下文,通过外部所传策略方法,进行路由具体策略算法实现。

2.3.4 总结

上述策略模式重构相比于if-else switch 等分支逻辑已经结构化,相互解耦,减少维护成本。总结有以下优点:

  • 策略算法可以自由切换,相互之间解耦。
  • 上下文可在运行时进行切换对象内的策略算法。
  • 扩展性好,新增其他策略算法时,实现通用算法策略接口即可。
  • 开闭原则,无需对上下文进行修改就能引入新的策略。

有有点,当然也有缺点:

  • 策略算法较少时,没必要引入新的类、接口,增加程序复杂度。

  • 客户端必须知道所有的策略实现类,才能够通过上下文进行设置选择合适的策略。

那么,我们可以通过工厂模式+策略模式进行结构化、抽象此类策略算法的结构。在扩展中进行演示。

3.常用示例

上述策略模式的重构仍然可以有优化的余地:

  • 各类策略可以定义枚举进行设定。
  • 通过工厂模式,客户端无需知道所有策略实现。

那我们直接上代码:

3.1 定义一个策略枚举

public enum StrategyEnum 
    FIXED,
    PERCENT,
    FIXED_AND_PERCENTAGE;

    StrategyEnum() 
    

在该枚举中定义了所有策略类型,后续新增策略算法是进行扩展该枚举即可。

3.2 策略接口增加serviceCode

public interface PricingStrategy 
    BigDecimal calculate(BigDecimal amount);

    //每次一个策略接口实现均会实现该接口 用以标记策略实现的具体类型
    String getServiceCode();

修改策略接口,增加getServiceCode() 方法。

3.3 策略接口实现

//固定计费模式
public class FixedPricingStrategy implements PricingStrategy 
    @Override
    public BigDecimal calculate(BigDecimal amount) 
        return new BigDecimal("1.00");
    

    @Override
    public String getServiceCode() 
        return StrategyEnum.FIXED.name();
    



//固定百分比计费
class PercentPricingStrategy implements PricingStrategy 
    @Override
    public BigDecimal calculate(BigDecimal amount) 
        if (amount == null) 
            return BigDecimal.ZERO;
        
        return amount.multiply(new BigDecimal("0.02"));
    

    @Override
    public String getServiceCode() 
        return StrategyEnum.PERCENT.name();
    


策略实现类中均实现了getServiceCode() 接口,用以返回该策略实现类所属类型或服务编码,后续可以通过工厂模式进行生产获取该类型策略。

3.4 策略工厂

@Slf4j
public class PricingStrategyFactory 

    public PricingStrategyFactory(List<PricingStrategy> list) 
        init(list);
    

    private final Map<String, PricingStrategy> pricingStrategyMap = new HashMap<>();

    private void init(List<PricingStrategy> strategyList) 
        if (strategyList == null) 
            return;
        
        for (PricingStrategy strategy : strategyList) 
            String serviceCode = strategy.getServiceCode();
            if (serviceCode == null) 
                throw new IllegalArgumentException(String.format("Registration service code cannot be empty:%s",
                        strategy.getClass()));
            
            if (!pricingStrategyMap.containsKey(serviceCode)) 
                pricingStrategyMap.put(serviceCode, strategy);
                log.info("Registration service:  , ", serviceCode, strategy.getClass());
             else 
                throw new IllegalArgumentException(String.format("Duplicate registration service: %s , %s , %s",
                        serviceCode, pricingStrategyMap.get(serviceCode).getClass(), strategy.getClass()));
            
        
    

    public PricingStrategy getService(String serviceCode) 
        return pricingStrategyMap.get(serviceCode);
    

这里我们将所有计费策略实现全部存储在一个Map中进行初始化。

通过策略接口中getServiceCode() 作为key进行存储,后续通过getService(String serviceCode) 即可获得对应的策略接口实现。

3.5 使用方式

public class TestStrategyFactory 

    private PricingStrategyFactory pricingStrategyFactory;

    @BeforeEach
    void init() 
        List<PricingStrategy> strategyList = initPricingStrategy();
        pricingStrategyFactory = new PricingStrategyFactory(strategyList);
    

    @Test
    void testFixedCalculate() 
        PricingStrategy strategy = pricingStrategyFactory.getService(StrategyEnum.FIXED.name());
        BigDecimal fixedResult = strategy.calculate(new BigDecimal("100"));
        Assertions.assertEquals(fixedResult, new BigDecimal("1.00"));
    

    @Test
    void testPercentCalculate() 
        PricingStrategy strategy = pricingStrategyFactory.getService(StrategyEnum.PERCENT.name());
        BigDecimal percentResult = strategy.calculate(new BigDecimal("100"));
        Assertions.assertEquals(percentResult, new BigDecimal("2.00"));
    

    // 这里初始化所有PricingStrategy 接口的所有实现。
    // 如果你是通过spring管理,直接通过@Autowrited即可注入得到List<PricingStrategy>
    static List<PricingStrategy> initPricingStrategy() 
        return Arrays.asList(new FixedPricingStrategy(), new PercentPricingStrategy());
    

上面使用方式就不过多解释了。注意初始化PricingStrategyFactory 的方式,此处只有一个构造器,需要注入策略接口的所有实现。

如果你是用的是spring进行管理,那么直接可以通过@Autowired 的方式即可将所有接口实现进入注入,得到一个List<PricingStrategy>

下面看看执行启动日志,可以很清晰的看到目前应用已注册多种策略:

Registration service: FIXED , class cc.ccoder.designpatterns.strategy.refactor.FixedPricingStrategy
Registration service: PERCENT , class cc.ccoder.designpatterns.strategy.refactor.PercentPricingStrategy

当然如果你有多个平行的策略时,都需要这样创建一个工厂,岂不是重复的逻辑又增加了。

是否有一种优雅的方式进行工厂方法复用呢?

上述将策略接口实现类放入Map中、从Map中通过serviceCode获取对应策略接口实现的逻辑应该是一致的,我们便可将其进行抽象出来。

4. 策略模式和工厂模式组合

  • 定义一个高度抽象的接口,提供serviceCode方法即可。
  • 定义一个策略接口的工厂接口,提供对应的策略方法。
  • 定义一个抽象的工厂方法,用以将策略接口方法放入Map,通过serviceCode获取对应策略接口。

以上便是我们的需求,那么接下来就开始编码看看。

4.1 一个提供serviceCode方法的接口

public interface CodeService 

    /**
     * 服务编码必须唯一
     *
     * @return 服务编码
     */
    String getServiceCode();

这是一个顶层接口,后续所有策略工厂模式接口均需要实现该接口,根据策略枚举内容进行返回。

4.2 策略接口的工厂

public interface CodeServiceFactory<Provider extends CodeService> 

    /**
     * 获取服务
     *
     * @param serviceCode 服务编码
     * @return 返回服务,不存在时返回null
     */
    Provider getService(String serviceCode);

这里限定了后续所有的策略提供者Provider 均需要继承于CodeService 接口。

4.3 抽象的工厂方法

public abstract class AbstractCodeServiceFactory<Provider extends CodeService>
  implements CodeServiceFactory<Provider> 

    private static final Logger log = LoggerFactory.getLogger(AbstractCodeServiceFactory.class);

    private final Map<String, Provider> serviceProviderMap = new HashMap<>();

    protected AbstractCodeServiceFactory(List<Provider> providers) 
        initializeProviderMap(providers);
    

    /**
     * Initialize Factory Service
     *
     * @param providers 服务接口
     */
    策略模式与工厂模式实践(代码片段)

hello,大家好,我是聪聪。文章目录1.介绍2.策略模式结构2.1分支逻辑解释2.2策略模式设计2.3策略模式代码2.3.1通用策略接口及其各策略实现。2.3.2执行上下文信息2.3.3客户端调用2.3.4总结3.常用示例3.1定义一个策略枚举3.2策... 查看详情

策略模式与工厂模式实践(代码片段)

hello,大家好,我是聪聪。文章目录1.介绍2.策略模式结构2.1分支逻辑解释2.2策略模式设计2.3策略模式代码2.3.1通用策略接口及其各策略实现。2.3.2执行上下文信息2.3.3客户端调用2.3.4总结3.常用示例3.1定义一个策略枚举3.2策... 查看详情

策略模式与简单工厂模式(代码片段)

...,工厂模式调用方可以直接调用工厂实例的方法属性等。策略模式是将生成实例的使用策略放在策略类中配置后才提供调用方使用,策略模式不能直接调用实例的方法属性,需要在策略类中封装策略后调用。 事列代码:usingSys... 查看详情

|啥是工厂模式和策略模式?(代码片段)

   简单工厂模式与策略模式  前言设计模式(DesignPattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代... 查看详情

工厂模式策略者模式责任链模式综合应用(代码片段)

设计模式的具体运用:  简单工厂模式、策略者模式、责任链模式定义与使用classLoader的具体运用  自定义的classloader来动态加载类程序功能设计:  在商城购物时,商城可能会在特殊的日子、或者依据会员等级,对结算... 查看详情

代码片-策略模式+工厂模式(代码片段)

通过策略类实现不同场景的策略处理,通过工厂模式创建不同的策略对象1.策略实现接口、策略实现类1.1策略接口/***策略接口*/publicinterfaceIWarnRulepublicvoidwarn();1.2策略实现类/***防拆告警策略实现类*/publicclassAntiRemovalWarnimplements... 查看详情

代码片-策略模式+工厂模式(代码片段)

通过策略类实现不同场景的策略处理,通过工厂模式创建不同的策略对象1.策略实现接口、策略实现类1.1策略接口/***策略接口*/publicinterfaceIWarnRulepublicvoidwarn();1.2策略实现类/***防拆告警策略实现类*/publicclassAntiRemovalWarnimplements... 查看详情

代码片-策略模式+工厂模式(代码片段)

通过策略类实现不同场景的策略处理,通过工厂模式创建不同的策略对象1.策略实现接口、策略实现类1.1策略接口/***策略接口*/publicinterfaceIWarnRulepublicvoidwarn();1.2策略实现类/***防拆告警策略实现类*/publicclassAntiRemovalWarnimplements... 查看详情

策略模式(strategypattern)(代码片段)

策略模式(StrategyPattern)概念定义算法家族,分别封装起来,让它们之间可以互相替换,让算法变化,不会影响到用户。模式结构模式实例与解析这里的例子还是上文简单工厂模式中的计算器加减乘除的设计。与原先的简单工厂... 查看详情

springboot中使用策略模式+工厂模式(代码片段)

策略模式和工厂模式相信大家都比较熟悉,但是大家有没有在springboot中实现策略和工厂模式?  具体策略模式和工厂模式的UML我就不给出来了,使用这个这两个模式主要是防止程序中出现大量的IFELSEIFELSE....。接下来咱... 查看详情

设计模式之简单工厂模式与策略模式(通过两种模式设计的计算器/java)(代码片段)

...小项目来练习使用设计模式。本次选择了简单工厂模式、策略模式以及两个模式混合使用来完成计 查看详情

设计模式之简单工厂模式与策略模式(通过两种模式设计的计算器/java)(代码片段)

...小项目来练习使用设计模式。本次选择了简单工厂模式、策略模式以及两个模式混合使用来完成计 查看详情

工程实践之路:c++接口设计中的工厂模型(代码片段)

工程实践之路:C++接口设计中的工厂模型设计模式之工厂模式为什么使用工厂模式1.工厂设计模式是为了将对象的创建与使用进行分离2.其他好处简单工厂模式工厂方法模式抽象工厂模式参考资料上一篇文章写了《工程... 查看详情

代码片-策略模式+工厂模式(代码片段)

通过策略类实现不同场景的策略处理,通过工厂模式创建不同的策略对象1.策略实现接口、策略实现类1.1策略接口/***策略接口*/publicinterfaceIWarnRulepublicvoidwarn();1.2策略实现类/***防拆告警策略实现类*/publicclassAntiRemovalWarnimplements... 查看详情

设计模式——策略模式(代码片段)

策略模式以解决商场收银员业务为背景。什么是策略模式?策略模式:它定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式可以让算法的变化不会影响带到适应算法的客户。策略模式的使用。在什么时候使用策... 查看详情

策略模式和简单工厂模式的结合使用(代码片段)

1//策略类,定义所有支持的算法的公共接口2publicabstractclassStrategy3publicabstractvoidalgorithmMethod(intnumberA,intnumberB);4 1//具体策略类,封装了具体的算法或行为,继承于Strategy2//用于加法的算法3publicclassConcreteStrategyAddextendsStra 查看详情

编程学习之简单工厂模式与策略模式

很久之前在学习c++的时候就听到老师说写代码要考虑重构,架构,在此期间学习到了一种简单工厂模式。何为简单工厂模式呢?简单工厂模式又叫静态工厂方法模式(StaticFactoryMethodPattern),是通过专门定义一个类来负责创... 查看详情

设计模式实践笔记第二节:抽象工厂模式(代码片段)

抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。抽象工厂模式(AbstractFactory)也是一种创建型设计模式,通常用来解决「产品族」的创建问题。抽象工厂模式结构工厂模... 查看详情