Having nothing to do over the weekend, I was home watching Game of Thrones Season 8 with gusto. It felt like it was going to suck, but I couldn’t help but see who would sit on the Iron Throne.

My girlfriend was ordering take-out, as if she was having a little problem with the discount.

strategy

A strategy is a set of strategies that can achieve a goal. In some specific cases, the strategies are interchangeable.

Like the deals we see on food delivery platforms. Full reduction, membership and red envelopes, each major discount contains a number of specific preferential programs. Such as full reduction activities, can be full 20 minus 15, full 50 minus 30 and so on. Members include ordinary members, super members and so on.

Each preferential mode below a number of preferential schemes, in fact, is a strategy. These strategies are mutually exclusive and interchangeable. And it has a certain priority order.

As shown in the figure above, there are four kinds of discounts used in one order, so we can say that we use four kinds of discount strategies in combination.

How to calculate the amount

Let’s start with takeout membership discounts as an example. In order to promote sales, a shop on the takeout platform has set up a variety of member discounts, including 20% discount for super members, 90% discount for ordinary members and no discount for ordinary users.

We hope that when users pay, according to the user’s membership level, they can know which discount strategy users meet, and then discount, calculate the amount payable.

You can write this in your code:

public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) {

    if (BuyerType.SUPER_VIP.name().equals(buyerType)) {
        returnOrderPrice. Multiply (new BigDecimal (0.8)); }if (BuyerType.VIP.name().equals(buyerType)) {
        returnOrderPrice. Multiply (new BigDecimal (0.9)); }return orderPrice;
}
Copy the code

The above code is relatively simple, is in the code through if-else logical judgment, different types of members enjoy different discount prices.

Add one more type of membership

At this time, the platform added a store exclusive membership, which can exclusively enjoy a 30% discount of dishes in a store, so the code should be changed to the following:

public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) {

    if (BuyerType.PARTICULARLY_VIP.name().equals(buyerType)) {
        returnOrderPrice. Multiply (new BigDecimal (0.7)); }if (BuyerType.SUPER_VIP.name().equals(buyerType)) {
        returnOrderPrice. Multiply (new BigDecimal (0.8)); }if (BuyerType.VIP.name().equals(buyerType)) {
        returnOrderPrice. Multiply (new BigDecimal (0.9)); }return orderPrice;
}
Copy the code

Membership discount changes

Later, with the development of business, new requirements require that exclusive members can enjoy the discount only when the shop order amount is more than 30 yuan. The code needs to be changed again:

public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) {

    if (BuyerType.PARTICULARLY_VIP.name().equals(buyerType)) {
        if (orderPrice.compareTo(new BigDecimal(30)) > 0) {
            returnOrderPrice. Multiply (new BigDecimal (0.7)); }}if (BuyerType.SUPER_VIP.name().equals(buyerType)) {
        returnOrderPrice. Multiply (new BigDecimal (0.8)); }if (BuyerType.VIP.name().equals(buyerType)) {
        returnOrderPrice. Multiply (new BigDecimal (0.9)); }return orderPrice;
}
Copy the code

Then, there is another abnormal demand, if the user’s super membership has expired, and the expiration time is within a week, then the user’s single order according to the super membership discount, and a strong reminder at the cashier, guide the user to open the membership again, and the discount is only once. The code needs to be modified as follows:

public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) {

    if (BuyerType.PARTICULARLY_VIP.name().equals(buyerType)) {
        if (orderPrice.compareTo(new BigDecimal(30)) > 0) {
            returnOrderPrice. Multiply (new BigDecimal (0.7)); }}if (BuyerType.SUPER_VIP.name().equals(buyerType)) {
        returnOrderPrice. Multiply (new BigDecimal (0.8)); }if (BuyerType.VIP.name().equals(buyerType)) {
        int  superVipExpiredDays  = getSuperVipExpiredDays();
        int superVipLeadDiscountTimes  = getSuperVipLeadDiscountTimes();
        if(superVipExpiredDays < 7 && superVipLeadDiscountTimes =0){
            updateSuperVipLeadDiscountTimes();
            returnOrderPrice. Multiply (new BigDecimal (0.8)); }returnOrderPrice. Multiply (new BigDecimal (0.9)); }return orderPrice;
}
Copy the code


Why use the policy pattern

The above code, all the code about membership discount is written in a calPrice method, add or subtract a member type, need to change the whole method. Also consider the priority of this membership discount.

In addition to increasing the membership type, any change in the discount strategy for any of the membership types also needs to be changed to the whole algorithm.

This leads to a bloated algorithm, which in turn leads to more and more lines of code, requiring the developer to make every single change in the code to do all the functionality back.

For example, if I just changed the discount of super membership from 20% to 15%, because the code is all together, I need to revert to all the functions of membership discount at launch.

Over time, this code has become a code that no one is willing to change, and no one dares to change. Commonly known as “shit mountain”.
This code makes the code extremely unreadable, maintainable, extensible, and regressive.

The strategy pattern

We say that in everyday life, there are many plans for achieving goals, and each plan is called a strategy. In software development, a similar situation is often encountered. There are multiple ways to achieve a certain function. At this time, a design pattern can be used to make the system flexibly choose the solution approach, but also can easily add new solutions. This is the strategic pattern.

Strategy Pattern refers to defining a series of algorithms, encapsulating each algorithm and making them interchangeable. The policy pattern lets the algorithm change independently of the customers that use it.

In particular, the policy pattern is only suitable for managing a set of algorithms of the same type, and these algorithms are completely mutually exclusive. This means that only one of multiple policies is effective at any one time. Such as between full 20 minus 10 and full 30 minus 20; Between regular member discount and super member discount.

In the Strategy mode, some independent classes are defined to encapsulate different algorithms, and each class encapsulates a specific algorithm. Here, each class that encapsulates an algorithm can be called a Strategy. In order to ensure the consistency of these strategies, an abstract Strategy class is generally used to define the algorithm. Each algorithm corresponds to a specific policy class.

To realize the strategic pattern, it must be inseparable from the strategy. As mentioned above, discounts for super members, ordinary members and exclusive members are all strategies. This can be done entirely through the policy pattern.

The implementation policy pattern mainly includes the following roles:

Abstract Policy class

First define an interface, this interface is the abstract policy class, the interface defines the calculation of the price method, the concrete implementation is defined by the concrete policy class.

Public interface Buyer {/** * calculate the due price */ public BigDecimal calPrice(BigDecimal orderPrice); }Copy the code

Concrete policy class

Three specific policy classes are defined for different members, each of which implements price calculation methods.

/** * private member */ Public class ParticularlyVipBuyer {@override public BigDecimal calPrice(BigDecimal orderPrice) {if (orderPrice.compareTo(new BigDecimal(30)) > 0) {
            returnOrderPrice. Multiply (new BigDecimal (0.7)); }} /** * public class SuperVipBuyer implements Buyer {@override public BigDecimal calPrice(BigDecimal) orderPrice) {returnOrderPrice. Multiply (new BigDecimal (0.8)); }} /** * public class implements Buyer {@override public BigDecimal calPrice(BigDecimal orderPrice) {  int superVipExpiredDays = getSuperVipExpiredDays(); int superVipLeadDiscountTimes = getSuperVipLeadDiscountTimes();if(superVipExpiredDays < 7 && superVipLeadDiscountTimes =0){

            returnOrderPrice. Multiply (new BigDecimal (0.8)); }returnOrderPrice. Multiply (new BigDecimal (0.9)); }}Copy the code

The definitions of the above categories reflect the design principle of package changes, and the specific discount method of different members will not affect other members.

After defining the abstract policy class and the concrete policy class, we will define the context class, the so-called context class, is the class of the integration algorithm. In this case, the cash register system. Members are integrated in a combined way.

Public class Cashier {/** * private Cashier; public Cashier(Buyer buyer){ buyer = buyer; } public BigDecimal quote(BigDecimal orderPrice) {returnthis.buyer.calPrice(orderPrice); }}Copy the code

The Cashier class is a context class, defined in terms of more composition, less inheritance, and programming for interfaces, not implementations.

Because of the combination + interface approach, we don’t need to change the Cashier class for other types of membership. We just have to define another class that implements the Buyer interface.

In addition to adding the type of member, when we want to modify the discount of a member, we only need to modify the corresponding policy class of the member, and do not need to modify other policies. You control the scope of the change. The cost is greatly reduced.

Let’s define a client to test it:

Public class Test {public static void main(String[] args) {public static void main(String[] args) {Buyer strategy = new Buyer(); // create context Cashier Cashier = new Cashier(strategy); // Calculate price BigDecimal quote = cashier. Quote (300); System.out.println("The final price of goods for ordinary members is:" + quote.doubleValue());

        strategy = new SuperVipBuyer();
        cashier = new Cashier(strategy);
        quote = cashier.quote(300);
        System.out.println("The final price of super member goods is:"+ quote.doubleValue()); }}Copy the code

Output result:

// The final price of regular member items is: 270.0 // The final price of super member items is: 240.0Copy the code

As you can see from the example above, the policy pattern only encapsulates the algorithm, providing new algorithms to plug into the existing system, and does not determine which algorithm to use when. It is up to the client to decide which algorithm to use in what circumstances.

Advantages and disadvantages of strategic patterns

The strategy pattern can fully embody the principles of object-oriented design, such as encapsulation change, more combination, less inheritance, programming for interface, not programming for implementation.

The strategy mode has the following characteristics:

  • The focus of the strategy pattern is not on how algorithms are implemented, but how they are organized and invoked to make the program structure more flexible, maintainable, and extensible.

  • Each strategy algorithm in the strategy mode is equal. For a specific set of strategy algorithms, everyone has the same status, and because of this equality, algorithms can be replaced with each other. All the strategy algorithms are independent of each other in implementation, there is no dependence on each other. So this series of policy algorithms can be described as follows: Policy algorithms are different implementations of the same behavior.

  • At run time, the policy pattern can use only one specific policy implementation object at a time, and while it can dynamically switch between different policy implementations, only one can be used at a time.

If all concrete policy classes have some common behavior. At this point, these common behaviors should be placed in the common abstract policy role Strategy class. Of course, the abstract policy role must be implemented using Java abstract classes, not interfaces.

However, there is no silver bullet in programming, and the strategy mode is no exception. It also has some disadvantages. Let’s review its advantages first:

  • The policy pattern provides perfect support for the “open closed principle”, allowing users to choose algorithms or behaviors without modifying the existing system, or to flexibly add new ones.

  • The policy pattern provides a way to manage related algorithm families. The hierarchical structure of a policy class defines an algorithm or behavior family. Proper use of inheritance can avoid code duplication by moving common code into a parent class.

  • Use the policy pattern to avoid multiple conditional (if-else) statements. Multiple conditional statement is not easy to maintain, it takes which algorithm or take which behavior of the logic and algorithm or behavior of the logic mixed together, all listed in a multiple conditional statement, than the use of inherited methods but also primitive and backward.

But at the same time, he also has the following shortcomings:

  • The client must know all the policy classes and decide which one to use. This means that the client must understand the differences between these algorithms in order to choose the right algorithm class at the right time. The creation and selection of this policy class can also be aided by the factory pattern.

  • Because the policy pattern encapsulates each specific policy implementation as a separate class, the number of objects can be significant if there are many alternative policies. You can reduce the number of objects to some extent by using the share pattern.


Finally, attached is a mind map of the content of this article: