Java怎么优雅的使用策略模式

本篇内容介绍了“Java怎么优雅的使用策略模式”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

    什么是策略模式

    策略模式是一种行为型模式,它将对象和行为分开,将行为定义为 一个行为接口 和 具体行为的实现。策略模式最大的特点是行为的变化,行为之间可以相互替换。每个if判断都可以理解为就是一个策略。本模式使得算法可独立于使用它的用户而变化。

    简单理解就是,针对不同的场景,使用不同的策略进行处理。

    策略模式结构

    Java怎么优雅的使用策略模式

    • Strategy 接口定义了一个算法族,它们都实现了 behavior() 方法。

    • Context 是使用到该算法族的类,其中的 doSomething() 方法会调用 behavior(),setStrategy(Strategy) 方法可以动态地改变 strategy 对象,也就是说能动态地改变 Context 所使用的算法。

    策略模式适用场景

    • 如果在一个系统里面有许多类,它们之间的区别仅在于它们 的行为,那么使用策略模式可以动态地让一个对象在许多行 为中选择一种行为。

    • 一个系统需要动态地在几种算法中选择一种。

    • 如果一个对象有很多的行为,如果不用恰当的模式,这些行 为就只好使用多重的条件选择语句来实现。

    • 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。

    生活中比较常见的应用模式有:

    • 电商网站支付方式,一般分为银联、微信、支付宝,可以采用策略模式。

    • 电商网站活动方式,一般分为满减送、限时折扣、包邮活动,拼团等可以采用策略模式。

    简单示例

    场景:最近太热了,想要降降温,有什么办法呢

    首先,定义一个降温策略的接口

    public interface CoolingStrategy {
    
        /**
         * 处理方式
         */
        void handle();
    
    }

    定义3种降温策略;实现策略接口

    public class IceCoolingStrategy implements CoolingStrategy {
        @Override
        public void handle() {
            System.out.println("使用冰块降温");
        }
    }
    public class FanCoolingStrategy implements CoolingStrategy {
    
        @Override
        public void handle() {
            System.out.println("使用风扇降温");
        }
    }
    public class AirConditionerCoolingStrategy implements CoolingStrategy {
        @Override
        public void handle() {
            System.out.println("使用空调降温");
        }
    }

    定义一个降温策略的上下文

    public class CoolingStrategyContext {
    
        private final CoolingStrategy strategy;
    
        public CoolingStrategyContext(CoolingStrategy strategy) {
            this.strategy = strategy;
        }
    
        public void coolingHandle() {
            strategy.handle();
        }
    
    }

    测试

    public class Main {
        public static void main(String[] args) {
            
            CoolingStrategyContext context = new CoolingStrategyContext(new FanCoolingStrategy());
            context.coolingHandle();
    
            context = new CoolingStrategyContext(new AirConditionerCoolingStrategy());
            context.coolingHandle();
    
            context = new CoolingStrategyContext(new IceCoolingStrategy());
            context.coolingHandle();
        }
    }

    运行结果:

    使用风扇降温 
    使用空调降温 
    使用冰块降温 

    以上就是一个策略模式的简单实现

    项目实战

    场景

    模拟在购买商品时候使用的各种类型优惠券(满减、直减、折扣、n元购)

    这个场景几乎也是大家的一个日常购物省钱渠道,购买商品的时候都希望找一些优惠券,让购买的商品更加实惠。而且到了大促的时候就会有更多的优惠券需要计算那些商品一起购买更加优惠!

    用一坨坨代码实现

    /**
     * 优惠券折扣计算接口
     * <p>
     * 优惠券类型;
     * 1. 直减券
     * 2. 满减券
     * 3. 折扣券
     * 4. n元购
     */
    public class CouponDiscountService {
    
        public double discountAmount(int type, double typeContent, double skuPrice, double typeExt) {
            // 1. 直减券
            if (1 == type) {
                return skuPrice - typeContent;
            }
            // 2. 满减券
            if (2 == type) {
                if (skuPrice < typeExt) return skuPrice;
                return skuPrice - typeContent;
            }
            // 3. 折扣券
            if (3 == type) {
                return skuPrice * typeContent;
            }
            // 4. n元购
            if (4 == type) {
                return typeContent;
            }
            return 0D;
        }
    
    }
    • 以上是不同类型的优惠券计算折扣后的实际金额。

    • 入参包括;优惠券类型、优惠券金额、商品金额,因为有些优惠券是满多少减少多少,所以增加了typeExt类型。这也是方法的不好扩展性问题。

    • 最后是整个的方法体中对优惠券抵扣金额的实现,最开始可能是一个最简单的优惠券,后面随着产品功能的增加,不断的扩展if语句。实际的代码可能要比这个多很多

    策略模式重构代码

    Java怎么优雅的使用策略模式

    • 整体的结构模式并不复杂,主要体现的不同类型的优惠券在计算优惠券方式的不同计算策略。

    • 这里包括一个接口类(ICouponDiscount)以及四种优惠券类型的实现方式。

    • 最后提供了策略模式的上下控制类处理,整体的策略服务。

    代码实现

    优惠券接口

    public interface ICouponDiscount<T> {
    
        /**
         * 优惠券金额计算
         * @param couponInfo 券折扣信息;直减、满减、折扣、N元购
         * @param skuPrice   sku金额
         * @return           优惠后金额
         */
        BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice);
    
    }
    • 定义了优惠券折扣接口,也增加了泛型用于不同类型的接口可以传递不同的类型参数。

    • 接口中包括商品金额以及出参返回最终折扣后的金额,这里在实际开发中会比现在的接口参数多一些,但核心逻辑是这些。

    优惠券接口实现

    满减

    public class MJCouponDiscount implements ICouponDiscount<Map<String,String>>  {
    
        /**
         * 满减计算
         * 1. 判断满足x元后-n元,否则不减
         * 2. 最低支付金额1元
         */
        public BigDecimal discountAmount(Map<String,String> couponInfo, BigDecimal skuPrice) {
            String x = couponInfo.get("x");
            String o = couponInfo.get("n");
    
            // 小于商品金额条件的,直接返回商品原价
            if (skuPrice.compareTo(new BigDecimal(x)) < 0) return skuPrice;
            // 减去优惠金额判断
            BigDecimal discountAmount = skuPrice.subtract(new BigDecimal(o));
            if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE;
    
            return discountAmount;
        }
    }

    直减

    public class ZJCouponDiscount implements ICouponDiscount<Double>  {
    
        /**
         * 直减计算
         * 1. 使用商品价格减去优惠价格
         * 2. 最低支付金额1元
         */
        public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
            BigDecimal discountAmount = skuPrice.subtract(new BigDecimal(couponInfo));
            if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE;
            return discountAmount;
        }
    
    }

    折扣

    public class ZKCouponDiscount implements ICouponDiscount<Double> {
    
    
        /**
         * 折扣计算
         * 1. 使用商品价格乘以折扣比例,为最后支付金额
         * 2. 保留两位小数
         * 3. 最低支付金额1元
         */
        public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
            BigDecimal discountAmount = skuPrice.multiply(new BigDecimal(couponInfo)).setScale(2, BigDecimal.ROUND_HALF_UP);
            if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE;
            return discountAmount;
        }
    
    }

    N元购

    public class NYGCouponDiscount implements ICouponDiscount<Double> {
    
        /**
         * n元购购买
         * 1. 无论原价多少钱都固定金额购买
         */
        public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
            return new BigDecimal(couponInfo);
        }
    
    }

    以上是四种不同类型的优惠券计算折扣金额的策略方式,可以从代码中看到每一种优惠方式的优惠金额。

    策略控制类

    public class Context<T> {
    
        private ICouponDiscount<T> couponDiscount;
    
        public Context(ICouponDiscount<T> couponDiscount) {
            this.couponDiscount = couponDiscount;
        }
    
        public BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice) {
            return couponDiscount.discountAmount(couponInfo, skuPrice);
        }
    
    }
    • 策略模式的控制类主要是外部可以传递不同的策略实现,在通过统一的方法执行优惠策略计算。

    • 另外这里也可以包装成map结构,让外部只需要对应的泛型类型即可使用相应的服务。

    测试类

    public class ApiTest {
    
        private Logger logger = LoggerFactory.getLogger(ApiTest.class);
    
        @Test
        public void test_zj() {
            // 直减;100-10,商品100元
            Context<Double> context = new Context<Double>(new ZJCouponDiscount());
            BigDecimal discountAmount = context.discountAmount(10D, new BigDecimal(100));
            logger.info("测试结果:直减优惠后金额 {}", discountAmount);
        }
    
        @Test
        public void test_mj() {
            // 满100减10,商品100元
            Context<Map<String,String>> context = new Context<Map<String,String>>(new MJCouponDiscount());
            Map<String,String> mapReq = new HashMap<String, String>();
            mapReq.put("x","100");
            mapReq.put("n","10");
            BigDecimal discountAmount = context.discountAmount(mapReq, new BigDecimal(100));
            logger.info("测试结果:满减优惠后金额 {}", discountAmount);
        }
    
    
        @Test
        public void test_zk() {
            // 折扣9折,商品100元
            Context<Double> context = new Context<Double>(new ZKCouponDiscount());
            BigDecimal discountAmount = context.discountAmount(0.9D, new BigDecimal(100));
            logger.info("测试结果:折扣9折后金额 {}", discountAmount);
        }
    
        @Test
        public void test_nyg() {
            // n元购;100-10,商品100元
            Context<Double> context = new Context<Double>(new NYGCouponDiscount());
            BigDecimal discountAmount = context.discountAmount(90D, new BigDecimal(100));
            logger.info("测试结果:n元购优惠后金额 {}", discountAmount);
        }
    
    }
    • 以上四组测试分别验证了不同类型优惠券的优惠策略,测试结果是满足我们的预期。

    • 这里四种优惠券最终都是在原价100元上折扣10元,最终支付90元。

    “Java怎么优雅的使用策略模式”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程之家网站,小编将为大家输出更多高质量的实用文章!

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

    相关推荐


    摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 连接 连接池产生原因 连接池实现原理 小结 TEMPERANCE:Eat not to dullness;drink not to elevation.节制
    摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 一个优秀的工程师和一个普通的工程师的区别,不是满天飞的架构图,他的功底体现在所写的每一行代码上。-- 毕玄 1. 命名风格 【书摘】类名用 UpperCamelC
    今天犯了个错:“接口变动,伤筋动骨,除非你确定只有你一个人在用”。哪怕只是throw了一个新的Exception。哈哈,这是我犯的错误。一、接口和抽象类类,即一个对象。先抽象类,就是抽象出类的基础部分,即抽象基类(抽象类)。官方定义让人费解,但是记忆方法是也不错的 —包含抽象方法的类叫做抽象类。接口
    Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket一、引子文件,作为常见的数据源。关于操作文件的字节流就是 —FileInputStream&amp;FileOutputStream。
    作者:泥沙砖瓦浆木匠网站:http://blog.csdn.net/jeffli1993个人签名:打算起手不凡写出鸿篇巨作的人,往往坚持不了完成第一章节。交流QQ群:【编程之美 365234583】http://qm.qq.com/cgi-bin/qm/qr?k=FhFAoaWwjP29_Aonqz
    本文目录 线程与多线程 线程的运行与创建 线程的状态 1 线程与多线程 线程是什么? 线程(Thread)是一个对象(Object)。用来干什么?Java 线程(也称 JVM 线程)是 Java 进程内允许多个同时进行的任务。该进程内并发的任务成为线程(Thread),一个进程里至少一个线程。 Ja
    Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket在面向对象编程中,编程人员应该在意“资源”。比如?1String hello = &quot;hello&quot;; 在代码中,我们
    摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第103篇原创 《程序兵法:Java String 源码的排序算法(一)》 文章工程:* JDK 1.8* 工程名:algorithm-core-le
    摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 一、父子类变量名相同会咋样? 有个小故事,今天群里面有个人问下面如图输出什么? 我回答:60。但这是错的,答案结果是 40 。我知错能改,然后说了下父子类变
    作者:泥瓦匠 出处:https://www.bysocket.com/2021-10-26/mac-create-files-from-the-root-directory.html Mac 操作系统挺适合开发者进行写代码,最近碰到了一个问题,问题是如何在 macOS 根目录创建文件夹。不同的 ma
    作者:李强强上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算。这一讲,泥瓦匠带你走进Java中的进制详解。一、引子在Java世界里,99%的工作都是处理这高层。那么二进制,字节码这些会在哪里用到呢?自问自答:在跨平台的时候,就凸显神功了。比如说文件读写,数据通信,还
    1 线程中断 1.1 什么是线程中断? 线程中断是线程的标志位属性。而不是真正终止线程,和线程的状态无关。线程中断过程表示一个运行中的线程,通过其他线程调用了该线程的 方法,使得该线程中断标志位属性改变。 深入思考下,线程中断不是去中断了线程,恰恰是用来通知该线程应该被中断了。具体是一个标志位属性,
    Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want需求 项目在设计表的时候,要处理并发多的一些数据,类似订单号不能重复,要保持唯一。原本以为来个时间戳,精确到毫秒应该不错了。后来觉得是错了,测试环境下很多一
    纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 加微信:bysocket01
    Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want.文章Points:1、介绍RESTful架构风格2、Spring配置CXF3、三层初设计,实现WebService接口层4、撰写HTTPClient 客户
    Writer :BYSocket(泥沙砖瓦浆木匠)什么是回调?今天傻傻地截了张图问了下,然后被陈大牛回答道“就一个回调…”。此时千万个草泥马飞奔而过(逃哈哈,看着源码,享受着这种回调在代码上的作用,真是美哉。不妨总结总结。一、什么是回调回调,回调。要先有调用,才有调用者和被调用者之间的回调。所以在百
    Writer :BYSocket(泥沙砖瓦浆木匠)一、什么大小端?大小端在计算机业界,Endian表示数据在存储器中的存放顺序。百度百科如下叙述之:大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加
    What is a programming language? Before introducing compilation and decompilation, let&#39;s briefly introduce the Programming Language. Programming la
    Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket泥瓦匠喜欢Java,文章总是扯扯Java。 I/O 基础,就是二进制,也就是Bit。一、Bit与二进制什么是Bit(位)呢?位是CPU
    Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocket一、前言 泥瓦匠最近被项目搞的天昏地暗。发现有些要给自己一些目标,关于技术的目标:专注很重要。专注Java 基础 + H5(学习) 其他操作系统,算法,数据结构当成课外书博览。有时候,就是那样你越是专注方面越