background

As far as I know, almost all Internet companies have projects related to e-commerce, and most of them are important, such as JINGdong and Taobao. Since there are e-commerce projects, it will inevitably involve commodities, once there are commodities, there will be various promotional activities, such as 100 minus 20, March 8 Women’s Day 10% off and so on similar activities. As a coder how to achieve the requirements of the product dog, the minimum change code, the most elegant implementation. Today’s food is not good, about D girl’s problem. NetCore c# code is used as an example. Other languages are similar.

D Girl version

First of all, D girl has a commodity object, and there is a price attribute in the commodity. The unit of price is minutes

Class Product {// other attributes omitted public int Price {get; set; }}Copy the code

And then there’s an activity that goes up to 100 minus 20, which is what the code looks like at the settlement price

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

Any questions? According to the requirements, there is no problem, and the calculation results are correct. But in terms of procedural art, it’s ugly. Now there is another 10% discount activity, which happens to have one product participating in the above two activities, and can also be used in combination (assuming that the order of participation in the activity is the first discount and then full reduction). This is what D girl’s code looks like

public int GetPrice() { Product p = new Product(); Int ret = p.rice * 90/100; If (ret >= 100 * 100) {ret = ret-20 * 100; } return ret; }Copy the code

If there is a similar activity now, this piece of code still needs to be modified, which seriously violates the open and closed principle. Moreover, the code that has been online is frequently modified, and the probability of bug will be greatly increased. This is also the reason why D girl’s leader scolded her and made her codereview.

Optimized version

So how do you optimize it? Before I change the code, I want to remind you that there are a few important points to note:

  1. Commodity dishes think it is good to have a common base class, so that there is a control point for all items, leaving an entry point for adding attributes uniformly later. Like a gateway system, why can be born gateway this component, because with it we can easily add authentication, authorization, statistics and other column behavior.
  2. Any promotional activity should have a base class, similar to the merchandise base class.
  3. For commodities, any promotion activity is the behavioral change point of commodities, which affects the final price of commodities, so the behavior of obtaining price should be treated specially.
  4. Different types of promotions should be self-expanding without affecting other types of promotions.
  5. Different kinds of promotional activities can be used in combination (in fact, it involves the question of whether the standard for calculating each activity is the original price of goods or the price after promotion).

Based on the above points, first of all, the object of the commodity to do an abstraction

Public int Price {get; public int Price {get; set; } public abstract int GetPrice(); } class VirtualProduct: BaseProduct {public override int GetPrice() {return this.price; }}Copy the code

Next, the base class of the activity needs to be abstracted

Abstract class BaseActivity: BaseProduct {} Abstract class BaseActivity: BaseProduct {}Copy the code

Some of you might ask, why do we inherit the base class of the good? The main purpose is for the base class of the activity to be nested so that I can implement multiple activities to be used at the same time. If you don’t understand, it’s ok to continue with this question

Implement a discounted event

Class DiscountActivity: BaseActivity {BaseProduct product = null; public DiscountActivity(int discount, BaseProduct _product) { Discount = discount; product = _product; } public int Discount {get; set; } public override GetPrice() {return product.getprice () * Discount / 100; }}Copy the code

Implements a full decrement activity, and supports custom full conditions

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 GetPrice() {var productAmount = product.getprice (); / / on the product price is available to reduce the price of the var reductValue = reductMap. OrderByDescending (s = > s.K ey). FirstOrDefault (s = > productAmount > = s.Key).Value; return productAmount - reductValue; }}Copy the code

Now let’s do a promotion for our products

VirtualProduct p = new VirtualProduct() { Price=1000}; // DiscountActivity da = new DiscountActivity(90, p); var retPrice= da.GetPrice(); Console.WriteLine($" discounted price {retPrice}"); Dictionary<int, int> m = new Dictionary<int, int>(); m.Add(200, 5); // Max 200 minus 5 m.dd (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}");Copy the code

Output result:

The discounted price is 900. The discounted price is 880. The discounted price is 860Copy the code

Now we’re finally able to have a little more grace with both full and discount sales

Evolve to multiple sales at the same time

The above code can be more elegant to carry out single product promotion activities, but the reality is often very skinny, the real electric shopping mall landscape with multiple commodity settlement, that with the same idea how to achieve it?

  1. Since we need to implement multi-item promotion settlement this time, we need a custom item list as the settlement object. This object is similar to a single item at the level of behavior, with an abstraction of one point of demand change: the acquisition price
// Base class for the list of goods used for activity settlement using class ActivityListProduct: Public virtual int GetPrice() {int ret = 0; public virtual int GetPrice() {int ret = 0; base.ForEach(s => { ret += s.GetPrice(); }); return ret; }}Copy the code
  1. Abstract the base class of a multi-item promotion to be inherited by different promotions. Here we need to inherit ActivityListProduct. Why? Similar to singleton, for multiple subclasses to be able to nest calls
BaseActivityList: ActivityListProduct {}Copy the code
  1. Create a discount and full discount campaign
// Class DiscounVityList: BaseActivityList {ActivityListProduct product = null; // Class DiscountActivityList: BaseActivityList {ActivityListProduct product = null; public DiscountActivityList(int discount, ActivityListProduct _product) { Discount = discount; product = _product; } public int Discount {get; set; } public override int GetPrice() { var productPrice = product.GetPrice(); return productPrice * Discount / 100; }} // ReductionActivityList: BaseActivityList {ActivityListProduct Product = null; // Dictionary<int, int> reductMap = null; public ReductionActivityList(Dictionary<int, int> _redutMap, ActivityListProduct _product) { reductMap = _redutMap; product = _product; } public override GetPrice() {var productAmount = product.getprice (); / / on the product price is available to reduce the price of the var reductValue = reductMap. OrderByDescending (s = > s.K ey). FirstOrDefault (s = > productAmount > = s.Key).Value; return productAmount - reductValue; }}Copy the code

Start with a wave of multi-product promotions

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($" Discounted price {dalist.GetPrice()}"); DiscountActivityList dalist2 = new DiscountActivityList(90, dalist); Console.WriteLine($" Discounted price {dalist2.getPrice ()}"); DiscountActivityList dalist3 = new DiscountActivityList(90, dalist2); Console.WriteLine($" Discounted price {dalist3.getPrice ()}"); Dictionary<int, int> m = new Dictionary<int, int>(); m.Add(200, 5); // Max 200 minus 5 m.dd (300, 10); m.Add(500, 20); m.Add(1000, 50); ReductionActivityList ral = new ReductionActivityList(m, dalist3); Console.WriteLine($" rall.getprice ()}");Copy the code

Settlement result:

The discounted price 1600 the discounted price 1440 the discounted price 1296 the full discount price 1246Copy the code

Now it’s almost safe for D girl to get away with it

See the comments section for the deified version

Do you know why d-girl was named D-girl?

More interesting articles

  • Distributed large concurrent series
  • Architectural Design Series
  • Series of interesting algorithms and data structures
  • Design Pattern series