这个设计模式的用法,一般人我不告诉他

这个设计模式的用法,一般人我不告诉他


我们知道,设计模式分为创建型、结构型、行为型三大类,今天要介绍的策略模式属于行为型设计模式,它是一种应用非常广泛的设计模式,在JDK源码、Spring框架中都可以经常看到它的身影,正确使用策略模式,可以大大提升代码的可扩展性。如果在代码中看到大量的复杂的if-else,可以试试用策略模式来进行改造和优化。
一、定义
我们先来看看策略模式的定义:

这个设计模式的用法,一般人我不告诉他

翻译过来大意就是:定义一组算法,将每个算法分别封装起来,并让它们可以相互替换。策略模式使算法的变化独立于使用它的客户端。
二、策略模式结构
策略模式主要由以下三个部分组成:
  • Strategy:抽象策略类,它定义了一些通用的、具体策略类必须实现的公共处理方法它通常是一个接口或者抽象类。
  • Concrete Strategy:具体策略类,它实现了抽象策略类定义的方法。
  • Context:上下文,它持有一个策略类的引用,委托策略变量来调用具体策略中的算法。

这个设计模式的用法,一般人我不告诉他

策略模式UML类图
三、案例
下面我们用一个较为常见但也颇为典型的案例来进一步说明策略模式:假设某个电商网站在搞打折促销活动,规则如下:
      1. 非会员不打折。

      2. 普通会员只要超过购买金额超过200元,就打九折优惠。

      3. 银牌会员统一打八折,如果购买金额超过300元,折后再立减20元(不累积计算)。

      4. 金牌会员统一打七折,如果购买金额超过300元,则直接按六折计算。

如果没使用策略模式,代码可能是这样的:
public long calculate(String userType, long amount){
    if("ordinary".equals(userType)){
        if(amount>200){
            return amount/100*90;
        }
        return amount;
    }
    if("silver".equals(userType)){
        if(amount>300){
            return amount/100*80-20;
        }
        return amount/100*80;
    }
    if("golden".equals(userType)){
        if(amount>300){
            return amount/100*60;
        }
        return amount/100*70;
    }
    return amount;
}
可以看出,这样的代码,可维护性和可扩展性都很不好,非常僵化,如果其中一种促销规则需要修改,或者需要新增一种促销规则,都需要重新修改代码,对现有的无需改变的规则也产生了影响。针对这种情况,我们可以使用策略模式来进行重构。为了讲解方便,我们只编写最关键的代码,对于异常情况、硬编码的法数字、数据类型的采用等方面没有过多考虑。
首先定义一个抽象的策略接口:
package com.sample.patterns.strategy;

public interface PromotionStrategy {
    // 计算促销后的金额
    long calculate(long amount);
}
再定义各种算法的具体实现类:
package com.sample.patterns.strategy;
// 非会员的计算策略
public class NonMemberStrategy implements PromotionStrategy{
    // 看起来有点多余,但主要是考虑:1.统一性;2.扩展性
    @Override
    public long calculate(long amount) {
        return amount;
    }
}
package com.sample.patterns.strategy;
// 普通会员的计算策略
public class OrdinaryMemberStrategy implements PromotionStrategy{
    // 购买金额超过200元,就打九折
    @Override
    public long calculate(long amount) {
        if(amount>200){
            return amount/100*90;
        }
        return amount;
    }
}
package com.sample.patterns.strategy;
// 银牌会员的计算策略
public class SilverMemberStrategy implements PromotionStrategy{
    // 八折,如果金额超过300元,再折后立减20元
    @Override
    public long calculate(long amount) {
        if(amount>300){
            return amount/100*80-20;
        }
        return amount/100*80;
    }
}
package com.sample.patterns.strategy;
// 金牌会员的计算策略
public class GoldenMemberStrategy implements PromotionStrategy{
    // 打七折,如果金额超过300元,则直接按六折计算
    @Override
    public long calculate(long amount) {
        if(amount>300){
            return amount/100*60;
        }
        return amount/100*70;
    }
}
按照我们的规则,有四种促销策略,所以相应的定义了四个策略类,在每个具体的策略类中实现对应的算法逻辑,由于它们都实现了同一个接口,所以它们是可以相互替换的。如果需要修改其中某个策略的计算方法,只要修改对应的类即可,对其它实现类没有任何影响,或者如果需要新增一个计算策略,我们直接新增一个类,实现计算策略接口就行了,同样对现有的计算逻辑也没有影响。这也体现了开-闭的设计原则,即对扩展开放,对修改关闭。
接下来定义上下文:
package com.sample.patterns.strategy;
// 促销策略的上下文
public class PromotionContext {
    // 促销策略接口
    private PromotionStrategy promotionStrategy;
    public PromotionContext(PromotionStrategy promotionContext){
        this.promotionStrategy = promotionContext;
    }
    // 计算促销后的金额
    public long calculate(long amount){
        return promotionStrategy.calculate(amount);
    }
}

再定义一个客户端来进行测试:
package com.sample.patterns.strategy;
// 客户端测试类
public class PromotionClient {
    public static void main(String[] args) {
        // 四种促销策略的上下文
        PromotionContext nonMemberContext = new PromotionContext(new NonMemberStrategy());
        PromotionContext ordinaryMemberContext = new PromotionContext(new OrdinaryMemberStrategy());
        PromotionContext silverMemberContext = new PromotionContext(new SilverMemberStrategy());
        PromotionContext goldenMemberContext = new PromotionContext(new GoldenMemberStrategy());
        // 以购买金额500为例,输出各种会员类型促销后的金额
        System.out.println(nonMemberContext.calculate(500));
        System.out.println(ordinaryMemberContext.calculate(500));
        System.out.println(silverMemberContext.calculate(500));
        System.out.println(goldenMemberContext.calculate(500));
    }
}
输出结果分别为:500,450,380,300。实现了运营部提出的打折需求,后续如果运营部门变更需求,我们也可以快速的进行修改而将影响面控制到最小,同时,代码清晰,耦合性较低,值得提倡。以上示例的UML图如下:

这个设计模式的用法,一般人我不告诉他

三、总结
上面我们以电商促销为例讲解了策略模式,但它不仅仅适用于数学计算这一类的场景,只要在不同的场景下有不同的处理,都可以考虑使用策略模式,比如不同的文件类型上传到不同的服务器,不同的食材有不同的加工方法等等。
当然,策略模式也不是完美无缺的,它既有优点,也有不足的地方,从上面的代码明显可以看出来,客户端必须要知道各个策略类之间的差异以便选择合适的那个进行调用。总结一下,策略模式的优点如下:
  • 它对扩展开放,对修改关闭,可以灵活新增或者修改算法实现逻辑。
  • 把算法的实现细节与使用算法的代码分隔开。
  • 使用策略模式可以避免使用多重条件转移语句。
它也有缺点:
  • 可能会产生大量的策略类,且功能单一、可复用性较低。
  • 客户端必须知道所有的策略类,并知道它们之间的差异以便选择合适的那个。
  • 增加了Context这个中间层,可能会带来设计上的复杂性和性能的一些损耗。

都看到这里了,请帮忙一键三连啊,也就是点击文末的分享、在看、点赞,这样会让我的文章让更多人看到,也会大大地激励我进行更多的输出,谢谢!

推荐阅读:

一网打尽:MySQL索引失效的场景大搜罗

《论语》是很多公司取名的源泉

原文始发于微信公众号(互联网全栈架构):这个设计模式的用法,一般人我不告诉他

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/173663.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!