Python and design patterns: Policy patterns

There are often many ways to complete a task, which we call strategy.

For example, in the supermarket, if your shopping points are over 1000, you can exchange 10 yuan for cash vouchers. If you buy 10 pieces of the same commodity, you can give a 10% discount. If the purchase amount exceeds 500, you can enjoy a discount of 50 yuan. These are three different promotion strategies.

For another example, to contact friends and classmates, you can call, send text messages, send wechat or email. These are four different contact strategies.

For another example, to travel, we can choose train, bus, plane or self driving. These are four different travel strategies.

The above real scenarios all have the shadow of the strategy selection model. You can consider using the strategy mode.

The classic strategy model consists of three parts

  • Context: context environment class
  • Strategy: policy base class
  • ConcreteStrategy: specific strategy

Take the first supermarket activity as an example.

  • Context: Order class, order information, including commodity, price and quantity, for the buyer, etc
  • Strategy: Promotion class, abstract base class, including an abstract method (calculate discount)
  • Contretestrategy: it is divided into three classes, FidelityPromo, BulkItemPromo and LargeOrderPromo, which implement specific discount calculation methods.

The first is the Order class:

class Item:
    def __init__(self, issue, price, quantity):
        self.issue = issue
        self.price = price
        self.quantity = quantity

    def total(self):
        return self.price * self.quantity

class Order:
    def __init__(self, customer, promotion=None):
        self.cart = []
        self.customer = customer
        self.promotion = promotion

    def add_to_cart(self, *items):
        for item in items:
            self.cart.append(item)

    def total(self):
        total = 
        for item in self.cart:
            total += item.total()

        return total

    def due(self):
        if not self.promotion:
            discount = 
        else:
            discount  = self.promotion.discount(self)
        return (self.total() - discount)

Then there is the strategy of exchanging points for cash coupons. In order to ensure that our code has good scalability and maintainability, I will first write a policy class, which is an abstract base class, and its subclasses are a specific strategy, which must be implemented   discount   Methods, such as our points for cash strategy.

from abc import ABC, abstractmethod

class Promotion(ABC):
    @abstractmethod
    def discount(self, order):
        pass


class FidelityPromo(Promotion):
    '''
    If the points are over 1000 points, you can exchange 10 yuan cash coupons
    '''
    def discount(self, order):
        return 10 if order.customer.fidelity >1000 else 

Suppose Xiao Ming goes to the mall to buy a dress (600 yuan) and two pairs of shoes (200 * 2). His shopping points are 1500 points.

At ordinary times, shopping malls generally have no activities, but there are activities of exchanging points for cash vouchers for many years.

>>> from collections import namedtuple

# Define two fields: name and shopping points
>>> Customer = namedtuple('Customer', 'name fidelity')
>>> xm = Customer('Xiao Ming', 1500)
>>> item1 = Item('shoes', 200, 3)
>>> item2 = Item('clothes', 600, 1)
>>> order = Order(xm, FidelityPromo())
>>> order.add_to_cart(item1, item2)

# The original price is 1200. After using the points, it's only 1190
>>> order
<Order Total:1200 due:1190>

Seeing that may day is coming soon, the mall is ready to launch a big promotion

  • As long as you buy 10 single items, you can get a 10% discount.
  • If the total order amount is greater than or equal to 500, you can immediately reduce 50.

With what we used before   Strategy mode   Lay the foundation, we are not using Selling mobile phone numbers Hard coding is used to configure the policy, so there is no need to change too much source code. Just directly define the two Promotion policy classes in section 51 (also inherited from the Promotion abstract base class), just like a plug-in, plug and play.

class BulkItemPromo(Promotion):
    '''
    If you buy 10 single items, you can get a 10% discount.
    '''
    def discount(self, order):
        discount = 
        for item in order.cart:
            if item.quantity >= 10:
                discount += item.total() * 0.1
        return discount

class LargeOrderPromo(Promotion):
    '''
    If the total order amount is greater than or equal to 500, you can immediately reduce 50
    '''
    def discount(self, order):
        discount = 
        if order.total() >= 500:
            discount = 50

        return discount

Seeing that the activity of the mall was so awesome, Xiao Ming's wallet also bulled up, and began to set up daily necessities.

If the first strategy is used, the original price is 600, and it only costs 580

>>> from collections import namedtuple
>>> Customer = namedtuple('Customer', 'name fidelity')

>>> xm = Customer('Xiao Ming', 300)

>>> item1 = Item('tissue', 20, 10)
>>> item2 = Item('Edible oil', 50, 4)
>>> item3 = Item('milk', 50, 4)


>>> order = Order(xm, BulkItemPromo())
>>> order.add_to_cart(item1, item2, item3)

>>> order
<Order Total:600 due:580.0>

If the second strategy is used, the original price is 600, and it only costs 550

>>> from collections import namedtuple
>>> Customer = namedtuple('Customer', 'name fidelity')

>>> xm = Customer('Xiao Ming', 300)

>>> item1 = Item('tissue', 20, 10)
>>> item2 = Item('Edible oil', 50, 4)
>>> item3 = Item('milk', 50, 4)


>>> order = Order(xm, LargeOrderPromo())
>>> order.add_to_cart(item1, item2, item3)

>>> order
<Order Total:600 due:550>

The two strategies are plug and play. You only need to select the corresponding strategy when placing an order at the foreground. The original business logic does not need to be changed.

>>> order = Order(xm, BulkItemPromo())
>>> order = Order(xm, LargeOrderPromo())

However, the problem soon came again. The mall engaged in activities, but let customers manually choose which preferential strategy to use. As a conscientious merchant, it should be able to automatically compare all strategies to get the most favorable price to customers. This requires the background code to be able to find out all the currently available strategies and compare the discounts one by one.

# Find out all the promotion strategies
all_promotion = [globals()[name] for name in globals() if name.endswith('Promo') and name != 'BestPromo']

# Implement an optimal policy class
class BestPromo(Promotion):
    def discount(self, order):
        # Find all policies in the current file
        all_promotion = [globals()[name] for name in globals() if name.endswith('Promo') and name != 'BestPromo']

        # Calculate maximum discount
        return max([promo().discount(order) for promo in all_promotion])

When placing an order at the front desk, it will automatically calculate all preferential policies and directly tell customers the cheapest price.

# Choose this optimal strategy directly
>>> order = Order(xm, BestPromo())
>>> order.add_to_cart(item1, item2, item3)

>>> order
<Order Total:600 due:550>

Through the above examples, we can summarize the benefits of using the policy pattern

  1. Excellent scalability, convenient transplantation and flexible use. It is convenient to extend the policy;
  2. Each policy can be switched freely. This is also one of the benefits of relying on abstract classes to design interfaces;

But at the same time, the strategic model will also bring some disadvantages.

  1. When the project is large, there may be many strategies, which is not easy to maintain;
  2. The user of a strategy must know which strategies are available before deciding which strategy to use, which is contrary to Dimitri's law.

For the above examples, think carefully. In fact, there are many places that can be optimized.

For example, in Order to implement the classic pattern, we must first define an abstract base class, and then implement a specific policy class. For the above simple logic of calculating discount price, it can be implemented by function, and then specify the policy function when instantiating the Order class. There is no need to move the class out. This can avoid creating policy objects constantly when placing orders and reduce redundant runtime consumption. There is no specific code here.

Therefore, to learn design patterns, we should not only know how to use such patterns to organize code, but also understand their ideas, learn and use them flexibly and flexibly.

The above is about today   Strategy mode   Some personal sharing, if you can't speak in place, please leave a message on the background to correct!

Tags: Python

Posted on Fri, 29 Oct 2021 02:43:43 -0400 by nate2687