当前位置 博文首页 > 韩超的博客 (hanchao5272):Java常用设计模式的实例学习系列-面

    韩超的博客 (hanchao5272):Java常用设计模式的实例学习系列-面

    作者:[db:作者] 时间:2021-09-05 16:16

    超级链接: Java常用设计模式的实例学习系列-绪论

    参考:《HeadFirst设计模式》


    1.原始设计

    简述

    本文以购物车支付场景为例,对面向对象的六个原则进行理解。

    本文中的代码是逐步重构的,如果本步骤的代码与上步骤的代码相同,则不再展示。

    本文的主要目的是理解六个设计原则,所以对于需求是否合理和代码是否粗糙就请不要计较了。

    完整代码可以参考github:https://github.com/hanchao5272/design-pattern

    1.2.场景

    • 每个商品都有名称和价钱。
    • 购物车可以添加多个商品。
    • 购物车支付时使用支付宝支付。
    • 可能的需求变化:
      • 增加微信支付。
      • 商品的价格计算不仅仅有普通折扣,还有会员折上折。
      • 购物车不只当前这种,后面会出现更加先进的购物车。
      • … …

    1.3.原始代码

    直接上代码

    商品类:Goods

    /**
     * <p>商品</P>
     *
     * @author hanchao
     */
    #Setter
    #Getter
    #AllArgsConstructor
    public class Goods {
        /**
         * 商品名称
         */
        private String name;
    
        /**
         * 商品价格
         */
        private Float price;
    
        /**
         * 折扣
         */
        private Float discount;
    }
    

    购物车类:ShoppingCart

    /**
     * <p>购物车</P>
     *
     * @author hanchao
     */
    #Slf4j
    #NoArgsConstructor
    public class ShoppingCart {
        /**
         * 商品列表
         */
        private List<Goods> goodsList = new ArrayList<>();
    
        /**
         * 添加商品
         */
        public ShoppingCart addGoods(Goods goods) {
            goodsList.add(goods);
            return this;
        }
    
        /**
         * 显示商品
         */
        public ShoppingCart showGoods() {
            goodsList.forEach(goods -> log.info("购物车中有:{},价格:{}.", goods.getName(), goods.getPrice() * goods.getDiscount()));
            log.info("-------------------------------------");
            log.info("购物车中获取总价格:{}元\n", this.totalCost());
            return this;
        }
    
        /**
         * 计算总价
         */
        public float totalCost() {
            return goodsList.stream().map(goods -> goods.getPrice() * goods.getDiscount()).reduce(Float::sum).orElse(0f);
        }
    
        /**
         * 连接支付宝
         */
        public void connect() {
            log.info("开始进行支付宝支付:");
            log.info("1.初始化支付宝客户端...");
            log.info("2.设置请求参数...");
            log.info("3.请求支付宝进行付款,并获取支付结果...");
            log.info("-------------------------------------");
        }
    
        /**
         * 支付(日志显示2位小数)
         */
        public void pay() {
            float money = this.totalCost();
            connect();
            log.info("4.通过支付宝支付了" + FormatUtil.format(money) + "元.");
        }
    }
    

    **工具类:浮点型保留2位小数:FormatUtil **

    /**
     * <p>格式化工具类</P>
     *
     * @author hanchao
     */
    public class FormatUtil {
        /**
         * 浮点数显示两位小数
         */
        public static String format(float number) {
            return new DecimalFormat("0.00").format(Objects.isNull(number) ? 0 : number);
        }
    }
    

    测试代码

        public static void main(String[] args) {
            ShoppingCart shoppingCart = new ShoppingCart();
    
            shoppingCart.addGoods(new Goods("一双球鞋", 3500f, 1f))
                    .addGoods(new Goods("一件外套", 2800.00f, 0.80f))
                    .showGoods()
                    .pay();
        }
    

    测试结果:

    2019-07-18 14:19:03,784  INFO - 购物车中有:一双球鞋,价格:3500.0. 
    2019-07-18 14:19:03,786  INFO - 购物车中有:一件外套,价格:2240.0. 
    2019-07-18 14:19:03,786  INFO - ------------------------------------- 
    2019-07-18 14:19:03,793  INFO - 购物车中获取总价格:5740.0元
     
    2019-07-18 14:19:03,793  INFO - 开始进行支付宝支付: 
    2019-07-18 14:19:03,793  INFO - 1.初始化支付宝客户端... 
    2019-07-18 14:19:03,793  INFO - 2.设置请求参数... 
    2019-07-18 14:19:03,793  INFO - 3.请求支付宝进行付款,并获取支付结果... 
    2019-07-18 14:19:03,793  INFO - ------------------------------------- 
    2019-07-18 14:19:03,794  INFO - 4.通过支付宝支付了5740.00元. 
    

    ##2. 六个原则

    ###2.1. 原则一:单一职责原则

    单一职责原则:

    • 就一个类而言,应该仅有一个引起它变化的原因.
    • 简而言之:一个类应该是一组相关性很高的函数、数据的封装。

    问题

    回顾章节1.3.ShoppingCart类,发现它违背了单一职责原则:购物逻辑与支付逻辑放在了同一个类中。

    随着需求的不断发展,支付逻辑与购物逻辑都在不断增多,代码越来越复杂,这种设计毫无扩展性与灵活性。

    解决

    将支付宝相关逻辑抽离出来,放到单独的类中处理。

    支付类:AliPayClient

    /**
     * <p>支付宝类</P>
     *
     * @author hanchao
     */
    #Slf4j
    public class AliPayClient {
        /**
         * 连接支付宝
         */
        private void connect() {
            log.info("开始进行支付宝支付:");
            log.info("1.初始化支付宝客户端...");
            log.info("2.设置请求参数...");
            log.info("3.请求支付宝进行付款,并获取支付结果...");
            log.info("-------------------------------------");
        }
    
        /**
         * 支付(日志显示2位小数)
         */
        public void pay(float money) {
            connect();
            log.info("4.通过支付宝支付了" + FormatUtil.format(money) + "元.");
        }
    }
    

    购物车类:ShoppingCart

    /**
     * <p>购物车</P>
     *
     * @author hanchao
     */
    #Slf4j
    #NoArgsConstructor
    public class ShoppingCart {
      	//....
        
        /**
         * 通过支付宝支付
         */
        public void pay() {
            new AliPayClient().pay(totalCost());
        }
    }
    

    总结:

    经过上述优化,AliPayClient只负责支付相关逻辑,ShoppingCart只负责购物相关逻辑。当一方需求发生变化时,只需要修改一个类,不会影响另一个类。


    2.2.原则二:开闭原则

    开闭原则

    • 对扩展开发,对修复关闭。
    • 在面向对象设计中,不允许更改的是系统的抽象层,而允许扩展的是系统的实现层。
    • 换言之,定义一个尽可能不易发生变化的抽象设计层,允许尽可能多的行为在实现层被实现。
    • 解决问题关键在于抽象化
    • 这里的抽象指的是interface或者abstract class
    • 抽象的过程,实质上是在概括归纳总结它的本质。
    • 通过抽象,还能够统一规范实现类的需要实现的方法。
    • 同样是抽象,强调的是对类本身的处理。

    问题

    回顾章节2.2.的代码,发现它违背了开闭原则:支付客户端和购物车都是具体实现类。

    解决

    将支付客户端和购物车抽象化。

    支付宝接口:IAliPayClient

    /**
     * <p>支付宝的抽象类</P>
     *
     * @author hanchao
     */
    public interface IAliPayClient {
        /**
         * 连接
         */
        void connect();
    
        /**
         * 支付
         */
        void pay(Float money);
    }
    
    

    支付宝实现类:AliPayClient

    /**
     * <p>支付宝</P>
     *
     * @author hanchao
     */
    #Slf4j
    public class AliPayClient implements IAliPayClient {
        /**
         * 连接
         */
        @Override
        public void connect() {
            //...
        }
    
        /**
         * 支付
         */
        @Override
        public void pay(Float money) {
           //...
        }
    }
    

    购物车接口:IShoppingCart

    /**
     * <p>购物车的抽象类</P>
     *
     * @author hanchao
     */
    public interface IShoppingCart {
    
        /**
         * 添加商品
         */
        IShoppingCart addGoods(Goods goods);
    
        /**
         * 显示商品
         */
        IShoppingCart showGoods();
    
        /**
         * 计算总价
         */
        Float totalCost();
    
        /**
         * 通过支付宝支付
         */
        void pay();
    }
    

    购物车实现类:

    /**
     * <p>普通购物车</P>
     *
     * @author hanchao
     */
    #Slf4j
    #NoArgsConstructor
    public class ShoppingCart implements IShoppingCart {
      	//....
    
        /**
         * 添加商品
         */
        @Override
        public IShoppingCart addGoods(Goods goods) {
            //...
        }
    
        /**
         * 显示商品
         */
        @Override
        public IShoppingCart showGoods() {
            //...
        }
    
        /**
         * 计算总价
         */
        @Override
        public Float totalCost() {
            //...
        }
    
        /**
         * 通过支付宝支付
         */
        @Override
        public void pay() {
            IAliPayClient aliPayClient = new AliPayClient();
            aliPayClient.pay(totalCost());
        }
    }
    

    测试代码

        public static void main(String[] args) {
          	//注意这里是接口
            IShoppingCart shoppingCart = new ShoppingCart();
    
            shoppingCart.addGoods(new Goods("一双球鞋", 3500f, 1f)