Topic: Suppose you have a pizza shop and its functions include placing orders and making pizzas. How would your code be written?

def order_pizza(a):
    pizza = Pizza()
    pizza.prepare()
    pizza.bake()
    pizza.cut()
    pizza.box()
    return pizzaCopy the code

But now you have a problem, your pizza shop needs more pizzas, so now you need to add some code to determine the appropriate type of pizza, and then make the pizza:

def order_pizza(pizza_type):  Now pass the pizza type to order_pizza()

    Based on the Pizza type, we instantiate the correct concrete class and then assign it to the Pizza instance variable
    if pizza_type == 'cheese':
        pizza = CheesePizza()
    elif pizza_type == 'greek':
        pizza = GreekPizza()
    elif pizza_type == 'pepperoni':
        pizza = PepperoniPizza()
    # Once we have a pizza, we need to prepare it (roll it out, add seasoning), then bake it, slice it, and box it
    pizza.prepare()
    pizza.bake()
    pizza.cut()
    pizza.box()
    return pizzaCopy the code

However, after several days of practice, you find that ClamPizza and Veggie Pizza are popular among customers, but Greek Pizza is not popular among customers. At this time, you need to modify the code:

def order_pizza(pizza_type):  Now pass the pizza type to order_pizza()

    Based on the Pizza type, we instantiate the correct concrete class and then assign it to the Pizza instance variable
    if pizza_type == 'cheese':
        pizza = CheesePizza()
    # elif pizza_type == 'Greek ': # Greek pizza no longer appears on the menu
    # pizza = GreekPizza()
    elif pizza_type == 'pepperoni':
        pizza = PepperoniPizza()
    # Clam Pizza and Veggie Pizza have been added
    elif pizza_type == 'clam':
        pizza = ClamPizza()
    elif pizza_type == 'veggie':
        pizza = VeggiePizza()

    # Once we have a pizza, we need to prepare it (roll it out, add seasoning), then bake it, slice it, and box it
    pizza.prepare()
    pizza.bake()
    pizza.cut()
    pizza.box()
    return pizzaCopy the code

Now you see a problem. Order_pizza () instantiates the concrete Pizza class internally, and order_pizza() is not closed to modifications, so that the code of order_pizza() has to be modified every time a new Pizza is added. A better way to create a Pizza object is to abstract it out and modify the code as follows:

# Remove the code that creates the object from the order_pizza method
def create_pizza(pizza_type):
    Based on the Pizza type, we instantiate the correct concrete class and then assign it to the Pizza instance variable
    if pizza_type == 'cheese':
        pizza = CheesePizza()
    elif pizza_type == 'pepperoni':
        pizza = PepperoniPizza()
    elif pizza_type == 'clam':
        pizza = ClamPizza()
    elif pizza_type == 'veggie':
        pizza = VeggiePizza()
    return pizza

def order_pizza(pizza_type):  Now pass the pizza type to order_pizza()
    The pizza class is created using the create_pizza() method
    pizza = create_pizza(pizza_type)

    # Once we have a pizza, we need to prepare it (roll it out, add seasoning), then bake it, slice it, and box it
    pizza.prepare()
    pizza.bake()
    pizza.cut()
    pizza.box()
    return pizzaCopy the code

Simple Factory model

We extracted the code that created the Pizza object into a new method that we called a factory.

The factory handles the details of creating the object, and once you have create_pizza, order_pizza() becomes the customer of this object. When a pizza is needed, just tell the factory what type of pizza it needs and ask it to make one.

Now the order_pizza() method only cares about getting a pizza from the factory. This pizza implements the pizza interface, so it can call prepare(), bake(), cut(), and box().

Q: Now you may be asking, what is the benefit of this code that looks more complicated? It just seems to be moving the problem to another object. A: At this point, Order_pizza is only a customer of Create_Pizza, and other customers (such as pizza shop menu PizzaShopMenu) can also use this factory to get pizzas. Wrap the pizza creation code in a class that only needs to change this part of the code when you implement changes later.

Here our factory create_order() is a simple method, and methods that define a simple factory using methods are often referred to as simple factory patterns (simple factories are more like a programming habit than a design pattern).

Redo PizzaStore class

In the above code, order_pizza is the customer code, but in order to make our pizza shop more extensible, we need to modify the customer code:

class SimplePizzaFactory:

    def create_pizza(self, pizza_type):.return pizza

class PizzaStore:
    def order_pizza(self, pizza_type):  Now pass the pizza type to order_pizza()
        factory = SimplePizzaFactory()
        pizza = factory.create_pizza(pizza_type)
        ...
        return pizza

    Here are other possible methodsCopy the code

In this code, we have wrapped a method (create_pizza) with a class (SimplePizzaFactory). The purpose is to enable factories to change the behavior of creating methods through inheritance and, in doing so, to improve the extensibility of factory methods.

Now take a look at the class diagram of our pizza shop:

Pizza shop class diagram

Limitations of the simple factory pattern

disadvantages

  • Because the factory class centralizes all product creation logic, it violates the principle of highly cohesive responsibility assignment, and when it does not work properly, the entire system is affected.
  • It is difficult to expand the system. Once new products are added, the factory logic has to be modified. If there are many product types, the factory logic may be too complex, which is not conducive to the expansion and maintenance of the system.

Usage scenarios

  • The factory class is responsible for creating fewer objects
  • The customer only knows the parameters passed into the factory class and doesn’t care how the object is created (logically); The client doesn’t need to care about the creation details or even remember the class name, just the parameters that the type corresponds to.

To overcome these limitations, we move on to the factory method pattern

Factory method pattern

Now we have a new problem, after we set up the pizza shop, some people want to join in, but we also want to control the production process of pizza, how to achieve it?

First, to use the framework for the pizza shop, all we need to do is put the create_pizza() method back into the PizzaStore class, but this method needs to be implemented once in each subclass. Now the PizzaStore code is:

class PizzaStore:

    def create_pizza(self, pizza_type):
        NotImplementedError is raised for every method that needs to be subclassed
        We could also set PizzaStore's metaclass to ABC.ABCMeta
        # In this case, this class is truly an abstract base class
        raise NotImplementedError()

    def order_pizza(self, pizza_type):  Now pass the pizza type to order_pizza()

        pizza = self.create_pizza(pizza_type)

        # Once we have a pizza, we need to prepare it (roll it out, add seasoning), then bake it, slice it, and box it
        pizza.prepare()
        pizza.bake()
        pizza.cut()
        pizza.box()
        return pizzaCopy the code

So we declare a factory method. This factory method handles object creation and encapsulates the creation behavior in a subclass so that the client code on the parent class is decoupled from the subclass’s object creation code.

We put create_pizza back into PizzaStore so that subclasses that inherit this method are responsible for defining their own create_pizza() method. Now let’s look at the PizzaStore subclass diagram:

Here NYStlyePizzaStore and ChicagoStylePizzaStore need to define their own create_pizza methods.

Now look at the full code:

#! -*- coding: utf-8 -*-

class Pizza:

    name = None
    dough = None
    sauce = None
    toppings = []

    def prepare(self):
        print("Preparing %s" % self.name)
        print("Tossing dough...")
        print("Adding sauce...")
        print("Adding toppings: ")
        for topping in self.toppings:
            print(" %s" % topping)

    def bake(self):
        print("Bake for 25 minutes at 350")

    def cut(self):
        print("Cutting the pizza into diagonal slices")

    def box(self):
        print("Place pizza in official PizzaStore box")

    def __str__(self):
        return self.name


class NYStyleCheesePizza(Pizza):

    name = "NY Style Sauce and Cheese Pizza"
    dough = "Thin Crust Dough"
    sauce = "Marinara Sauce"
    toppings = ["Grated"."Reggiano"."Cheese"]


class ChicagoStyleCheesePizza(Pizza):

    name = "Chicago Style Deep Dish Cheese Pizza"
    dough = "Extra Thick Crust Dough"
    sauce = "Plum Tomato Sauce"
    toppings = ["Shredded"."Mozzarella"."Cheese"]

    def cut(self):
        print("Cutting the pizza into square slices")


class PizzaStore:
    def create_pizza(self, pizza_type):
        NotImplementedError is raised for every method that needs to be subclassed
        We could also set PizzaStore's metaclass to ABC.ABCMeta
        # In this case, this class is truly an abstract base class
        raise NotImplementedError()

    def order_pizza(self, pizza_type):  Now pass the pizza type to order_pizza()

        pizza = self.create_pizza(pizza_type)

        # Once we have a pizza, we need to prepare it (roll it out, add seasoning), then bake it, slice it, and box it
        pizza.prepare()
        pizza.bake()
        pizza.cut()
        pizza.box()
        return pizza

class NYStylePizzStore(PizzaStore):
    def create_pizza(self, pizza_type):
        Based on the Pizza type, we instantiate the correct concrete class and then assign it to the Pizza instance variable
        if pizza_type == 'cheese':
            pizza = NYStyleCheesePizza()
        return pizza


class ChicagoStylePizzaStore(PizzaStore):
    def create_pizza(self, pizza_type):
        Based on the Pizza type, we instantiate the correct concrete class and then assign it to the Pizza instance variable
        if pizza_type == 'cheese':
            pizza = ChicagoStyleCheesePizza()
        return pizza


def main(a):
    nystore = NYStylePizzStore()
    pizza = nystore.order_pizza('cheese')
    print("goodspeed ordered a %s" % pizza)

    print("*" * 100)
    chicago_store = ChicagoStylePizzaStore()
    pizza = chicago_store.order_pizza('cheese')
    print("goodspeed ordered a %s" % pizza)


if __name__ == '__main__':
    main()Copy the code

The factory method create_pizza() directly raises NotImplementedError. This is done to force subclasses to re-implement create_pizza() or raise NotImplementedError if they don’t. You could also set PizzaStore’s metaclass to ABC.ABCMeta, in which case the class would be a real abstract base class.

Now let’s look at the class diagram of the factory method pattern:

Factory method pattern class diagram

The product class and the creator class are parallel hierarchies of classes and their relationship is shown below:

Factory method pattern definition

From the above introduction, we can get the general definition of factory method pattern:

Parent in factory method pattern, the factory is responsible for defining public interfaces, objects created products and factories subclass object is responsible for generating the specific products, the purpose is to delay product class instantiation operations to complete, factory subclasses by factory subclasses to determine exactly which a specific product class to instantiate.

The factory method pattern can encapsulate instantiations of concrete types, and the abstract Creator provides a factory method for creating objects. In the abstract Creator, any other implementation method might use this method to lock the manufactured product, but only subclasses actually implement the factory method and create the product.

Here is the factory method pattern schematic class diagram:

Factory method pattern class diagram

Factory method pattern benefits

  • Factory methods create objects in one place, making it easier to trace them.
  • The factory method pattern can help us change the implementation of products fromuseIn theThe decoupling. If you add a product or change the implementation of the product, Creator will not be affected.
  • Another advantage of using the factory method pattern is that when you add a new product to the system, you don’t need to modify the abstract factory and the interface provided by the abstract product, you don’t need to modify the client, or you don’t need to modify other concrete factories and concrete products, you just need to add a concrete factory and concrete products. In this way, the scalability of the system has become very good, fully in line with the “open and closed principle”.

The factory approach allows you to create new objects when necessary, improving performance and memory utilization. If you instantiate the class directly to create the object, you will need to allocate additional memory each time you create a new object.

Simple factoryandThe factory methodThe difference between

The simple factory does everything in one place (create_pizza), whereas the factory method creates a framework and lets subclasses decide how to implement it. For example, in the factory method, the order_pizza() method provides a generic framework for creating pizzas, and the order_pizza() method relies on the factory method to create concrete classes and produce the actual pizza. What kind of pizza is made by inheriting PizzaStore to achieve. But simple factories simply encapsulate objects and do not have the flexibility of factory methods.

Examples of using factory patterns in Python applications

Djangos Forms module uses the factory method pattern to create form fields. WTForm also uses the factory method pattern. The factory method pattern is also used in the different database connection parts of SQLAlchemy.

conclusion

The core idea of the factory method pattern is to define a common interface for creating objects, and let the factory, rather than the customer, decide which classes need to be instantiated. It is often used when constructing the overall framework of the system. The factory method mode seems to be relatively simple, but its connotation is extremely profound. The theory of abstraction, encapsulation, inheritance, delegation, polymorphism and other object-oriented design has been well reflected, and its application range is very wide.

reference

  • Head First Design Patterns
  • Master Python Design Patterns
  • Python Programming in Action
  • Python Design Pattern series 3: Create the Factory Method pattern

Finally, thank your girlfriend for her support.

Welcome to follow (April_Louisa) Buy me a Fanta
Welcome to attention
Buy me a Fanta