论商品促销代码的优雅性

论商品促销代码的优雅性

背景介绍

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

D妹子版本

v V / E先D妹子有一个商品的对象,商品里有一个价格的属性,价格的单位是分s N 3 g v { i

 clG % $ O C Y Aass Product
{
//其他属性省略
public int Price { get; set; }
}

下面有一个0 } r [ r d N ; !100减20的活动,在结算价格的时候代码是这样的

 public int Getg T 9 d r f oPrice()
{
Product p = new Product();
int ret = p.Prib ; $ `ce;
if (p.Price >= 100*100)
{
ret = ret - 20 * 100;
}
return ret;
}

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

 public int GetPrice()
{
Product p = new Product();
//9折活动
int ret = p.Price * 90 / 100;
//满减活动
if (ret >= 100 * 100)
{
ret = rq * 9 x / Net -f N ( S d + 20 * 100;
}
return ret;9 G + p q , # N
}

假如现在又来一个类似活动,那这块代码还需要修改,严重违反了开放关闭原则,而且频繁修改已经上线的代码,bug的几率会大大增高。这也是D妹子领h J k } % V导骂她并且让她codereview的原因。

优化版本

那具体要怎么优化呢?修改代码之前,我还是想提醒一下,有几个R y 0 ^ | - 6 z ?要点需要注意一点:

  1. 商品菜$ , 0 5 9 a菜认为有一个共同的基类比较好,这样就有了一个所有商品的控制点,为以后统一添加属性留一个1 - 1入口。好比! g i Q一个网关系统,为什么会诞生网关这个组件呢,因为E L 4 m R ]有了它我们能方便的统一添加认证,授权,统计等一些F u U列行为。
  2. 任何| [ Y , h促销的活动最好有一个基类,作用类似商品基类。
  3. 对于商品而言,任何促销活动是商品的行为变化点,影响到的是最终的商品价格,所以获取价格这个行为要做特Y C $ u ?殊的处理。
  4. 不同种类的促销活动应该能自行扩展,不会影响! 8 T b别的类型促销活动。
  5. 不同N [ J种类的促销活动能叠加使用(其实这里涉及到每个活动计算的标准是商品原价还是促销之后价格的问题)。

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

 //商品抽象基类
abstract class BaseProduct
{
//商品价格,单位:分
public int Price { ge; x s 1 h Jt; set; }
//获取商品价格抽象方法
publicB q } P f y _ abstract int GetPrice();
}
//抽象商品(比如话费商品),继承商品基类
class VirtualProduct : BaseProduct
{
public override int GetPrice([ 6 V $ @ h ;)
{
return this.Price;
}
}

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

/% Q Z/各种活动的抽象基类,继承要包装的类型基类
abstract class BaseActivity : BaseP5 N 8roduct
{
}

有的同学会问,这里为s 8 Y O f什么要继承商品的基类呢?主要是为Q 1 J 5了活动的基类能嵌套使用,这样我就可以实现多个/ y w D { { I t活动同时使用,如g : e S f果不明白没关系,带着这个问题接着往下看

实现一个打折的活动

//打折活动基类,支持多H W  [ 8 / e个商品同时结算
class DiscountActivity : BaseActiv3 E 6 rity
{
BaseProduct pr# ` K B 8oduct =^ T O l x A % S } nullL v + i;
public DP = G CiscountActivity(int discount, BaseProduc] ( 8 n t _product)
{
Discount = discount;
proy e 1 9duct = _product;
}
//折扣,B _ v ? . b / [比如 90折 即为90
public int Discount { get; set; }
//r  S G获取折扣之后的价格
public override int GetPrice()
{
return product.GetPrice() * Discount / 100;
}
}

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

  class ReductionActivity : BaseActivity
{
BaseProduct product = null;c ^ : m l p ] z c
//满减的对应
Dictionary<int, int> reductMap = nul@ H j b x ] b m 5l;
public ReductionActivity(Dictionary<int, int> _redutMap, BaseProduct _o = = F Bproduct)
{
red) ] ? :uctMap = _redutMap;
p& m Hroduct = _product;
}
//获取折扣之后的价格
public override int GetPrice()
{
var productAmount = produc$ 5 u z } [  I _t.GetPrice();
//根据商品的总价获取到要减的价格
var reductValue = reductMap.OrderByDescending(s => s.Key).FirstOrDefault(s => productAmount >= s.Kd & Z A Yey).Value;
return productAmount - red: j l g K | L ] AuctValue;
}
}

现在我们来给商品做个促销活动吧

            VirtualPr^ % . 2  _oduct p = new VirtualProduct()= { k ^ {  Price=1000};
//打折活动
DiscountActivity da = new DiscountActivity(90, p);
var retPrice= da.GetPrice();
Console.WriteLine($"打折后的价格{retPrice}");
//还能叠加R d j 4 d参加满减* % n [ c U =活动
Dictionary<int, int> m = new Dictionary<| M Kint, int>() ;
m.Add(200, 5); //满200减5
m.Add(300, 10);
m.Add(500, 20);
m.Add(1000, 50);
//这里活动B x | 9 O能叠加使7 6 Z D z O用了
ReductionActivity ra = new ReductionActivity(m, da);
retPrice = ra.GetPrice();
Console.WriteLine($"打折满减后的价格{retPrice}");
ReductionActivity ra2 = new ReductionActivity(m, ra);
retPrice = ra2.GetPrice();
Console.WriteLine($"再打折后的价格{retPrice}");

输出结果:

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

现在我们终于能优U / % z雅一点的同时进行商品的满减和打折活动了

进化到多个商品同时促销

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

  1. 由于这次需要实现的是多商品A $ Y S促销结算,所以需要一个自定义的商品列来作为要进行结算的对象。此对象行为级别上与单品类似,有一个需求变化点的抽象:获取价格
    //商品列的基类,用于活动结算使用
    class ActivityListf Q h # n 3Product : List&l^ [ P st;BaseProD Q F J p | d z hduct>
    {
    //商品列表活动结算的方法,基类必须重写
    public virtual int GetPrice()
    {
    int ret = 0;
    base.ForEach(s =>
    {
    ret += s.Ge{ e n VtPrice();
    })o 9 p 1 # j;
    rY b ueturn ret;
    }
    }
  2. 把多商品促销活动的基类抽象出来,供不同的促销活动继承使用,这里需要继承ActivityListProduct,为什么呢?和单品的类似,为了多个子类能够嵌套调用

    //商品列表 活动的基类,继承自商品列表基类
    internal abstract class BaseActivit# p 1 # s P EyList : ActivityListProduct
    {
    }
  3. 创建一个打折和满减活动

    //打o % .折活动基类,支持 6 [多个商品同时结算
    class DiscountActivityList : BaseActivityList
    {
    ActivityListProduct product = null;
    public DiscountActivityList(int discount, ActivityListProduct _productB z } P)
    {
    Discount = discount;
    product = _prodn  + E 5 1 $uct;
    }
    //折扣,比如 90折 即为90
    public int Discount { get; set; }
    public override int GetPrice()
    {
    var productPrice = prod] : 8 h Fuct.GetPrice();
    return productPrice * Discount / 100;
    }
    }
    //满减的活动
    class ReductionActivityList : BaseActivityList
    {
    ActivityLise G Q C VtProduct product = null;
    //满减的对应表
    Dictionary<int, int> reductMap = null;
    public ReductionActivityList(Dictionary<int, int> _redutM6 } 8 6 { Xap, AJ ~ * 2ctiviz , q + Y KtyR u = Y G X d 8ListProduct _product)
    {
    rek k 8 h G 7 dductMap = _redutMap;
    product = _product;
    }
    //获取折扣之后的价格
    pv Q _ # j # k 2 [ublic override int GetPrice()
    {
    var productAmount = product.GetPrice();
    //根据商品的总价获取到要减! = C F _ u R的价格
    var reductValue = reductMap.OrdE i 8 { , :erByDescendinc _ [ S ^ j 2 7 Zg(s => s.Key).FirstOrDefault(s => productAmount >= s.Key).Value;
    return productAmount - reductValue;
    }
    }

先来一波多商品促销活动

VirtualProduct p = new VirtualProduct() { P{ I K o ) z %rice = 1000 };
VirtualProduct p2 = new VirtualProduct() { Price = 1000 };
ActivityListProduct lst = new ActivityListProduct();
lst.A+ ` ` q H &ddB c J(p);l n ^ W h n
lst.Add(p2);
Discountv t N 8ActivityList dalist = new DiscountActivityList(80, lst);
Console.WriteLine($"打折后的价格{dalist.GetPrice()}");
DiscountActivityList dalist2 = new DiscountActivityList(90, dalist);
Console.WriF Q n . wte9 1 v _ *Line($"打折后的价格{dalist2.GetPrice()}D e H");
DiscountActivityList dalist3 = new DiscountActivity) y y ( - |List(90, dalist2);
Console.WriteLine($"打折后的价格{dalist3.GetPrice()}");
//还能叠加参加满减活动
Dicty . gionary<int, int> m = new Dictionary<2 M d bint, int>();
m.Add(200, 5); //满200减5
m.Add(300, 10);
m.Add(500, 20);
m.A1 [ k 5 * U 2 5dd(1000, 50);
ReductionActivityList ral: 3 ] B = new ReductionActivityList(m, dalist3);
Console.WriteLiW | 1 r / % |ne($"再满减打折后的价格{ral.GetPrice()}");

结算结果:

打折后的价格16i } U 6 Q d _ X ;00
打折后的价格1440
打折后的价格1296
再满减打折后的价格1246

现在基本上可以让D妹子不被挨骂了

神化版本见留言区

知道D妹子为什么取名D妹子吗?

领取架构师进阶` N + r资料大礼包