디자인 패턴

파이썬 프로그래밍에서 디자인 패턴(Design Pattern)은 객체지향을 설계하는 과정에서 발생하는 문제를 해결하기 위해 사용되는 패턴이다. 


데코레이터 패턴

데코레이터(decorator) 패턴@를 사용해 함수나 메서드를 정의하는 방법이다. 

def decorator(func):
    def wrapper(*args, **kwargs):
        print("start")
        func(*args, **kwargs) # test(*args, **kwargs)
        print("end")

    return wrapper


@decorator
def test():
    print("Test Decorator")

test()

>>> start
>>> Test Decorator
>>> end

위 예시의 경우, test라는 함수를 decorator 함수의 인자로 받아 사용하고 있다. 데커레이터를 사용하면 wrapper 함수를 계속해서 재사용할 수 있게 된다. 위 코드를 풀어쓰면 다음과 같다. 

def decorator():
    def wrapper(*args, **kwargs):
        print("start")
        print("Test Decorator")  # test()
        print("end")

    return wrapper


test = decorator()
test()

@classmethod, @staticmethod

@classmethod는 cls라는 파라미터를 통해 클래스에 접근한다. 일반적인 인스턴스 메서드는 self를 통해 클래스 인스턴스를 받아오는 것과 다르게, 클래스 메서드cls를 통해 클래스 자체를 가져온다. 

@staticmethod정적메서드를 구현할 때 사용한다. 첫 번째 매개변수로 self를 받아오지 않기 때문에 클래스 속성에 접근할 수 없다. 비슷한 맥락의 메서드를 클래스로 묶어두지만, 독립적으로 관리하고 싶을 때 사용할 수 있다. 

class Main(object):
    
    @classmethod
    def class_method(cls):
        print("Class Method")

    @staticmethod
    def static_method():
        print("Static Method")


Main.class_method()
Main.static_method()

>>> Class Method
>>> Static Method

클래스 메서드와 인스턴스 메서드의 차이는 아래 예제를 통해 확인할 수 있다. 

class Main(object):
    _var = "A"

    def __init__(self):
        self._var = "B"

    def instance_method(self):
        print(self._var)

    @classmethod
    def class_method(cls):
        print(cls._var)


main = Main()
main.instance_method()
>>> B

Main.class_method()
>>> A

cls는 초기화되지 않은 <class '__main__.Main'>를 뜻하기 때문에 B가 아닌 A를 가져온다. (cls는 B에 접근할 수 없다.)


옵저버 패턴

옵저버(observer) 패턴은 하나의 객체가 일대다 관계를 가질 때, 종속된 하위 객체에 일괄적으로 내용을 전달하는 방식을 뜻한다. 

class Subject(object):
    def __init__(self):
        self._observers = set()

    def add_observer(self, new):
        self._observers.add(new)

    def update(self):
        for observer in self._observers:
            observer.plus_two()


class Observer(object):
    def __init__(self, var):
        self._var = var

    def plus_two(self):
        self._var += 2


three = Observer(3)
seven = Observer(7)

subject = Subject()
subject.add_observer(three)
subject.add_observer(seven)
subject.update()

print(three._var)
>>> 5
print(seven._var)
>>> 9

Observer의 add_two 메서드를 Subject의 update 메서드를 통해 한 번에 실행시킬 수 있다. 

@property 활용

아래 코드에서 get_var 메서드를 사용해 정보를 가져오고, set_var 메서드를 이용해 정보를 수정한다. 

class Test(object):
    def __init__(self):
        self._var = 0

    def get_var(self):
        # getter
        return self._var

    def set_var(self, new_var):
        # setter
        self._var = new_var


test = Test()
test.set_var(5)
var = test.get_var()
print(var)
>>> 5

하지만 @property를 활용하면 외부에서 get이나 set을 숨기고 사용할 수 있다.

class Test(object):
    def __init__(self):
        self._var = 0

    @property
    def var(self):
        return self._var

    @var.setter
    def var(self, new_var):
        self._var = new_var


test = Test()
test.var = 3
var = test.var
print(var)
>>> 3

@property를 이용해 값을 가져오고, @변수명. setter를 활용해 정보를 수정한다. 


싱글턴 패턴

싱글턴(singleton) 패턴은 전역에서 접근 가능한 하나의 객체만을 허용해 다른 값이 생성되어 충돌하는 것을 막는다. 아래 예시는 _instance를 하나만 생성하여 다른 인스턴스가 생성되는 것을 막는다. 

class SingleTon(object):
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super().__new__(cls)
        return cls._instance


class Var(SingleTon):
    def __init__(self, var):
        self._var = var


a = Var(5)
print(f"before b; a = {a._var}")

b = Var(10)
print(f"after b; a = {a._var}")
print(f"after b; b = {b._var}")

>>> before b; a = 5
>>> after b; a = 10
>>> after b; b = 10

a와 b는 별도로 정의한 인스턴스임에도 불구하고, a._var가 5로 생성되었지만 b가 생성되면서 a, b 모두 10으로 변경되었다. 

print(a is b)
print(a._instance)
print(b._instance)
>>> True
>>> <__main__.SingleTon object at 0x00000190834611C8>
>>> <__main__.SingleTon object at 0x00000190834611C8>

a is b가 True라는 것은 a. b 모두 같은 값을 참조하고 있다는 뜻이다. 실제로 확인해보면 _instance는 같은 메모리 주소를 사용하고 있다.