[PYTHON] I studied about design patterns (personal memo) Part 4 (AbstractFactory pattern, Bridge pattern, Strategy pattern)

Introduction

This article is a personal study memo. I am writing an article driven by the obsession that what I input must be output. I am writing this article on Qiita with the hope that someone who is familiar with it will be able to point out mistakes and give advice.

I live a working life as an engineer, but I haven't learned about design patterns properly, so I studied.

What is described here https://github.com/ck-fm0211/notes_desigh_pattern I'm uploading to.

Past log

I studied about design patterns (personal memo) Part 1 I studied about design patterns (personal memo) Part 2 I studied about design patterns (personal memo) Part 3

Abstract Factory pattern

-By preparing a class that specializes in instantiation, a pattern to definitely generate a series of objects that require consistency --Example --Consider a program to create a "car" --Try adding tires and steering wheel to the variable car, which is a "car" object.

  car.add_tire(CarTire());
  car.add_handle(CarHandle());

――It may happen that a programmer makes a mistake and gives you a different tire (bicycle tire).

  car.add_tire(BicycleTire());
  car.add_handle(CarHandle());

--It is possible to use objects that should not be used --In such a case, prepare a factory class that takes care of creating a series of objects necessary for creating a car, and when creating objects such as tires and steering wheels, use this factory class to create objects. By making the generation of, it is possible to avoid the above mistake. --By changing this factory class, you will be able to change a series of objects to be used.

Actually use

Subject

――Think about making a "pot"

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


class HotPot:
    pot = None
    soup = None
    protein = None
    vegetables = []
    other_ingredients = []

    @staticmethod
    def hot_pot(pot):
        HotPot.pot = pot

    @staticmethod
    def add_soup(soup):
        HotPot.soup = soup

    @staticmethod
    def add_main(protein):
        HotPot.protein = protein

    @staticmethod
    def add_vegitables(vegitables):
        HotPot.vegetables = vegitables

    @staticmethod
    def add_other_ingredients(other_ingredients):
        HotPot.other_ingredients = other_ingredients

--Prepare a Factory class that abstracts the process of making a pot

class Factory(metaclass=ABCMeta):

    @abstractmethod
    def get_soup(self):
        pass

    @abstractmethod
    def get_main(self):
        pass

    @abstractmethod
    def get_vegetables(self):
        pass

    @abstractmethod
    def get_other_ingredients(self):
        pass

--Prepare the Mizutaki Factory class that inherits this, and make mizutaki based on this.

class MizutakiFactory(Factory):

    def get_soup(self):
        return "torigara"

    def get_main(self):
        return "chicken"

    def get_vegetables(self):
        return ["hakusai", "ninjin", "negi"]

    def get_other_ingredients(self):
        return ["tofu"]


class Sample:

    @staticmethod
    def main():
        hp = HotPot()
        mf = MizutakiFactory()

        hp.add_soup(mf.get_main())
        hp.add_main(mf.get_main())
        hp.add_vegitables(mf.get_vegetables())
        hp.add_other_ingredients(mf.get_other_ingredients())

--Select and generate the Factory class to be actually used according to the character string given in the argument

class Sample:


    @staticmethod
    def create_factory(s):
        if s == "kimchi hot pot":
            return KimuchiFactory()

        elif s == "Sukiyaki":
            return SukiyakiFactory()

        else:
            return MizutakiFactory()

    @staticmethod
    def main(arg):
        hp = HotPot()
        fc = create_factory(arg)

        hp.add_soup(fc.get_main())
        hp.add_main(fc.get_main())
        hp.add_vegitables(fc.get_vegetables())
        hp.add_other_ingredients(fc.get_other_ingredients())

--In the main method, processing proceeds without knowing the actual type of the Factory method. In other words, we are proceeding with processing using the abstract Factory class. ――It is possible to respond to the request to "softly replace the objects to be used".

Summary of Builder patterns

AbstractFactory.png

Bridge pattern

--Functions and implementations can be separated and each can be extended independently. --Example --Suppose a class MyClassA with a method called methodA is inherited by two classes, MyClassASub1 and MyClassASub2, which have different implementations of the methodA method. --Suppose you created a class called MyClassB class that inherits from MyClassA in order to add a method called methodB to MyClassA. --For MyClassB, if you want to use the same implementation as methodA implemented in MyClassASub1 and MyClassASub2, you need to create classes such as MyClassBSub1 and MyClassBSub2 that inherit from the MyClassB class ← ** Troublesome ** ――The larger the scale, the more troublesome the game becomes.

Actually use

Subject

--Consider an abstract class Sorter that has a sort function and a class (QuickSorter class, BubbleSorter class) that implements the sort (Object obj []) method, which is an abstract method defined in this Sorter class. --The code for the Sorter class, QuickSorter class, and BubbleSorter class is as follows. The implementation of the sort part is omitted here because it is not important.

# -*- coding:utf-8 -*-
from abc import ABCMeta, abstractmethod


class Sorter(metaclass=ABCMeta)

    @abstractmethod
    def sort(self, obj):
        pass


class QuickSorter(Sorter):

    def sort(self, obj):
        #Quick sort
        pass


class BubbleSorter(Sorter):

    def sort(self, obj):
        #Bubble sort
        pass

--I want to add a timer_sorter method that has a function to display the time taken for sorting in the Sorter class.

from datetime import datetime

class TimerSorter(metaclass=ABCMeta, Sorter):

    @abstractmethod
    def time_sorter(self, obj):
        start = datetime.now()
        Sorter.sort(obj)
        end = datetime.now()
        print("time:" + str((end - start)))

--At this rate, no implementation can be given to the TimerSorter class. --In order to implement it, it is necessary to prepare a class equivalent to QuickSorter / BubbleSorter. --Think with the Bridge pattern. Design the method to be delegated to the class hierarchy for implementation for the method whose implementation may be changed. --Class hierarchy for implementation: Here, consider the class hierarchy whose parent is the SortImple class as the class hierarchy that gives the implementation of the sort method.

class SortImple(metaclass=ABCMeta):
    @abstractmethod
    def sort(self, obj):
        pass


class Sorter:
    si = SortImple()

    @staticmethod
    def sorter(si):
        Sorter.si = si

    def sort(self, obj):
        Sorter.si.sort(obj)


class QuickSorterImple(SortImple):

    def sort(self, obj):
        #Quick sort
        pass


class BubbleSorterImple(SortImple):

    def sort(self, obj):
        #Bubble sort
        pass

--By doing this, in order to add functionality, even a new class created by extending the Sorter class will be able to use the implementation part that already exists. For example, to create a TimerSorter class that extends the Sorter class, it will be as follows.

from datetime import datetime


class TimerSorter(metaclass=ABCMeta, Sorter):

    def __init__(self, sort_impl):
        super(sort_impl)

    @abstractmethod
    def time_sorter(self, obj):
        start = datetime.now()
        Sorter.sort(obj)
        end = datetime.now()
        print("time:" + str((end - start)))

--By separating the class hierarchy for extending the function and the class hierarchy for extending the implementation, you can use the implementation hierarchy class and the extension class in any combination you like. --In this example, the Sorter and SortImple classes serve as a bridge between the extension class hierarchy and the implementation extension class hierarchy.

Summary of Builder patterns

Bridge.png

Strategy pattern

――When programming normally, the algorithm may be implemented in a form that blends into the method. --Change the algorithm by branching with an if statement, etc. --In the Strategy pattern, be aware of the strategy part and create it as a separate class. --Create the strategy x part as a separate class, and if you want to change the strategy, change the strategy class to be used. ――By using the Strategy pattern, the design becomes more flexible and easier to maintain than the algorithm that is integrated into the method.

Actually use

Subject

――There are many cases where you have to change the algorithm depending on the situation. For example, in a game program, the strategy algorithm is changed depending on the difficulty level. ――Here, let's think about an algorithm that makes a simple comparison between large and small. --First, create a Human class that represents humans. The Human class shall have four parameters: name, height, weight and age.

# -*- coding:utf-8 -*-
from abc import ABCMeta, abstractmethod


class Human:

    def __init__(self, name, height, weight, age):
        self.name = name
        self.height = height
        self.weight = weight
        self.age = age

--Here, consider a class called SampleClass that compares the magnitude of two Human instances given.

class SampleClass:

    @staticmethod
    def compare(h1: Human, h2: Human) -> int:
        if h1.age > h2.age:
            return 1
        elif h1.age == h2.age:
            return 0
        else:
            return -1

--Here, I'm only thinking about comparing ages and returning the results. ――However, Human objects have multiple parameters, and there are several possible ways to compare Humans. --The comparison result will differ depending on which parameter is used and how. ――For example, when comparing simply by age and when comparing by height, the results will be different. --Therefore, consider a program that allows you to specify the parameters to be compared.

class SampleClass2:

    type = -1
    COMPARE_AGE = 1
    COMPARE_HEIGHT = 2
    COMPARE_WEIGHT = 3

    def __init__(self, type):
        SampleClass2.type = type

    @staticmethod
    def compare(h1: Human, h2: Human) -> int:
        if SampleClass2.type == SampleClass2.COMPARE_AGE:
            if h1.age > h2.age:
                return 1
            elif h1.age == h2.age:
                return 0
            else:
                return -1
        elif SampleClass2.type == SampleClass2.COMPARE_HEIGHT:
            if h1.height > h2.height:
                return 1
            elif h1.height == h2.height:
                return 0
            else:
                return -1
        #・ ・ ・

――It becomes complicated code. In the Strategy pattern, the part of the algorithm that needs to be changed according to the situation is consciously separated as a separate class to improve the prospect of modifying or adding the algorithm. --First, separate the comparison algorithm part as a class. For example, create an AgeComparator class to compare ages.

class AgeComparator:

    @staticmethod
    def compare(h1: Human, h2: Human) -> int:
        if h1.age > h2.age:
            return 1
        elif h1.age == h2.age:
            return 0
        else:
            return -1

--Separate the comparison algorithm part so that the actual comparison process can be delegated to AgeComparator.

class MyClass:

    @staticmethod
    def compare(h1: Human, h2: Human) -> int:
        return AgeComparator.compare(h1, h2)

――This alone has no merit, and it is not a Strategy pattern. The Strategy pattern requires that separate algorithm parts have a common interface. --That is, multiple classes separated as an algorithm need to have a common interface. --In the sample case, in addition to the AgeComparator class for comparing ages, the HeightComparator class for comparing heights and the WeightComparator class for comparing weights can be considered. --Give a common interface to the classes that represent these comparison algorithms. --Here, let's define the Comparator interface.

class Comparator(metaclass=ABCMeta):

    @staticmethod
    @abstractmethod
    def compare(h1: Human, h2: Human) -> int:
        pass


class AgeComparator(Comparator):

    @staticmethod
    def compare(h1: Human, h2: Human) -> int:
        if h1.age > h2.age:
            return 1
        elif h1.age == h2.age:
            return 0
        else:
            return -1


class HeightComparator(Comparator):

    @staticmethod
    def compare(h1: Human, h2: Human) -> int:
        if h1.height > h2.height:
            return 1
        elif h1.height == h2.height:
            return 0
        else:
            return -1

--By doing this, SampleClass can be rewritten as follows.

class SampleClass:

    def __init__(self, comp: Comparator):
        self._comp = comp

    def compare(self, h1: Human, h2: Human) -> int:
        return self._comp.compare(h1, h2)

Summary of Strategy patterns

Strategy.png

Recommended Posts

I studied about design patterns (personal memo) Part 4 (AbstractFactory pattern, Bridge pattern, Strategy pattern)
I studied about design patterns (personal memo) Part 3 (Prototype pattern, Builder pattern)
I studied about design patterns (personal memo) Part 5 (Composite pattern, Decorator pattern, Visitor pattern)
I studied about design patterns (personal memo) Part 8 (Proxy pattern, Command pattern, Interpreter pattern)
I studied about design patterns (personal memo) Part 7 (Observer pattern, Memento pattern, State pattern, Flyweight pattern)
I studied about design patterns (personal memo) Part 6 (Chain of Responsibility pattern, Facade pattern, Mediator pattern)
Design Pattern #Strategy
I studied about Systemd properly