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.
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
-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.
――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".
--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.
--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.
――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.
――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)
Recommended Posts