歡迎光臨
每天分享高質量文章

程式員過關斬將–論商品促銷程式碼的優雅性

點選上方藍色字型,關註我們

菜菜哥,YY說你幫她解決了幾個問題,也幫我解決一個唄

原來是D妹子,來坐我身邊,說下情況

我的專案是個電商專案,現在產品狗要給商品做活動

正常呀

我一個新手初來咋到頂不住壓力了,上次來一個折扣活動,現在又來一個滿減

正常呀

最要命的兩個活動還能疊加使用

正常呀

我寫的程式碼讓老大罵了一頓,讓我做最佳化

程式碼有多爛?

離近一點,我給你看看

好嘞

 

◆◆
背景介紹
◆◆
 

        據我所知,幾乎所有的網際網路公司都帶有和電商有關的專案,而且在大多數公司裡面還是舉足輕重的重頭戲,比如京東,淘寶。既然有電商專案,必然會涉及到商品,一旦有商品就會有各種促銷活動,比如 滿100減20,三八婦女節9折等等類似活動。作為一個coder怎麼才能在實現產品狗的需求下,最小改動程式碼,最優雅的實現呢。今天菜菜不才,就D妹子的問題獻醜一番。以下以.netCore c#程式碼為例,其他語言類似。

 

◆◆
D妹子版本
◆◆
 

        首先D妹子有一個商品的物件,商品裡有一個價格的屬性,價格的單位是分

 class Product
    {
        //其他屬性省略
        public int Price { getset; }
    }

 

下麵有一個滿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妹子的程式碼就變成了這樣

        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的原因。

 

◆◆
最佳化版本
◆◆
 

那具體要怎麼最佳化呢?修改程式碼之前,我還是想提醒一下,有幾個要點需要註意一點:

1.  商品菜菜認為有一個共同的基類比較好,這樣就有了一個所有商品的控制點,為以後統一新增屬性留一個入口。好比一個閘道器係統,為什麼會誕生閘道器這個元件呢,因為有了它我們能方便的統一新增認證,授權,統計等一些列行為。

2.  任何促銷的活動最好有一個基類,作用類似商品基類。

3.  對於商品而言,任何促銷活動是商品的行為變化點,影響到的是最終的商品價格,所以獲取價格這個行為要做特殊的處理。

4.  不同種類的促銷活動應該能自行擴充套件,不會影響別的型別促銷活動。

5.  不同種類的促銷活動能疊加使用(其實這裡涉及到每個活動計算的標準是商品原價還是促銷之後價格的問題)。

 

基於以上幾點,首先把商品的物件做一下抽象

//商品抽象基類
    abstract class BaseProduct
    {
        //商品價格,單位:分
        public int Price { getset; }
        //獲取商品價格抽象方法
        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 { getset; }
        //獲取折扣之後的價格
        public override int GetPrice()
        {
            return product.GetPrice() * Discount / 100;
        }
    }

 

實現一個滿減的活動,而且支援自定義滿減條件

`
  class ReductionActivity : BaseActivity
    {
        BaseProduct product = null;
        //滿減的對應表
        Dictionary<intint> reductMap = null;

        public ReductionActivity(Dictionary<intint> _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<intint> m = new Dictionary<intint>() ;
            m.Add(2005); //滿200減5
            m.Add(30010); 
            m.Add(50020);
            m.Add(100050);
            //這裡活動能疊加使用了
            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
    {
        //商品串列活動結算的方法,基類必須重寫
        public virtual int GetPrice()
        {
            int ret = 0;
            base.ForEach(s =>
            {
                ret += s.GetPrice();
            });
            return ret;
        }
    }

 

2把多商品促銷活動的基類抽象出來,供不同的促銷活動繼承使用,這裡需要繼承ActivityListProduct,為什麼呢?和單品的類似,為了多個子類能夠巢狀呼叫

//商品串列 活動的基類,繼承自商品串列基類
    internal abstract class BaseActivityList : ActivityListProduct
    {

    }

 

3.  建立一個打折和滿減活動

//打折活動基類,支援多個商品同時結算
    class DiscountActivityList : BaseActivityList
    {
        ActivityListProduct product = null;
        public DiscountActivityList(int discount, ActivityListProduct _product)
        {
            Discount = discount;
            product = _product;
        }
        //折扣,比如 90折 即為90
        public int Discount { getset; }
        public override int GetPrice()
        {
            var productPrice = product.GetPrice();
            return productPrice * Discount / 100;
        }
    }
    //滿減的活動
    class ReductionActivityList : BaseActivityList
    {
        ActivityListProduct product = null;
        //滿減的對應表
        Dictionary<intint> reductMap = null;

        public ReductionActivityList(Dictionary<intint> _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<intint> m = new Dictionary<intint>();
            m.Add(2005); //滿200減5
            m.Add(30010);
            m.Add(50020);
            m.Add(100050);

            ReductionActivityList ral = new ReductionActivityList(m, dalist3);
            Console.WriteLine($"再滿減打折後的價格{ral.GetPrice()}");

 

結算結果:

打折後的價格1600
打折後的價格1440
打折後的價格1296
再滿減打折後的價格1246

    贊(0)

    分享創造快樂