실용적인 Python 디자인 패턴 정리

Jepeto·2020년 3월 29일
20
post-thumbnail

Mastering Python Design Patterns


Factory Pattern

factory pattern은 객체 생성을 간단하게 하기 위하여 사용된다.

세부적으로 두가지 패턴이 존재한다.
1. factory method: input에 따라 객체 생성이 달라지는 방식
2. abstract factory: 여러 객체 생성을 연관된 group별로 묶어서 객체를 생성하는 방식

1. Factory Method

  • The factory method centralizes object creation and tracking your objects becomes much easier

ex1) forms을 상속받고, 다양한 field의 객체를 생성함

from django import forms

class PersonForm(forms.Form):
    name = forms.CharField(max_length=100)
    birth_date = forms.DateField(required=False)

ex2) for connecting you to different databases (MySQL, SQLite)

ex3) for creating the geometrical object that you request (circle, triangle)

ex4) 다양한 input인 XML, JSON file을 parsing 하고, client's connection을 centralize함

import json
import xml.etree.ElementTree as etree


class JSONDataExtractor:

    def __init__(self, filepath):
        self.data = dict()
        with open(filepath, mode='r', encoding='utf-8') as f:
            self.data = json.load(f)

    @property
    def parsed_data(self):
        return self.data


class XMLDataExtractor:

    def __init__(self, filepath):
        self.tree =  etree.parse(filepath)

    @property
    def parsed_data(self):
        return self.tree


def dataextraction_factory(filepath):
    if filepath.endswith('json'):
        extractor = JSONDataExtractor
    elif filepath.endswith('xml'):
        extractor = XMLDataExtractor
    else:
        raise ValueError('Cannot extract data from {}'.format(filepath))
    return extractor(filepath)


def extract_data_from(filepath):
    factory_obj = None
    try:
        factory_obj = dataextraction_factory(filepath)
    except ValueError as e:
        print(e)
    return factory_obj


def main():
    sqlite_factory = extract_data_from('data/person.sq3')
    print()

    json_factory = extract_data_from('data/movies.json')
    json_data = json_factory.parsed_data
    print(f'Found: {len(json_data)} movies')
    for movie in json_data:
        print(f"Title: {movie['title']}")
        year = movie['year']
        if year:
            print(f"Year: {year}")
        director = movie['director']
        if director:
            print(f"Director: {director}")
        genre = movie['genre']
        if genre:
            print(f"Genre: {genre}")
        print()

    xml_factory = extract_data_from('data/person.xml')
    xml_data = xml_factory.parsed_data
    liars = xml_data.findall(f".//person[lastName='Liar']")
    print(f'found: {len(liars)} persons')
    for liar in liars:
        firstname = liar.find('firstName').text
        print(f'first name: {firstname}')
        lastname = liar.find('lastName').text
        print(f'last name: {lastname}')
        [print(f"phone number ({p.attrib['type']}):", p.text) 
              for p in liar.find('phoneNumbers')]
        print()
    print()


if __name__ == '__main__':
    main()

2. Abstract factory

  • factory method pattern의 일반화 버전. 객체 생성을 group화 시켜서 함.
  • 객체 생성 용이의 장점, memory 사용성 향상, performance 향상에 대한 장점이 있음.

ex1)

  • The 'FrogWorld' class is an abstract factory.
  • The 'WizardWorld' class is an abstract factory.
  • The 'GameEnvironment' class is the main entry point of our game. It accepts the factory as an input and uses it to create the world of the game.
# Frog game

class Frog:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name

    def interact_with(self, obstacle):
        act = obstacle.action()
        msg = f'{self} the Frog encounters {obstacle} and {act}!'
        print(msg)

class Bug:
    def __str__(self):
        return 'a bug'

    def action(self):
        return 'eats it'

class FrogWorld:
    def __init__(self, name):
        print(self)
        self.player_name = name

    def __str__(self):
        return '\n\n\t------ Frog World -------'

    def make_character(self):
        return Frog(self.player_name)

    def make_obstacle(self):
        return Bug()


# Wizard game

class Wizard:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name

    def interact_with(self, obstacle):
        act = obstacle.action()
        msg = f'{self} the Wizard battles against {obstacle} and {act}!'
        print(msg)

class Ork:
    def __str__(self):
        return 'an evil ork'

    def action(self):
        return 'kills it'

class WizardWorld:
    def __init__(self, name):
        print(self)
        self.player_name = name

    def __str__(self):
        return '\n\n\t------ Wizard World -------'

    def make_character(self):
        return Wizard(self.player_name)

    def make_obstacle(self):
        return Ork()

# Game environment
class GameEnvironment:
    def __init__(self, factory):
        self.hero = factory.make_character()
        self.obstacle = factory.make_obstacle()

    def play(self):
        self.hero.interact_with(self.obstacle)

def validate_age(name):
    try:
        age = input(f'Welcome {name}. How old are you? ')
        age = int(age)
    except ValueError as err:
        print(f"Age {age} is invalid, please try again...")
        return (False, age)
    return (True, age)

def main():
    name = input("Hello. What's your name? ")
    valid_input = False
    while not valid_input:
        valid_input, age = validate_age(name)
    game = FrogWorld if age < 18 else WizardWorld
    environment = GameEnvironment(game(name))
    environment.play()


if __name__ == '__main__':
    main()

Builder Pattern

- 여러가지 step을 걸쳐서 객체를 생성해야할 경우 Builder Pattern을 사용합니다.

  • factory pattern 과 builder pattern의 차이점
    • factory pattern은 객체 생성이 single step 이지만, builder pattern은 객체 생성이 multiple steps
    • factorpy pattern은 객체 생성을 즉각적으로 하지만, builder pattern은 the client code explicitly asks the director to return the final object when it needs it

2가지 중요요소가 있음
1. builder : (여러가지 step을 거쳐야하는) 복잡한 객체를 생성하는 역할
2. director : The component that controls the building process using a builder instance. (customize할 수있는 요소를 customize 해주는 역할 같다.)

ex1) 패스트푸드 식당에서 다양한 버거를 준비해야하고, 다양한 package를 해야하는 상황에 적용가능

  • In this case, the director is the cashier who gives instructions about what needs to be prepared to the crew, and the builder is the person from the crew that takes care of the specific order.

ex2) The django-widgy editor contains a page builder that can be used for creating HTML pages with different layouts.

ex3) 피자 레스토랑

  • 코드 구조
  • two builders : MargaritaBuilder, CreamyBaconBuilder
  • Each builder creates a 'Pizza' instance
  • prepare_dough() is just a wrapper to the prepare_dough() method of the Pizza class.
  • The director in this example is the waiter, which accepts a builder as a parameter and executes all the pizza-preparation steps in the right order.
from enum import Enum
import time

PizzaProgress = Enum('PizzaProgress', 'queued preparation baking ready')
PizzaDough = Enum('PizzaDough', 'thin thick')
PizzaSauce = Enum('PizzaSauce', 'tomato creme_fraiche')
PizzaTopping = Enum('PizzaTopping', 
                    'mozzarella double_mozzarella bacon ham mushrooms red_onion oregano')
STEP_DELAY = 3 # in seconds for the sake of the example


class Pizza:
    def __init__(self, name):
        self.name = name
        self.dough = None
        self.sauce = None
        self.topping = []

    def __str__(self):
        return self.name

    def prepare_dough(self, dough):
        self.dough = dough
        print(f'preparing the {self.dough.name} dough of your {self}...')
        time.sleep(STEP_DELAY)
        print(f'done with the {self.dough.name} dough')

        
class MargaritaBuilder:
    def __init__(self):
        self.pizza = Pizza('margarita')
        self.progress = PizzaProgress.queued
        self.baking_time = 5 # in seconds for the sake of the example

    def prepare_dough(self):
        self.progress = PizzaProgress.preparation
        self.pizza.prepare_dough(PizzaDough.thin)

    def add_sauce(self):
        print('adding the tomato sauce to your margarita...')
        self.pizza.sauce = PizzaSauce.tomato
        time.sleep(STEP_DELAY)
        print('done with the tomato sauce')

    def add_topping(self):
        topping_desc = 'double mozzarella, oregano'
        topping_items = (PizzaTopping.double_mozzarella, PizzaTopping.oregano)
        print(f'adding the topping ({topping_desc}) to your margarita')
        self.pizza.topping.append([t for t in topping_items])
        time.sleep(STEP_DELAY)
        print(f'done with the topping ({topping_desc})')

    def bake(self):
        self.progress = PizzaProgress.baking
        print(f'baking your margarita for {self.baking_time} seconds')
        time.sleep(self.baking_time)
        self.progress = PizzaProgress.ready
        print('your margarita is ready')

        
class CreamyBaconBuilder:
    def __init__(self):
        self.pizza = Pizza('creamy bacon')
        self.progress = PizzaProgress.queued
        self.baking_time = 7 # in seconds for the sake of the example

    def prepare_dough(self):
        self.progress = PizzaProgress.preparation
        self.pizza.prepare_dough(PizzaDough.thick)

    def add_sauce(self):
        print('adding the crème fraîche sauce to your creamy bacon')
        self.pizza.sauce = PizzaSauce.creme_fraiche
        time.sleep(STEP_DELAY)
        print('done with the crème fraîche sauce')

    def add_topping(self):
        topping_desc = 'mozzarella, bacon, ham, mushrooms, red onion, oregano'
        topping_items =  (PizzaTopping.mozzarella,
                          PizzaTopping.bacon,
                          PizzaTopping.ham,
                          PizzaTopping.mushrooms,
                          PizzaTopping.red_onion, 
                          PizzaTopping.oregano)
        print(f'adding the topping ({topping_desc}) to your creamy bacon')
        self.pizza.topping.append([t for t in topping_items])
        time.sleep(STEP_DELAY)
        print(f'done with the topping ({topping_desc})')

    def bake(self):
        self.progress = PizzaProgress.baking
        print(f'baking your creamy bacon for {self.baking_time} seconds')
        time.sleep(self.baking_time)
        self.progress = PizzaProgress.ready
        print('your creamy bacon is ready')

        
class Waiter:
    def __init__(self):
        self.builder = None

    def construct_pizza(self, builder):
        self.builder = builder
        steps = (builder.prepare_dough, 
                 builder.add_sauce, 
                 builder.add_topping, 
                 builder.bake)
        [step() for step in steps]

    @property
    def pizza(self):
        return self.builder.pizza

        
def validate_style(builders):
    try:
        input_msg = 'What pizza would you like, [m]argarita or [c]reamy bacon? '
        pizza_style = input(input_msg)
        builder = builders[pizza_style]()
        valid_input = True
    except KeyError:
        error_msg = 'Sorry, only margarita (key m) and creamy bacon (key c) are available'
        print(error_msg)
        return (False, None)
    return (True, builder)

    
def main():
    builders = dict(m=MargaritaBuilder, c=CreamyBaconBuilder)
    valid_input = False
    while not valid_input:
        valid_input, builder = validate_style(builders)
    print()
    waiter = Waiter()
    waiter.construct_pizza(builder)
    pizza = waiter.pizza
    print()
    print(f'Enjoy your {pizza}!')

    
if __name__ == '__main__':
    main()

Prototype pattern

- 클래스로 객체를 새로 찍어내는(생성)하는 것이 아니라 기존 객체를 복사해와서 새로운 객체를 생성해내는 패턴

  • In Python, this can be done using the copy.deepcopy() function.
  • 복잡한 객체 생성로직이 필요할때 사용하면 좋음.

ex1) 여러 웹사이트을, 형식은 같지만 이름, 도메인, 생성일자 등만을 바꿔서 새롭게 만들어낼때 사용.

import copy

class Website: 
    def __init__(self, name, domain, description, author, **kwargs): 
        '''Examples of optional attributes (kwargs): 
           category, creation_date, technologies, keywords.
        ''' 
        self.name = name 
        self.domain = domain 
        self.description = description
        self.author = author
        
        for key in kwargs:
            setattr(self, key, kwargs[key])
 
    def __str__(self): 
        summary = [f'Website "{self.name}"\n',] 
        
        infos = vars(self).items()
        ordered_infos = sorted(infos)
        for attr, val in ordered_infos:
            if attr == 'name':
                continue
            summary.append(f'{attr}: {val}\n')
            
        return ''.join(summary) 

        
class Prototype: 
    def __init__(self): 
        self.objects = dict() 
 
    def register(self, identifier, obj): 
        self.objects[identifier] = obj 
 
    def unregister(self, identifier): 
        del self.objects[identifier] 
 
    def clone(self, identifier, **attrs): 
        found = self.objects.get(identifier) 
        if not found: 
            raise ValueError(f'Incorrect object identifier: {identifier}') 
        obj = copy.deepcopy(found) 
        for key in attrs:
            setattr(obj, key, attrs[key])

        return obj
        
def main(): 
    keywords = ('python', 'data', 'apis', 'automation')
    site1 = Website('ContentGardening', 
            domain='contentgardening.com', 
            description='Automation and data-driven apps', 
            author='Kamon Ayeva',
            category='Blog',
            keywords=keywords)
 
    prototype = Prototype() 
    identifier = 'ka-cg-1' 
    prototype.register(identifier, site1)
    
    site2 = prototype.clone(identifier, 
            name='ContentGardeningPlayground',
            domain='play.contentgardening.com', 
            description='Experimentation for techniques featured on the blog', 
            category='Membership site',
            creation_date='2018-08-01') 
 
    for site in (site1, site2): 
        print(site)
    print(f'ID site1 : {id(site1)} != ID site2 : {id(site2)}')
    
if __name__ == '__main__': 
    main()

Singleton Pattern

- class가 단 1개의 instance만 가지도록 제한하는 패턴

  • Python 3 nowadays, the recommended technique we will choose is the metaclass technique.

ex1) concurrent access를 해야하는 상황에서 쓸 수 있음.
Controlling concurrent access to a shared resource. For example, the class managing the connection to a database.

ex2) the class at the core of the logging system or utility

ex3) URL fetcher

  • define the SingletonType class, with its special __call__() method
import urllib.parse
import urllib.request


class SingletonType(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(SingletonType, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class URLFetcher(metaclass=SingletonType):

    def __init__(self):
        self.urls = []
    
    def fetch(self, url):
        req = urllib.request.Request(url)
        with urllib.request.urlopen(req) as response:
            if response.code == 200:
                the_page = response.read()
                print(the_page)
        
                urls = self.urls
                urls.append(url)
                self.urls = urls
            
    def dump_url_registry(self):
        return ', '.join(self.urls)


def main():

    MY_URLS = ['http://www.voidspace.org.uk', 
               'http://google.com', 
               'http://python.org',
               'https://www.python.org/error',
               ]

    print(URLFetcher() is URLFetcher())

    fetcher = URLFetcher()
    for url in MY_URLS:
        try:
            fetcher.fetch(url)
        except Exception as e:
            print(e)
            
    print('-------')
    done_urls = fetcher.dump_url_registry()
    print(f'Done URLs: {done_urls}')
    
if __name__ == '__main__':
    main()

Adapter Pattern

- 재사용할려는 클래스가 제공하는 인터페이스와 클라이언트가 사용하는 인터페이스가 다를 때, 인터페이스를 연결해주는 패턴

  • helps us make two incompatible interfaces compatible

Decorator Pattern

- 함수에 확장된 기능을 부여하는 패턴, @decorator

  • The decorator pattern is generally used for extending the functionality of an object
  • Data validation
  • Caching
  • Logging
  • Monitoring
  • Debugging
  • Business rules
  • Encryption

ex1) Registering a function as an event subscriber

ex2) Protecting a method with a specific permission

ex3) Implementing the adapter pattern

ex4) controlling HTTP compression and caching

ex5) caching and memoization

import functools 
 
def memoize(fn): 
    cache = dict() 
 
    @functools.wraps(fn) 
    def memoizer(*args): 
        if args not in cache: 
            cache[args] = fn(*args) 
        return cache[args] 
 
    return memoizer
    
@memoize 
def number_sum(n): 
    '''Returns the sum of the first n numbers''' 
    assert(n >= 0), 'n must be >= 0' 
    if n == 0:
        return 0
    else:
        return n + number_sum(n-1)
 
@memoize 
def fibonacci(n): 
    '''Returns the suite of Fibonacci numbers''' 
    assert(n >= 0), 'n must be >= 0'
    if n in (0, 1):
        return n
    else:
        return fibonacci(n-1) + fibonacci(n-2)    
        
def main():
    from timeit import Timer

    to_execute = [
        (number_sum, 
         Timer('number_sum(300)', 'from __main__ import number_sum')),
        (fibonacci, 
         Timer('fibonacci(100)', 'from __main__ import fibonacci'))    
    ]
    
    for item in to_execute:
        fn = item[0]
        print(f'Function "{fn.__name__}": {fn.__doc__}')
        t = item[1]
        print(f'Time: {t.timeit()}')
        print()

if __name__ == '__main__': 
    main()

Bridge pattern

- 다양한 speacilzed 된 class들을 구현해야하는 상황일때, 추상화를 통해서 구현부를 분리

구성

  • An abstraction that applies to all the classes
  • A separate interface for the different objects involved

ex) 브릿지 패턴

  • Resource Content abstraction, called ResourceContent. (maintain a reference to the object which represents the Implementor)
  • define the interface for implementation classes that help fetch content, that is, the ResourceContentFetcher class. This concept is called the Implementor.
  • bridge pattern을 사용함으로써, you can extract content from different sources and integrate the results in the same data manipulation system or user interface.
import abc
import urllib.parse
import urllib.request


class ResourceContent:
    """
    Define the abstraction's interface.
    Maintain a reference to an object which represents the Implementor.
    """

    def __init__(self, imp):
        self._imp = imp

    def show_content(self, path):
        self._imp.fetch(path)


class ResourceContentFetcher(metaclass=abc.ABCMeta):
    """
    Define the interface (Implementor) for implementation classes that help fetch content.
    """
    
    @abc.abstractmethod
    def fetch(path):
        pass
        

class URLFetcher(ResourceContentFetcher):
    """
    Implement the Implementor interface and define its concrete
    implementation.
    """
    
    def fetch(self, path):
        # path is an URL
        req = urllib.request.Request(path)
        with urllib.request.urlopen(req) as response:
            if response.code == 200:
                the_page = response.read()
                print(the_page)
                        
                
class LocalFileFetcher(ResourceContentFetcher):
    """
    Implement the Implementor interface and define its concrete
    implementation.
    """

    def fetch(self, path):
        # path is the filepath to a text file
        with open(path) as f:
            print(f.read())
        
       
def main():
    url_fetcher = URLFetcher()
    iface = ResourceContent(url_fetcher)
    iface.show_content('http://python.org')

    print('===================')
    
    localfs_fetcher = LocalFileFetcher()
    iface = ResourceContent(localfs_fetcher)
    iface.show_content('file.txt')

    
if __name__ == "__main__":
    main()

Facade Pattern

- 복잡한 classes들과 instructions들을 single function을 통해 실행 가능토록 한다.

  • The facade design pattern helps us to hide the internal complexity of our systems and expose only what is necessary to the client through a simplified interface.

  • By introducing facade, the client code can use a system by simply calling a single method/function.

ex) CPU가 boot시킬때 encapsulates the whole procedure했기 때문에 client에게 복잡함을 노출시키지 않는다.

코드 구성

  • We need to subclass ABCMeta using the metaclass keyword.
  • We use the @abstractmethod decorator for stating which methods should be implemented (mandatory) by all subclasses of server.
from enum import Enum 
from abc import ABCMeta, abstractmethod 
 
State = Enum('State', 'new running sleeping restart zombie') 
 
class User: 
    pass 
 
class Process: 
    pass 
 
class File: 
    pass 
 
class Server(metaclass=ABCMeta): 
    @abstractmethod 
    def __init__(self): 
        pass 
 
    def __str__(self): 
        return self.name 
 
    @abstractmethod 
    def boot(self): 
        pass 
 
    @abstractmethod  
    def kill(self, restart=True): 
        pass 
 
class FileServer(Server): 
    def __init__(self): 
        '''actions required for initializing the file server''' 
        self.name = 'FileServer' 
        self.state = State.new 
 
    def boot(self): 
        print(f'booting the {self}') 
        '''actions required for booting the file server''' 
        self.state = State.running 
 
    def kill(self, restart=True): 
        print(f'Killing {self}') 
        '''actions required for killing the file server''' 
        self.state = State.restart if restart else State.zombie 
 
    def create_file(self, user, name, permissions): 
        '''check validity of permissions, user rights, etc.''' 
        print(f"trying to create the file '{name}' for user '{user}' with permissions {permissions}") 
 
class ProcessServer(Server): 
    def __init__(self): 
        '''actions required for initializing the process server''' 
        self.name = 'ProcessServer' 
        self.state = State.new 
 
    def boot(self): 
        print(f'booting the {self}') 
        '''actions required for booting the process server''' 
        self.state = State.running 
 
    def kill(self, restart=True): 
        print(f'Killing {self}') 
        '''actions required for killing the process server''' 
        self.state = State.restart if restart else State.zombie 
 
    def create_process(self, user, name): 
        '''check user rights, generate PID, etc.''' 
        print(f"trying to create the process '{name}' for user '{user}'") 
 
class WindowServer: 
    pass 
 
class NetworkServer: 
    pass 
 
class OperatingSystem: 
    '''The Facade''' 
    def __init__(self): 
        self.fs = FileServer() 
        self.ps = ProcessServer() 
 
    def start(self): 
        [i.boot() for i in (self.fs, self.ps)] 
 
    def create_file(self, user, name, permissions): 
        return self.fs.create_file(user, name, permissions) 
 
    def create_process(self, user, name): 
        return self.ps.create_process(user, name) 
 
def main(): 
    os = OperatingSystem() 
    os.start()  
    os.create_file('foo', 'hello', '-rw-r-r') 
    os.create_process('bar', 'ls /tmp') 
 
if __name__ == '__main__': 
    main()

Flyweight pattern

- 다수의 개체의 공통적인 속성을 관리하여 메모리를 아끼는 패턴이다. 즉, 매우 많은 객체들을 생성해야할 때 사용하는 패턴이자, 그 많은 객체를 저장하기에는 메모리가 너무 많이 소모될때 사용해야하는 패턴

  • to minimize memory usage and improve performance by introducing data sharing between similar objects
  • Flyweight is an OOP-specific optimization design pattern that focuses on sharing object data.

ex1) 게임상에서, 모든 유저가 same team에 있고, common action이 있고 (jump, duck 등등) 이때 사용 가능

ex2) caching

ex3) The Exaile music player uses flyweight to reuse objects (in this case, music tracks) that are identified by the same URL.

ex4) 자동차

코드 구성

  • Notice that pool is a class attribute (a variable shared by all instances).
  • Using the __new__() special method, which is called before __init__(), we are converting the Car class to a metaclass that supports self-references. This means that cls references the Car class.
import random 
from enum import Enum 
 
CarType = Enum('CarType', 'subcompact compact suv') 
 
class Car: 
    pool = dict() 
 
    def __new__(cls, car_type): 
        obj = cls.pool.get(car_type, None) 
        if not obj: 
            obj = object.__new__(cls) 
            cls.pool[car_type] = obj 
            obj.car_type = car_type 
        return obj 
 
    def render(self, color, x, y):
        type = self.car_type
        msg = f'render a car of type {type} and color {color} at ({x}, {y})'
        print(msg)
 
def main(): 
    rnd = random.Random() 
    #age_min, age_max = 1, 30    # in years 
    colors = 'white black silver gray red blue brown beige yellow green'.split()
    min_point, max_point = 0, 100 
    car_counter = 0 
 
    for _ in range(10): 
        c1 = Car(CarType.subcompact) 
        c1.render(random.choice(colors), 
                  rnd.randint(min_point, max_point), 
                  rnd.randint(min_point, max_point)) 
        car_counter += 1 
 
    for _ in range(3): 
        c2 = Car(CarType.compact) 
        c2.render(random.choice(colors), 
                  rnd.randint(min_point, max_point), 
                  rnd.randint(min_point, max_point)) 
        car_counter += 1 
 
    for _ in range(5): 
        c3 = Car(CarType.suv) 
        c3.render(random.choice(colors), 
                  rnd.randint(min_point, max_point), 
                  rnd.randint(min_point, max_point)) 
        car_counter += 1 
 
    print(f'cars rendered: {car_counter}') 
    print(f'cars actually created: {len(Car.pool)}') 
 
    c4 = Car(CarType.subcompact) 
    c5 = Car(CarType.subcompact) 
    c6 = Car(CarType.suv) 
    print(f'{id(c4)} == {id(c5)}? {id(c4) == id(c5)}') 
    print(f'{id(c5)} == {id(c6)}? {id(c5) == id(c6)}') 

    
if __name__ == '__main__': 
    main()

Proxy pattern

- sensitive information 접근 통제

ex1) Introducing lazy initialization using a virtual proxy to create the objects only at the moment they are actually required can give us significant performance improvements. (마지막에 execute 되게 하는 방식)

ex2) user가 특정 권한을 가지고 있는지 체크할 때

class SensitiveInfo: 
    def __init__(self): 
        self.users = ['nick', 'tom', 'ben', 'mike'] 
 
    def read(self): 
        nb = len(self.users)
        print(f"There are {nb} users: {' '.join(self.users)}") 
 
    def add(self, user): 
        self.users.append(user) 
        print(f'Added user {user}') 
 
class Info:  
    '''protection proxy to SensitiveInfo''' 
 
    def __init__(self): 
        self.protected = SensitiveInfo() 
        self.secret = '0xdeadbeef' 
 
    def read(self): 
        self.protected.read() 
 
    def add(self, user): 
        sec = input('what is the secret? ') 
        self.protected.add(user) if sec == self.secret else print("That's wrong!") 
 
def main(): 
    info = Info() 
 
    while True: 
        print('1. read list |==| 2. add user |==| 3. quit') 
        key = input('choose option: ') 
        if key == '1': 
            info.read() 
        elif key == '2': 
            name = input('choose username: ') 
            info.add(name) 
        elif key == '3': 
            exit() 
        else: 
            print(f'unknown option: {key}') 
 
if __name__ == '__main__': 
    main()

Observer pattern

- a publish-subscribe relationship

  • The key point in this case is that multiple listeners (observers) can be attached to a single event (publisher).

ex1) RabbitMQ library, Several messaging protocols are supported, such as HTTP and AMQP. RabbitMQ can be used in a Python application to implement a publish-subscribe pattern

ex2) news feed(RSS)

ex3) Event-driven systems


State pattern

states 와 transitions 두가지 상태를 지닌 패턴

  • In an event-driven system, the transition from one state to another triggers an event/message.

ex1)

  • Reject our selection because the product we requested is out of stock
  • Reject our selection because the amount of money we inserted was not sufficient
  • Deliver the product and give no change because we inserted the exact amount
  • Deliver the product and return the change

Strategy pattern

여러가지 알고리즘을 만들어놓고, input에 따라 제일 적합한 알고리즘을 선택하도록 하는 패턴


Microservices pattern

이 책에서는 추가적으로 microservices pattern에 대해서 설명하고 있는데, 간단히 정리하고 넘어가겠다.

1. Retry pattern

error가 발생했을 때 다시 retry시키도록 하는 패턴

2. Circuit Breaker pattern

failures가 특정 threshold에 도달하면 회로를 차단하여 장애전파를 막는 기법의 패턴

3. Cache-Aside pattern

자주 사용되는 정보를 cache화

4. Throttling pattern

요청 횟수에 제한을 둠으로써 서비스 resource를 보호하는 패턴

  • throttling is based on limiting the number of requests a user can send to a given web service in a given amount of time, in order to protect the resources of the service from being overused by some users.

🏝이 글이 도움이 되셨다면 추천 클릭을 부탁드립니다 :)

참고자료:

profile
데이터, 아키텍처, 클라우드와 함께 탱고춤을~!!

1개의 댓글

comment-user-thumbnail
2020년 3월 30일

좋은 정보 알아갑니다 감사합니다

답글 달기