程序员过关斩将--论商品促销代码的优雅性
點(diǎn)擊上方藍(lán)色字體,關(guān)注我們
菜菜哥,YY說你幫她解決了幾個問題,也幫我解決一個唄
原來是D妹子,來坐我身邊,說下情況
我的項(xiàng)目是個電商項(xiàng)目,現(xiàn)在產(chǎn)品狗要給商品做活動
正常呀
我一個新手初來咋到頂不住壓力了,上次來一個折扣活動,現(xiàn)在又來一個滿減
正常呀
最要命的兩個活動還能疊加使用
正常呀
我寫的代碼讓老大罵了一頓,讓我做優(yōu)化
代碼有多爛?
離近一點(diǎn),我給你看看
好嘞
????????據(jù)我所知,幾乎所有的互聯(lián)網(wǎng)公司都帶有和電商有關(guān)的項(xiàng)目,而且在大多數(shù)公司里面還是舉足輕重的重頭戲,比如京東,淘寶。既然有電商項(xiàng)目,必然會涉及到商品,一旦有商品就會有各種促銷活動,比如 滿100減20,三八婦女節(jié)9折等等類似活動。作為一個coder怎么才能在實(shí)現(xiàn)產(chǎn)品狗的需求下,最小改動代碼,最優(yōu)雅的實(shí)現(xiàn)呢。今天菜菜不才,就D妹子的問題獻(xiàn)丑一番。以下以.netCore c#代碼為例,其他語言類似。
????????首先D妹子有一個商品的對象,商品里有一個價格的屬性,價格的單位是分
?class?Product????{
????????//其他屬性省略
????????public?int?Price?{?get;?set;?}
????}
下面有一個滿100減20的活動,在結(jié)算價格的時候代碼是這樣的
public?int?GetPrice()????????{???????????
????????????Product?p?=?new?Product();
????????????int?ret?=?p.Price;
????????????if?(p.Price?>=?100*100)
????????????{
????????????????ret?=?ret?-?20?*?100;
????????????}
????????????return?ret;
????????}
有問題嗎?按照需求來說沒有問題,而且計(jì)算的結(jié)果也正確。但是從程序藝術(shù)來說,其實(shí)很丑陋。現(xiàn)在又有一個全場9折的活動,恰巧有一個商品參與了以上兩個活動,而且還可以疊加使用(假設(shè)活動參與的順序是先折扣后滿減)。這時候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;
????????}
假如現(xiàn)在又來一個類似活動,那這塊代碼還需要修改,嚴(yán)重違反了開放關(guān)閉原則,而且頻繁修改已經(jīng)上線的代碼,bug的幾率會大大增高。這也是D妹子領(lǐng)導(dǎo)罵她并且讓她codereview的原因。
那具體要怎么優(yōu)化呢?修改代碼之前,我還是想提醒一下,有幾個要點(diǎn)需要注意一點(diǎn):
1.? 商品菜菜認(rèn)為有一個共同的基類比較好,這樣就有了一個所有商品的控制點(diǎn),為以后統(tǒng)一添加屬性留一個入口。好比一個網(wǎng)關(guān)系統(tǒng),為什么會誕生網(wǎng)關(guān)這個組件呢,因?yàn)橛辛怂覀兡芊奖愕慕y(tǒng)一添加認(rèn)證,授權(quán),統(tǒng)計(jì)等一些列行為。
2.? 任何促銷的活動最好有一個基類,作用類似商品基類。
3.? 對于商品而言,任何促銷活動是商品的行為變化點(diǎn),影響到的是最終的商品價格,所以獲取價格這個行為要做特殊的處理。
4.? 不同種類的促銷活動應(yīng)該能自行擴(kuò)展,不會影響別的類型促銷活動。
5.? 不同種類的促銷活動能疊加使用(其實(shí)這里涉及到每個活動計(jì)算的標(biāo)準(zhǔn)是商品原價還是促銷之后價格的問題)。
基于以上幾點(diǎn),首先把商品的對象做一下抽象
//商品抽象基類????abstract?class?BaseProduct
????{
????????//商品價格,單位:分
????????public?int?Price?{?get;?set;?}
????????//獲取商品價格抽象方法
????????public?abstract?int?GetPrice();
????}
????//抽象商品(比如話費(fèi)商品),繼承商品基類
????class?VirtualProduct?:?BaseProduct
????{
????????public?override?int?GetPrice()
????????{
????????????return?this.Price;
????????}
????}
接下來活動的基類也需要抽象出來
//各種活動的抽象基類,繼承要包裝的類型基類????abstract?class?BaseActivity?:?BaseProduct
????{
????}
有的同學(xué)會問,這里為什么要繼承商品的基類呢?主要是為了活動的基類能嵌套使用,這樣我就可以實(shí)現(xiàn)多個活動同時使用,如果不明白沒關(guān)系,帶著這個問題接著往下看
實(shí)現(xiàn)一個打折的活動
//打折活動基類,支持多個商品同時結(jié)算????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;
????????}
????}
實(shí)現(xiàn)一個滿減的活動,而且支持自定義滿減條件
`??class?ReductionActivity?:?BaseActivity
????{
????????BaseProduct?product?=?null;
????????//滿減的對應(yīng)表
????????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();
????????????//根據(jù)商品的總價獲取到要減的價格
????????????var?reductValue?=?reductMap.OrderByDescending(s?=>?s.Key).FirstOrDefault(s?=>?productAmount?>=?s.Key).Value;
????????????return?productAmount?-?reductValue;
????????}
????}
現(xiàn)在我們來給商品做個促銷活動吧
?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}");
輸出結(jié)果:
打折后的價格900打折滿減后的價格880
再打折后的價格860
現(xiàn)在我們終于能優(yōu)雅一點(diǎn)的同時進(jìn)行商品的滿減和打折活動了
以上代碼已經(jīng)可以比較優(yōu)雅的能進(jìn)行單品的促銷活動了,但是現(xiàn)實(shí)往往很骨感,真實(shí)的電商場景中多以多個商品結(jié)算為主,那用同樣的思路怎么實(shí)現(xiàn)呢?
1.? 由于這次需要實(shí)現(xiàn)的是多商品促銷結(jié)算,所以需要一個自定義的商品列表來作為要進(jìn)行結(jié)算的對象。此對象行為級別上與單品類似,有一個需求變化點(diǎn)的抽象:獲取價格
????class?ActivityListProduct?:?List<BaseProduct>
????{
????????//商品列表活動結(jié)算的方法,基類必須重寫
????????public?virtual?int?GetPrice()
????????{
????????????int?ret?=?0;
????????????base.ForEach(s?=>
????????????{
????????????????ret?+=?s.GetPrice();
????????????});
????????????return?ret;
????????}
????}
2.? 把多商品促銷活動的基類抽象出來,供不同的促銷活動繼承使用,這里需要繼承ActivityListProduct,為什么呢?和單品的類似,為了多個子類能夠嵌套調(diào)用
//商品列表?活動的基類,繼承自商品列表基類????internal?abstract?class?BaseActivityList?:?ActivityListProduct
????{
????}
3.? 創(chuàng)建一個打折和滿減活動
//打折活動基類,支持多個商品同時結(jié)算????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;
????????//滿減的對應(yīng)表
????????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();
????????????//根據(jù)商品的總價獲取到要減的價格
????????????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()}");
結(jié)算結(jié)果:
打折后的價格1600打折后的價格1440
打折后的價格1296
再滿減打折后的價格1246
現(xiàn)在基本上可以讓D妹子不被挨罵了
知道D妹子為什么取名D妹子嗎?
為了答謝粉絲的支持,菜菜二期福利來臨,你領(lǐng)取了嗎
●程序員過關(guān)斬將--你的面向接口編程一定對嗎?
●程序員修神之路--高并發(fā)下為什么更喜歡進(jìn)程內(nèi)緩存
●程序員修神之路--高并發(fā)優(yōu)雅的做限流(有福利)
●程序員過關(guān)斬將--快速遷移10億級數(shù)據(jù)
●程序員修神之路--分布式緩存的一條明路(附代碼)
●程序員修仙之路--把用戶訪問記錄優(yōu)化到極致
互聯(lián)網(wǎng)之路,菜菜與君一同成長
長按識別二維碼關(guān)注
你點(diǎn)的每個贊,我都認(rèn)真當(dāng)成了喜歡 創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的程序员过关斩将--论商品促销代码的优雅性的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [NewLife.XCode]实体类详解
- 下一篇: 在实际项目中使用LiteDB NoSQL