程序员过关斩将--论商品促销代码的优雅性

图片描述

背景介绍

据我所知,几乎全部的互联网公司都带有和电商有关的项目,并且在大多数公司里面仍是举足轻重的重头戏,好比京东,淘宝。既然有电商项目,必然会涉及到商品,一旦有商品就会有各类促销活动,好比 满100减20,三八妇女节9折等等相似活动。做为一个coder怎么才能在实现产品狗的需求下,最小改动代码,最优雅的实现呢。今天菜菜不才,就D妹子的问题献丑一番。如下以.netCore c#代码为例,其余语言相似。c#

D妹子版本

首先D妹子有一个商品的对象,商品里有一个价格的属性,价格的单位是分ide

class Product
    {
        //其余属性省略
        public int Price { get; set; }
    }

下面有一个满100减20的活动,在结算价格的时候代码是这样的优化

public int GetPrice()
        {           
            Product p = new Product();
            int ret = p.Price;
            if (p.Price >= 100*100)
            {
                ret = ret - 20 * 100;
            }
            return ret;
        }

有问题吗?按照需求来讲没有问题,并且计算的结果也正确。可是从程序艺术来讲,其实很丑陋。如今又有一个全场9折的活动,恰巧有一个商品参与了以上两个活动,并且还能够叠加使用(假设活动参与的顺序是先折扣后满减)。这时候D妹子的代码就变成了这样this

public int GetPrice()
        {
            Product p = new Product();
            //9折活动
            int ret = p.Price * 90 / 100;
            //满减活动
            if (ret >= 100 * 100)
            {
                ret = ret - 20 * 100;
            }
            return ret;
        }

假如如今又来一个相似活动,那这块代码还须要修改,严重违反了开放关闭原则,并且频繁修改已经上线的代码,bug的概率会大大增高。这也是D妹子领导骂她而且让她codereview的缘由。spa

优化版本

那具体要怎么优化呢?修改代码以前,我仍是想提醒一下,有几个要点须要注意一点:.net

  1. 商品菜菜认为有一个共同的基类比较好,这样就有了一个全部商品的控制点,为之后统一添加属性留一个入口。比如一个网关系统,为何会诞生网关这个组件呢,由于有了它咱们能方便的统一添加认证,受权,统计等一些列行为。
  2. 任何促销的活动最好有一个基类,做用相似商品基类。
  3. 对于商品而言,任何促销活动是商品的行为变化点,影响到的是最终的商品价格,因此获取价格这个行为要作特殊的处理。
  4. 不一样种类的促销活动应该能自行扩展,不会影响别的类型促销活动。
  5. 不一样种类的促销活动能叠加使用(其实这里涉及到每一个活动计算的标准是商品原价仍是促销以后价格的问题)。

基于以上几点,首先把商品的对象作一下抽象code

//商品抽象基类
    abstract class BaseProduct
    {
        //商品价格,单位:分
        public int Price { get; set; }
        //获取商品价格抽象方法
        public abstract int GetPrice();

    }
    //抽象商品(好比话费商品),继承商品基类
    class VirtualProduct : BaseProduct
    {
        public override int GetPrice()
        {
            return this.Price;
        }
    }

接下来活动的基类也须要抽象出来对象

//各类活动的抽象基类,继承要包装的类型基类
    abstract class BaseActivity : BaseProduct
    {

    }
有的同窗会问,这里为何要继承商品的基类呢?主要是为了活动的基类能嵌套使用,这样我就能够实现多个活动同时使用,若是不明白不要紧,带着这个问题接着往下看

实现一个打折的活动继承

//打折活动基类,支持多个商品同时结算
    class DiscountActivity : BaseActivity
    {
        BaseProduct product = null;

        public DiscountActivity(int discount, BaseProduct _product)
        {
            Discount = discount;
            product = _product;
        }

        //折扣,好比 90折 即为90
        public int Discount { get; set; }
        //获取折扣以后的价格
        public override int GetPrice()
        {
            return product.GetPrice() * Discount / 100;
        }
    }

实现一个满减的活动,并且支持自定义满减条件图片

class ReductionActivity : BaseActivity
    {
        BaseProduct product = null;
        //满减的对应表
        Dictionary<int, int> reductMap = null;

        public ReductionActivity(Dictionary<int, int> _redutMap, BaseProduct _product)
        {
            reductMap = _redutMap;
            product = _product;
        }
        //获取折扣以后的价格
        public override int GetPrice()
        {
            var productAmount = product.GetPrice();
            //根据商品的总价获取到要减的价格
            var reductValue = reductMap.OrderByDescending(s => s.Key).FirstOrDefault(s => productAmount >= s.Key).Value;
            return productAmount - reductValue;
        }
    }

如今咱们来给商品作个促销活动吧

VirtualProduct p = new VirtualProduct() {  Price=1000};           
            //打折活动
            DiscountActivity da = new DiscountActivity(90, p);
            var retPrice= da.GetPrice();
            Console.WriteLine($"打折后的价格{retPrice}");
            //还能叠加参加满减活动
            Dictionary<int, int> m = new Dictionary<int, int>() ;
            m.Add(200, 5); //满200减5
            m.Add(300, 10); 
            m.Add(500, 20);
            m.Add(1000, 50);
            //这里活动能叠加使用了
            ReductionActivity ra = new ReductionActivity(m, da);
            retPrice = ra.GetPrice();
            Console.WriteLine($"打折满减后的价格{retPrice}");

            ReductionActivity ra2 = new ReductionActivity(m, ra);
            retPrice = ra2.GetPrice();
            Console.WriteLine($"再打折后的价格{retPrice}");

输出结果:

打折后的价格900
打折满减后的价格880
再打折后的价格860

如今咱们终于能优雅一点的同时进行商品的满减和打折活动了

进化到多个商品同时促销

以上代码已经能够比较优雅的能进行单品的促销活动了,可是现实每每很骨感,真实的电商场景中多以多个商品结算为主,那用一样的思路怎么实现呢?

  1. 因为此次须要实现的是多商品促销结算,因此须要一个自定义的商品列表来做为要进行结算的对象。此对象行为级别上与单品相似,有一个需求变化点的抽象:获取价格
//商品列表的基类,用于活动结算使用
    class ActivityListProduct : List<BaseProduct>
    {
        //商品列表活动结算的方法,基类必须重写
        public virtual int GetPrice()
        {
            int ret = 0;
            base.ForEach(s =>
            {
                ret += s.GetPrice();
            });
            return ret;
        }
    }
  1. 把多商品促销活动的基类抽象出来,供不一样的促销活动继承使用,这里须要继承ActivityListProduct,为何呢?和单品的相似,为了多个子类可以嵌套调用
//商品列表 活动的基类,继承自商品列表基类
    internal abstract class BaseActivityList : ActivityListProduct
    {

    }
  1. 建立一个打折和满减活动
//打折活动基类,支持多个商品同时结算
    class DiscountActivityList : BaseActivityList
    {
        ActivityListProduct product = null;
        public DiscountActivityList(int discount, ActivityListProduct _product)
        {
            Discount = discount;
            product = _product;
        }
        //折扣,好比 90折 即为90
        public int Discount { get; set; }
        public override int GetPrice()
        {
            var productPrice = product.GetPrice();
            return productPrice * Discount / 100;
        }
    }
    //满减的活动
    class ReductionActivityList : BaseActivityList
    {
        ActivityListProduct product = null;
        //满减的对应表
        Dictionary<int, int> reductMap = null;

        public ReductionActivityList(Dictionary<int, int> _redutMap, ActivityListProduct _product)
        {
            reductMap = _redutMap;
            product = _product;
        }
        //获取折扣以后的价格
        public override int GetPrice()
        {
            var productAmount = product.GetPrice();
            //根据商品的总价获取到要减的价格
            var reductValue = reductMap.OrderByDescending(s => s.Key).FirstOrDefault(s => productAmount >= s.Key).Value;
            return productAmount - reductValue;
        }
    }

先来一波多商品促销活动

VirtualProduct p = new VirtualProduct() { Price = 1000 };
            VirtualProduct p2 = new VirtualProduct() { Price = 1000 };
            ActivityListProduct lst = new ActivityListProduct();
            lst.Add(p);
            lst.Add(p2);
            DiscountActivityList dalist = new DiscountActivityList(80, lst);
            Console.WriteLine($"打折后的价格{dalist.GetPrice()}");
            DiscountActivityList dalist2 = new DiscountActivityList(90, dalist);
            Console.WriteLine($"打折后的价格{dalist2.GetPrice()}");

            DiscountActivityList dalist3 = new DiscountActivityList(90, dalist2);
            Console.WriteLine($"打折后的价格{dalist3.GetPrice()}");

            //还能叠加参加满减活动
            Dictionary<int, int> m = new Dictionary<int, int>();
            m.Add(200, 5); //满200减5
            m.Add(300, 10);
            m.Add(500, 20);
            m.Add(1000, 50);

            ReductionActivityList ral = new ReductionActivityList(m, dalist3);
            Console.WriteLine($"再满减打折后的价格{ral.GetPrice()}");

结算结果:

打折后的价格1600
打折后的价格1440
打折后的价格1296
再满减打折后的价格1246

如今基本上可让D妹子不被挨骂了

神化版本见留言区

知道D妹子为何取名D妹子吗?


添加关注,查看更精美版本,收获更多精彩

image

相关文章
相关标签/搜索