-
디자인 패턴 (python)CS 지식/Common 2021. 3. 21. 22:43
참조:
www.fun-coding.org/PL&OOP2-2.html
gyoogle.dev/blog/guide/%EB%A9%B4%EC%A0%91%20%EC%A4%80%EB%B9%84.html
■ Design pattern 개요
(a) 목적
- 일종에 설계 기법이며, 설계 방법
- SW 재사용성, 호완성, 유지 보수성을 보장
(b) 특징
- idea에 가까움, 재사용/호환/유지보수 시 발생하는 문제를 해결/예방 하기 위한 패턴
(c) SOLID 원칙
- 정의 : 객체지향 설계 원칙
5 원칙
1) Single Responsibility Principle
하나의 클래스는 하나의 역할만을 수행
2) Open - close Principle
확장(상속)에는 열려있고, 수정에는 닫혀 있어야 함
3) Liskov Substitution Principle
자식이 부모의 자리에 항상 교체될 수 있어야 한다
== 부모의 property / method 모두를 자식이 가지고 있어야 함
4) Interface Segregation Principle
인터페이스가 잘 분리되어 클래스가 꼭 필요한 인터페이스만 구현이 되어있음
5) Dependency Inversion Property
상위 모듈이 하위 모둘에 의존하면 안됨
== 메타 클래스 / 추상화 클래스에 의존(필요하면), 추상화 자체는 세부 사항에 의존하면 안됨
(d) 분류 3 종류
- 3가지 방식으로 객체를 분류할 수 있음
- 생.구.행
1) 생성 패턴 : 객체의 생성 방식 결정
> DBConnection을 관리하는 Instance를 하나만 만들 수 있도록 제한하여, 불필요한 연결을 막음
== singletone creation
2) 구조 패턴 : 객체 to 객체, 이 사이의 관계를 조직> 2개의 인터페이스가 서로 호환이 되지 않을 때, 둘을 연결해주기 위해서
새로운 클래스를 만들어서 연결
== adapter pattern
3) 행위 패턴 : 객체의 행위를 조직, 관리, 연합
> 하위 클래스에서 구현해야 하는 함수 및 알고리즘들을 미리 선언하여, 상속시 이를 필수로 구현
== abstract class (python metaclass에서 abstract 작성)
추가 참조 : brownbears.tistory.com/485?category=378797
디자인 패턴의 종류
객체 지향 언어의 디자인 패턴은 세 가지
- Creational
- Structural
- Behavioural
Creational
Creational 디자인 패턴은 클래스에서 객체를 구성하는 방법과 관련이 있습니다. 객체를 새로 생성할 때, 인스턴스를 아무렇게 생성하면 코드를 파악하기 어렵게 만들 수 있습니다. Creational 디자인 패턴은 객체 생성에 대해 캡슐화하는 방법을 서술합니다.
- Builder Pattern
- Prototype Pattern
- Singleton Pattern
- Abstract Factory Pattern
Structural
Structural 디자인 패턴은 클래스 구성에 관련
- Adapter Pattern
- Bridge Pattern
- Composite Pattern
- Decorator Pattern
- Facade Pattern
- Flyweight Pattern
- Proxy Pattern
Behavioural
Behavioural 디자인 패턴은 클래스와 객체 간의 상호 작용에 관한 내용을 포함
- Interpreter Pattern
- Template Pattern
- Chain of Responsibility Pattern
- Command Pattern
- Iterator Pattern
- Mediator Pattern
- Memento Pattern
- Observer Pattern
- State Pattern
- Strategy Pattern
- Visitor Pattern
■ Adapter pattern
1) 정의 :
클래스를 바로 사용할 수 없는 경우 (ex 3rd party / 수정 불가)
중간에서 변환 역할을 해주는 클라스를 만들 때 adapter pattern
2) 사용 방법
- 상속
- 호환되지 않는 interface를 보유한 class도 변경 없이 활용 가능
(from SOLID : O / I)
- 3rd party 불변 class의 interface 변경 시에도, adaptee(client) 변경 없이 어댑터만 수정하면 됨
3) 예시
# https://hoony-gunputer.tistory.com/entry/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-Adapter-Pattern # Banner(기존의 클래스) class Banner: def __init__(self, name): self.name = name def printAster(self): print("*"+self.name+"*", end="\n") def printParen(self): print("(" + self.name + ")", end="\n") # Printer ## 행위 패턴, 반드시 가져야 하는 것 가짐 import abc class Printer: __metaclass__ = abc.ABCMeta @abc.abstractmethod def print_strong(self): pass @abc.abstractmethod def print_week(self): pass # PrintAdapter from Printer import Printer class PrintAdapter(Printer): def __init__(self, banner): self.banner = banner # 상속 받지 않고, 프로퍼티로 가져옴 def print_strong(self): self.banner.printAster() def print_week(self): self.banner.printParen()
from PrintAdapter import PrintAdapter from Banner import Banner if __name__ == '__main__': printer = PrintAdapter(Banner("Hi Hoony's Blog")) printer.print_strong() printer.print_week()
■ Factory pattern
1) 정의 :
객체 생성을 간단하게 하기 위하여 사용,
클래스의 객체(instance) 만들 때 subclass에서 만들게 하는 방법
→ 클래스 간의 결합도를 낮추기 위한 방식
→ ex) 결합도 높을 경우 : 하나의 클래스 오류/업데이트 시 연관된 클래스 모두를 손보거나 이해할 필요가 높은 경우
→ ex) 결합도 낮을 경우 : 하나의 클래스가 오류가 났을 때는 그 클래스만 고치면 되고 관련된 이전의 클래스까지 신경 쓸 필요가 없음
2) 종류
(a) Factory Method
> input에 따라 객체 생성이 달라지는 방식
- 예시 :
ex1) 장고에서 forms/models 상속받고, 다양한 field의 객체를 생성함
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함
(b) Abstract Factory Method
> 여러 객체 생성을 연관된 group으로 묶어서 객체를 생성
3) 예시
# 상세 클래스 만들기 class AndroidSmartphone: def send(self, message): print ("send a message via Android platform") class WindowsSmartphone: def send(self, message): print ("send a message via Window Mobile platform") class iOSSmartphone: def send(self, message): print ("send a message via iOS platform") # 팩토리 클래스 만들기 class SmartphoneFactory(object): def __init__(self): pass def create_smartphone(self, devicetype): if devicetype == 'android': smartphone = AndroidSmartphone() # <-- 실제 객체를 팩토리 클래스 안에서 만든다. elif devicetype == 'window': smartphone = WindowsSmartphone() # <-- 실제 객체를 팩토리 클래스 안에서 만든다. elif devicetype == 'ios': smartphone = iOSSmartphone() # <-- 실제 객체를 팩토리 클래스 안에서 만든다. return smartphone
■ Singleton pattern
1) 정의 :
애플리케이션이 시작될 때, 어떤 클래스가 최초 한 번만 메모리를 할당(static)하고
해당 메모리에 인스턴스를 만들어 사용하는 패턴, 메모리가 절약 됨
- '하나'의 인스턴스만 생성하여 사용하는 디자인 패턴
- 생성자가 여러번 호출되도, 실제로 생성되는 객체는 하나, 이후 생성자 호출은 이미 생성한 객체를 반환
- 싱글톤으로 구현한 인스턴스는 '전역'이므로, 다른 클래스의 인스턴스들이 데이터를 공유하는 것이 가능
> 데이터베이스 : 커넥션풀, 스레드풀, 캐시, 로그 기록 객체 등
> 안드로이드 앱 : 각 액티비티 또는 클래스마다 주요 클래스들 싱글톤 클래스를 만들어 어디서든 접근하도록 설계
> 인스턴스가 절대적으로 한 개만 존재하는 것을 보증하고 싶을 때※ 단점
> 싱글톤 인스턴스가 혼자 너무 많은 일을 ...OR.... 많은 데이터를 공유
→ 클래스들 간의 결합도↑, SOLID의 O 원칙(open-close) 안됨
> 멀티 스레드 환경
→ 동기화 처리를 하지 않았을 때, 인스턴스가 2개가 생성되는 문제
2) 예시
# 1. 싱글톤 클래스 만들기 class Singleton(type): # Type을 상속받음 __instances = {} # 클래스의 인스턴스를 저장할 속성 # 클래스로 인스턴스를 만들 때 호출되는 메서드 def __call__(cls, *args, **kwargs): # 클래스로 인스턴스를 생성하지 않았는지 확인 if cls not in cls.__instances: # 생성하지 않았으면 인스턴스를 생성하여 해당 클래스 사전에 저장 cls.__instances[cls] = super().__call__(*args, **kwargs) # 클래스로 인스턴스를 생성했으면 인스턴스 반환 return cls.__instances[cls]
# 2. 싱글톤 클래스 만들기 class PrintObject(metaclass=Singleton): # 메타클래스로 Singleton을 지정 def __init__(self): print("This is called by super().__call__")
■ Template method pattern
1) 정의 :
추상클래스를 통해 프로그램 큰 흐름을 제공해주고 이를 상속하는 클래스들이 구체적으로 구현하는 패턴
- 하나의 template을 공유 가능한 다중 class 들을 상속해 사용할 때, 유용한 방법
- abstract 클래스에서 무조건 가져야 하는 매써드를 설정한 후 세부 detail은 상속된 class 에서 설정
2) 예시
# Abstract Class import abc class AbstractPrint: __metaclass__ = abc.ABCMeta @abc.abstractmethod def open(self): pass @abc.abstractmethod def print_input(self): pass @abc.abstractmethod def close(self): pass def display(self): # 여기서 이미 큰 흐름을 모두 만듦 self.open() for i in range(0, 5): self.print_input() self.close() ## sub-class / concrete class ## from AbstractPrint import AbstractPrint # if needed class StringDisplay(AbstractPrint): def __init__(self, input_string): self.output_string = input_string self.width = len(input_string) def open(self): self.printLine() def print_input(self): print("|" + self.output_string + "|") def close(self): self.printLine() def printLine(self): # 상속/concrete 에서 detail 설정 result = "+" for i in range(0, self.width): result += "-" result += "+" print(result, end="\n") if __name__ == "__main__": hi = StringDisplay("hi") hi.display() >>> +--+ |hi| |hi| |hi| |hi| |hi| +--+
■ Observer pattern
1) 정의 :
상태를 가지고 있는 주체 객체 & 상태의 변경을 알아야 하는 관찰 객체
- 관련된 다른 객체들에게 상태 변경을 통보
- 비관찰자(관련된 다른 객체)들에게 관찰자(해당 객체)의 특정 이벤트 발생을 자동으로 모두 전달
> 잡지사 : 구독자
> 우유배달업체 : 고객- interface를 통해서 Observer class에 상태 변경을 알려주고, 안의 state를 관리하는 방식
2) 예시 :
- 추가 참조 : hoony-gunputer.tistory.com/entry/Observer-%ED%8C%A8%ED%84%B4?category=766290
## SMS / Email / Push 로 각각 알려야 하는 경우 class Observer: def __init__(self): self.observers = list() self.msg = str() def notify(self, event_data): for observer in self.observers: observer.notify(event_data) def register(self, observer): self.observers.append(observer) def unregister(self, observer): self.observers.remove(observer) class SMSNotifier: def notify(self, event_data): print(event_data, 'received..') print('send sms') class EmailNotifier: def notify(self, event_data): print(event_data, 'received..') print('send email') class PushNotifier: def notify(self, event_data): print(event_data, 'received..') print('send push notification') notifier = Observer() sms_notifier = SMSNotifier() email_notifier = EmailNotifier() push_notifier = PushNotifier() notifier.register(sms_notifier) notifier.register(email_notifier) notifier.register(push_notifier) # observer에 직접적으로 상태 변화를 알려주는 행위 ## 이후 동작은 해당 연관된 비관찰자들에게 자동으로 매써드 호출로 한번에 작업 수행 notifier.notify('user activation event') >>> # 결과 user activation event received.. send sms user activation event received.. send email user activation event received.. send push notification
■ Strategy pattern
1) 정의 :
어떤 동작을 하는 로직을 정의하고, 이것들을 하나로 묶어(캡슐화) 관리하는 패턴
- 새로운 로직을 추가하거나 변경할 때, 한번에 효율적으로 변경이 가능
2) 예시
> 참조 : hulk89.github.io/python%20design%20pattern/2016/11/08/strategy/
# FlyBehavior.py from abc import ABCMeta, abstractmethod class FlyBehavior: __metaclass__ = ABCMeta @abstractmethod def fly(self): pass class FlyWithWings(FlyBehavior): def fly(self): print("I can fly!") class FlyNoWay(FlyBehavior): def fly(self): print("I can't fly.....:(") # QuackBehavior.py from abc import ABCMeta, abstractmethod class QuackBehavior: __metaclass__ = ABCMeta @abstractmethod def quack(self): pass class Quack(QuackBehavior): def quack(self): print("Quack! Quack!") class Mute(QuackBehavior): def quack(self): print(".....") # Duck.py from abc import ABCMeta, abstractmethod class Duck: __metaclass__ = ABCMeta def __init__(self): self._flyBehavior = None self._quackBehavior = None @abstractmethod def print(self): pass def performFly(self): self._flyBehavior.fly() def performQuack(self): self._quackBehavior.quack() def setFlyBehavior(self, flyBehavior): self._flyBehavior = flyBehavior def setQuackBehavior(self, quackBehavior): self._quackBehavior = quackBehavior # RubberDuck.py from Duck import Duck from FlyBehavior import FlyNoWay from QuackBehavior import Quack class RubberDuck(Duck): def __init__(self): self._flyBehavior = FlyNoWay() self._quackBehavior = Quack() def print(self): print("I'm rubber duck!") def performFly(self): self._flyBehavior.fly() def performQuack(self): self._quackBehavior.quack() # RealDuck.py from Duck import Duck from FlyBehavior import FlyWithWings from QuackBehavior import Quack class RealDuck(Duck): def __init__(self): self._flyBehavior = FlyWithWings() self._quackBehavior = Quack() def print(self): print("I'm real duck!") def performFly(self): self._flyBehavior.fly() def performQuack(self): self._quackBehavior.quack()
# Test code from RubberDuck import RubberDuck from RealDuck import RealDuck from FlyBehavior import FlyNoWay if __name__ == "__main__": a = RealDuck() a.print() a.performFly() a.performQuack() a.setFlyBehavior(FlyNoWay()) # 동적으로 interface를 변경가능!! print("I'm wounded!") a.performFly() a.performQuack() print("====================") b = RubberDuck() b.print() b.performFly() b.performQuack() >>> # 결과 $ python main.py I'm real duck! I can fly! Quack! Quack! I'm wounded! I can't fly.....:( Quack! Quack! ==================== I'm rubber duck! I can't fly.....:( Quack! Quack!
■ Builder pattern
1) 정의 :
커다란 구조물(클래스) 등을 만들 때 차근차근히 쌓아 올리는 패턴을 사용하는 방식
- 다른 객체를 조합한 복합 객체(complex object)를 생성하기 위해 고안
- 복잡한 객체를 빌드하는 프로세스를 캡슐화 또는 숨기는 작업을 하고, 생성하는 객체와 해당 구조를 표현하는 객체를 분리
> 생성자 인자를 바깥에서 전달하지 않고, 생성자 매써드 안에 포함시켜 캡슐화를 하는 것
> 파이썬은 kwarg로 강제할당을 하므로, 간단히 해결되지만
다른 언어 : builder class를 가져오고, 상위 객체의 생성자에서 값을 받지 않고 builder class를 생성하여 해결
2) 예시 :
# Builder.py, 행위 패턴 import abc class Builder: __metaclass__ = abc.ABCMeta def setTitle(self, title): pass def setText(self, text): pass def setItems(self, items): pass def close(self): pass def getResult(self): pass """ 이 예제의 경우, builder를 behavior 패턴으로 빼놓았는데, builder class의 프로퍼티를 Director가 표현 class 인 경우, Director의 생성자에서는 아무 일을 하지 않고 constructore 매써드를 정의하여 빌더의 property를 설정하고 접근할 수 있게 함 """ # Director.py class Director: def __init__(self, builder): self.builder = builder def constructor(self): self.builder.setTitle("Greet") self.builder.setText("아침에는") self.builder.setItems(["goodMorning", "좋은 아침입니다.", "좋아"]) self.builder.setText("저녁에는") self.builder.setItems(["goodEvening", "멋진 저녁입니다.", "좋아"]) self.builder.close() # HTMLDocumnet.py from Builder import Builder class HtmlDocuments(Builder): def __init__(self): self.str = "" def setTitle(self, title): self.str += "<h1>"+title+"</h1>\n" def setText(self, text): self.str += "<p>" + text + "</p>\n" def setItems(self, items): self.str+="<ul>" for i, item in enumerate(items): self.str += "<li>"+str(i)+". "+item+"</li>\n" self.str += "</ul>\n" def close(self): super().close() def getResult(self): return self.str # textDocument.py from Builder import Builder class TextDocuments(Builder): def __init(self): self.str = "" def setTitle(self, title): self.str+="----------------------------" self.str += title+"\n" def setText(self, text): self.str += text + "\n" def setItems(self, items): for i, item in enumerate(items): self.str += "<li>"+i+". "+item+"</li>\n" def close(self): super().close() def getResult(self): return self.str # Main.py from Director import Director from HtmlDocuments import HtmlDocuments from TextDocuments import TextDocuments def isHtml(order): return order == "html" def makeDocumeents(documents): director = Director(documents) director.constructor() print(documents.getResult()) if __name__ == "__main__": order = input() if isHtml(order): # factory pattern in the __main__ thread htmlDocuments = HtmlDocuments() makeDocumeents(htmlDocuments) else: TextDocuments = TextDocuments() makeDocumeents(TextDocuments)
■ Prototype pattern
1) 정의 :
기존의 객체를 복사하여 새로운 객체를 만들고 복사한 객체를 변경하여 사용
- 추가 참조 : brownbears.tistory.com/488
mino-park7.github.io/python%20study/2018/08/15/python-design-pattern-prototype-pattern/
- 때때로 새로운 객체를 만드는 것이 기존 객체를 복사하는 것 보다 비용이 더 많이 들 수 있기 때문
2) 예시:
(1) 번
import copy class Score: def __init__(self, x, y): self.x = x self.y = y self.connect = False def connection(self): self.connect = True print('connection') score1 = Score(10, 20) score1.connection() score2 = copy.deepcopy(score1) score2.x = 90 score2.y = 100 print(score1.connect == score2.connect) # True
(2) 번
위의 Point class가 있을 경우 새로운 객체를 만드는 방법은 7가지
1) Point 클래스 객체를 생성자를 활용해 생성 (정적생성)
2) eval() 을 사용해 클래스 이름을 매개변수로 전달 (동적생성)
3) gettattr() 을 사용해 클래스 이름을 매개변수로 전달 (동적생성)
4) globals() 을 사용해 클래스 이름을 매개변수로 전달 (동적생성) - point3 과 동일한 방식
5) point5는 클래스 객체와 필요한 인자를 받는 제네릭 함수를 통해 생성
6) 고전적인 프로토타입 접근법을 생성
- 먼저 copy.deepcopy()를 사용해 기존 객체를 복제
- 복제된 객체를 초기화하거나 애트리뷰트를 재설정
7) 파이썬은 어떤 객체의 클래스 객체 (__class__)에 접근가능
- 기존 객체를 복제한 다음 복제본을 수정하는 대신
- 클래스 객체를 활용해 바로 새로운 객체를 만들 수 있다.반응형'CS 지식 > Common' 카테고리의 다른 글
CS - 공부 참조 (0) 2021.05.23 절차지향 vs 객체지향(객체지향 3대요소) (0) 2021.04.30 함수형 프로그래밍 vs 절차형 프로그래밍 (0) 2021.04.26 Overriding / Overloading 차이 (0) 2021.04.15