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

640?wx_fmt=gif

点击上方蓝色字体,关注我们

640?wx_fmt=gif

菜菜哥,YY说你帮她解决了几个问题,也帮我解决一个呗

640?wx_fmt=png640?wx_fmt=jpeg

原来是D妹子,来坐我身边,说下情况

640?wx_fmt=png640?wx_fmt=jpeg

我的项目是个电商项目,现在产品狗要给商品做活动

640?wx_fmt=png640?wx_fmt=jpeg

正常呀

640?wx_fmt=png640?wx_fmt=jpeg

我一个新手初来咋到顶不住压力了,上次来一个折扣活动,现在又来一个满减

640?wx_fmt=png640?wx_fmt=jpeg

正常呀

640?wx_fmt=png640?wx_fmt=jpeg

最要命的两个活动还能叠加使用

640?wx_fmt=png640?wx_fmt=jpeg

正常呀

640?wx_fmt=png640?wx_fmt=jpeg

我写的代码让老大骂了一顿,让我做优化

640?wx_fmt=png640?wx_fmt=jpeg

代码有多烂?

640?wx_fmt=png640?wx_fmt=jpeg

离近一点,我给你看看

640?wx_fmt=png640?wx_fmt=jpeg

好嘞

640?wx_fmt=png640?wx_fmt=jpeg640?wx_fmt=png


◆◆背景介绍◆◆


        据我所知,几乎所有的互联网公司都带有和电商有关的项目,而且在大多数公司里面还是举足轻重的重头戏,比如京东,淘宝。既然有电商项目,必然会涉及到商品,一旦有商品就会有各种促销活动,比如 满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<BaseProduct>
    {
        //商品列表活动结算的方法,基类必须重写
        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


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


神化版本见留言区

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

为了答谢粉丝的支持,菜菜二期福利来临,你领取了吗


640?wx_fmt=png


640?wx_fmt=gif

●程序员过关斩将--你的面向接口编程一定对吗?

●程序员修神之路--高并发下为什么更喜欢进程内缓存

●程序员修神之路--高并发优雅的做限流(有福利)

程序员过关斩将--快速迁移10亿级数据

程序员修神之路--分布式缓存的一条明路(附代码)

程序员修仙之路--把用户访问记录优化到极致

互联网之路,菜菜与君一同成长

长按识别二维码关注

640?wx_fmt=jpeg640?wx_fmt=png你点的每个赞,我都认真当成了喜欢

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/316658.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Math(牛客多校第三场)

Math 题意&#xff1a; 问你有多少对(x,y),1<x<y<n,满足(x2 y2)%(xy1) 0 题解&#xff1a; 这种题。。。直接打表芜湖~ 通过打表发现&#xff1a;满足情况的为(i,i * i * i),但是也有不和谐的声音出现&#xff1a;当x8时&#xff0c;会出现两个&#xff0c;一个…

[NewLife.XCode]增删改查入门

NewLife.XCode是一个有10多年历史的开源数据中间件&#xff0c;由新生命团队(2002~2019)开发完成并维护至今&#xff0c;以下简称XCode。整个系列教程会大量结合示例代码和运行日志来进行深入分析&#xff0c;蕴含多年开发经验于其中。开源地址&#xff1a;https://github.com/…

在实际项目中使用LiteDB NoSQL数据库

LiteDB 是一个 NoSQL 数据库&#xff0c;特点是 MongoDB like 和 0 配置。100% 原汁原味的 C# 开发, Release 只有一个 DLL&#xff0c;官方有一下适用场景&#xff1a;移动App&#xff0c;桌面小应用程序&#xff0c;特有的文件格式&#xff0c;小型的 Web 应用&#xff0c;需…

初探奥尔良(Orleans)

由于工作上关系目前经常被各种并发数据问题搞得焦头烂额&#xff0c;要么要性能舍弃数据上得一致性&#xff0c;要么要一致性但是却得到了特别糟糕的响应。难道鱼和熊掌真的无法兼得吗&#xff1f;然后找到了类似奥尔良这种基于Actor模型的kuangjia首先本人因为是C#系的所以暂不…

[NewLife.XCode]功能设置

NewLife.XCode是一个有10多年历史的开源数据中间件&#xff0c;由新生命团队(2002~2019)开发完成并维护至今&#xff0c;以下简称XCode。整个系列教程会大量结合示例代码和运行日志来进行深入分析&#xff0c;蕴含多年开发经验于其中&#xff0c;代表作有百亿级大数据实时计算项…

[NewLife.XCode]数据模型文件

NewLife.XCode是一个有10多年历史的开源数据中间件&#xff0c;由新生命团队(2002~2019)开发完成并维护至今&#xff0c;以下简称XCode。整个系列教程会大量结合示例代码和运行日志来进行深入分析&#xff0c;蕴含多年开发经验于其中。开源地址&#xff1a;https://github.com/…

[NewLife.XCode]高级增删改

NewLife.XCode是一个有10多年历史的开源数据中间件&#xff0c;支持nfx/netstandard&#xff0c;由新生命团队(2002~2019)开发完成并维护至今&#xff0c;以下简称XCode。整个系列教程会大量结合示例代码和运行日志来进行深入分析&#xff0c;蕴含多年开发经验于其中&#xff0…

[NewLife.XCode]数据初始化

NewLife.XCode是一个有10多年历史的开源数据中间件&#xff0c;支持nfx/netstandard&#xff0c;由新生命团队(2002~2019)开发完成并维护至今&#xff0c;以下简称XCode。整个系列教程会大量结合示例代码和运行日志来进行深入分析&#xff0c;蕴含多年开发经验于其中&#xff0…

[NewLife.XCode]反向工程(自动建表建库大杀器)

NewLife.XCode是一个有10多年历史的开源数据中间件&#xff0c;支持nfx/netstandard&#xff0c;由新生命团队(2002~2019)开发完成并维护至今&#xff0c;以下简称XCode。整个系列教程会大量结合示例代码和运行日志来进行深入分析&#xff0c;蕴含多年开发经验于其中&#xff0…

树上启发式合并

文章内容选自OI Wiki 参考博客 内容&#xff1a; 树上启发式合并&#xff08;dsu on tree&#xff09;对于某些树上离线问题可以速度大于等于大部分算法且更易于理解和实现的算法。 他是用来解决一类树上询问问题&#xff0c;一般这种问题有两个特征&#xff1a; 只有对子树…

Wexflow:C#中的开源工作流引擎

Wexflow是一个高性能、可扩展、模块化和跨平台的工作流引擎。Wexflow在GitHub&#xff1a;https://github.com/aelassas/Wexflow。Wexflow的目标是在没有用户干预的情况下自动执行重复任务。在Wexflow的帮助下&#xff0c;构建自动化和工作流过程变得简单。Wexflow还有助于使长…

ASP.NET Core 沉思录 - Logging 的两种介入方法

ASP.NET Core 中依赖注入是一个很重要的环节。因为几乎所有的对象都是由它创建的&#xff08;相关文章请参见《ASP.NET Core 沉思录 - ServiceProvider 的二度出生》&#xff09;。因此整个日志记录的相关类型也被直接添加到了 IServiceCollection 中。今天我们将介绍各个接口/…

C# 中的Async 和 Await 的用法详解

众所周知C#提供Async和Await关键字来实现异步编程。在本文中&#xff0c;我们将共同探讨并介绍什么是Async 和 Await&#xff0c;以及如何在C#中使用Async 和 Await。同样本文的内容也大多是翻译的&#xff0c;只不过加上了自己的理解进行了相关知识点的补充&#xff0c;如果你…

Docker的部署-包括网关服务(Ocelot)+认证服务(IdentityServer4)+应用服务

本文主要介绍通过Docker来部署通过.Net Core开发的微服务架构&#xff0c;部署的微服务主要包括统一网关&#xff08;使用Ocelot开发&#xff09;、统一认证&#xff08;IdentityServer4&#xff09;、应用服务&#xff08;asp.net core web api&#xff09;&#xff1b;本文不…

ASP.NET Core 实战:使用 Docker 容器化部署 ASP.NET Core + MySQL + Nginx

一、前言在之前的文章&#xff08;ASP.NET Core 实战&#xff1a;Linux 小白的 .NET Core 部署之路&#xff09;中&#xff0c;我介绍了如何在 Linux 环境中安装 .NET Core SDK / .NET Core Runtime、Nginx、MySQL&#xff0c;以及如何将我们的 ASP.NET Core MVC 程序部署到 Li…

VS2017 无法连接到Web服务器“IIS Express”终极解决方案

今天日了gou了&#xff0c;一大早打开VS2017的时候出现无法连接到Web服务器“IIS Express”的错误&#xff0c;然后必应了一下&#xff0c;再谷歌了一下找到的解决方法也都千篇一律&#xff0c;奈何都没能解决&#xff0c;最后通过静下心来的思考&#xff0c;尝试解决了问题&am…

Docker最全教程之使用.NET Core推送钉钉消息(二十)

前言上一篇我们通过实战分享了使用Go推送钉钉消息&#xff0c;由于技痒&#xff0c;笔者现在也编写了一个.NET Core的Demo&#xff0c;作为简单的对照和说明。最后&#xff0c;由于精力有限&#xff0c;笔者希望有兴趣的朋友可以分享下使用CoreRT将.NET Core编译成机器代码这块…

Average

Average 题意&#xff1a; 矩阵W的值可以通过数组a和b得到&#xff0c;W[i][j]a[i]b[j],现在求W的一个子矩阵&#xff0c;平均值最大&#xff0c;且子矩阵必须满足宽度至少是x&#xff0c;高度至少是y&#xff0c;计算最大平均值 题解&#xff1a; 那答案就变成了分别对a和b…

开箱即用Bumblebee独立部署搭建webapi网关详解

在之前的章节里都是讲述如何在程序中使用Bumblebee来构建一个Webapi网关&#xff1b;但这样显然有些麻烦&#xff0c;毕竟很多时候可能只需要一个简单负载处理&#xff0c;还需要写个程序针对服务进行编写代码或配置的确是比较麻烦的事情&#xff1b;如果有负载方面的调整还需要…

ASP.NET Core 文件系统

静态文件 目录浏览 默认页面 MIME类型配置 实战文件服务器 紧接上一讲 中间件 之后&#xff0c;今天来我们来讲一下关于 ASP.NET Core 中静态文件服务。什么是静态文件&#xff1f;先看一下下面例子&#xff08;在客户端浏览器中通过 url 路径访问了网站的一张图片&#xff09…