-
디자인 패턴Programming 2022. 12. 23. 17:47
* 헤드퍼스트 디자인패턴(개정판), Node.js 디자인 패턴 바이블 책을 보며 이해하고 작성, 참고 또는 직접 작성한 코드를 적어본다.
* 구상(Concrete) 클래스: new 키워드를 사용하여 인스턴스를 만드는 클래스(=구현 클래스)
* 추상(Abstract) 클래스: 하나의 추상 메소드를 포함하는 클래스로 이를 상속하는 자식 클래스는 추상 메소드를 구현해야함
* 인터페이스(Interface): 클래스의 공통된 기능을 정리한 명세서
* 컨택스트(Context): 사용자가 관심 있는 인터페이스를 정의/문제에 대한 정의,상황
옵저버
전략 패턴
- 특정 변경이 필요한 부분을 별도의 상호 교환 가능한 (전략) 객체로 추출하여 로직의 변경이 용이하도록 함
- 추출하여 합성하는 것이 포인트
- 해당 패턴을 알기전에 합성하는 부분에 대해서는 경험이 있었으나 요구상황에 따라 다르게 합성하는 것에 대한 경험은 없었고 사실 fly/flyable과 같이 나눈다는 것에 대한 개념은 그전엔 생각해보지 않았던 것 같음
from abc import * class AbstractAnimal(metaclass=ABCMeta): name = "name" weight = 0 height = 0 @abstractmethod def description(self): print('description') class Flyable(): def fly(self): print("fly") class NonFlyable(): def fly(self): print("can't fly") class Runable(): def run(self): print("run") class NonRunable(): def run(self): print("can't run") class Dog(AbstractAnimal, Runable, NonFlyable): def __init__(self, name, weight, height): self.name = name self.weight = weight self.height = height def description(self): print(f"dog description, name: {self.name}, weight: {self.weight}, height: {self.height}") Albi = Dog("Albi", 22, 100) Albi.description() Albi.run() Albi.fly() class Bird(AbstractAnimal, NonRunable, Flyable): def __init__(self, name, weight, height): self.name = name self.weight = weight self.height = height def description(self): print(f"bird description, name: {self.name}, weight: {self.weight}, height: {self.height}") Sevi = Bird("Sevi", 16, 60) Sevi.description() Sevi.run() Sevi.fly()상태 패턴
- 전략 패턴과 유사할수 있으나 대상 컴포넌트(=컨택스트)의 상태를 추출한 클래스에서 각 상태에 대응되는 작업을 수행함
- 컨택스트 메서드 내의 상태에 따른 조건문 코드를 없애고 상태 클래스로 모든 작업을 위임함
- 내부 상태가 변경할때 행동이 달라지게 되고 컨택스트는 그대로이나 바뀐 것 같은 결과를 얻음
from __future__ import annotations from abc import ABC, abstractmethod from time import time class FailsafeSocket: def __init__(self) -> None: self.queue = [] self.states = { "offline": Offline(self), "online": Online(self), "pending": Pending(self), } self.change_state("offline") def change_state(self, state): self.state = self.states[state] def send(self, data): self.state.send(data) class AbstractState(ABC): @abstractmethod def send(self) -> None: pass class Offline(AbstractState): def __init__(self, FailsafeSocket): self.socket = FailsafeSocket def send(self, data) -> None: print(f"{type(self).__name__} {data} not sent ") class Pending(AbstractState): def __init__(self, FailsafeSocket): self.socket = FailsafeSocket def send(self, data) -> None: self.socket.queue.append(data) print(f"{type(self).__name__} data {data} added to queue: {self.socket.queue}") class Online(AbstractState): def __init__(self, FailsafeSocket): self.socket = FailsafeSocket def send(self, data) -> None: if len(data) > 5: self.socket.change_state("offline") print(f"{type(self).__name__} too long data {data}, change to noline") self.socket.queue = [] else: self.socket.queue.append(data) print(f"{type(self).__name__} sent data: {self.socket.queue}") self.socket.queue = [] # 상태가 ON/OFF 일때 context의 send에서 분기가 있는 것이 아니라 해당 상태 객체에 정의된 메서드에 의해 할일을 수행 # 상태가 여러개라고 가저한다면 if 선언문을 제거할수 있고 # 상태의 변경에는 닫혀있지만 상태 확장에는 열려있음 if __name__ == "__main__": socket = FailsafeSocket() socket.send("1") socket.send("2") socket.send("3") socket.change_state("pending") socket.send("4") socket.send("5") socket.send("6") socket.change_state("online") socket.send("7") socket.send("8") socket.send("9") socket.send("11111111") socket.send("1") # socket1 = FailsafeSocket() # socket1.send("1") # socket1.send("2") # socket1.send("3") # socket1.change_state("pending") # socket1.send("4") # socket1.send("5") # socket1.send("6") # socket1.change_state("online") # socket1.send("7") # socket1.send("8") # socket1.send("9") # print(socket1 == socket) # false # print(socket.state.socket == socket1.state.socket) # false- 전략과 상태 패턴
- 전략 패턴은 어떤 전략 객체를 사용할지 클라이언트가 지정해주지만(=>처음 실행 시 객체 변경의 유연성)
- 상태 패턴은 행동을 상태 객체 내에 캡슐화하여 Context 내의 상태 객체를 바꾸는 것만으로 객체의 행동을 바꾼다.
템플릿 메서드 패턴
- 부모 클래스에서 추상 메서드를 포함한 일부 메서드들을 구현하고, 자식 클래스에서 요구사항에 따라 다른 메서드들(추상 메서드, 메서드 오버라이드(옵션))을 구현하여 특정 단계들만 확장하기 위함
- 상속을 기반을 함
- 클래스를 만들때 템플릿 메서드 패턴인지 모르고 이 패턴을 사용한 경험이 많이 있음
- 전략과 템플릿 메서드패턴
- 전략은 구상클래스들을 합성, 템플릿 메서드는 자식 클래스들을 확장하는 것에 초점
- 전략 패턴은은 객체 수준에서 작동하므로 런타임에 행동들을 전환, 템플릿 메서드는 클래스 수준에서 작동하므로 정적
싱글턴 패턴
- Python의 Flask 에서 app 이라는 객체를 바로 실행하는 것이 아니라 create_app()이라는 함수를 호출하여 app을 받도록 하는 부분에서 많이 사용했었고, DB Connection에 적용했던 경험이 있음
팩토리 패턴
빌더 패턴
- 객체 생성을 단순화하는 생성 디자인 패턴 -> 단계별 객체 생성하거나 복잡한 객체를 만들때 가독성 및 사용성이 향상됨
- 장점을 최대화할수 있는 경우는 인자가 목록이 길거나 복잡한 매개변수 입력을 사용하는 생성자가 있는 클래스
- 예를 들어 입력되던 파라미터가 변경이 필요할때 손쉽게 해결할 수 있음
- 파라미터 입력값이 많아질수록 복잡하고 순서를 외우야함
- 파이썬의 경우 키워드 파라미터가 존재해서 빌더 패턴의 장점이 큰 장점이 아니다. 바로 키워드를 지정해서 진행할 수 있고 이경우 순서또한 상관 없음
class UrlBuilder { setProtocol(protocol) { this.protocol = protocol return this } setHost(host) { this.host = host return this } setPort(port) { this.port = port return this } build() { return `${this.protocol}://${this.host}:${this.port}` } } const url = new UrlBuilder() .setProtocol('http') .setHost('localhost') .setPort('3000') .build() console.log(url) // http://localhost:3000커맨드 패턴
데코레이터 패턴
어댑터 패턴
- 특정 클래스 인터페이스를 클라이언트에서 요구하는 다른 인터페이스로 변환: 인터페이스가 호환되지 않아 사용할 수 없는 클래스를 사용할 수 있게 해준다.
퍼사드패턴
- 인터페이스를 단순하게 만들고 클라이언트와 구성 요소로 이루어진 서브시스템으로 분리함
- NestJS에서 Controller에서 여러 서비스 레이어들을 의존할때 이 수가 많아진다면 이를 분리하여 하나의 퍼사드레이어를 의존하고 퍼사드 레이어에서 기존 서비스들을 의존하게 하여 분리/단순하게 함
export class AggregationFacade { constructor( private readonly _userService: UserService, private readonly _orderService: OrderService, private readonly _paymentService: PaymentService, ) {} }어댑터 패턴과 퍼사드 패턴
- 둘다 여러 개의 클래스를 감쌀 수 있지만, 퍼사드는 인터페이스를 단순하게 만드는 용도이고 어댑터는 인터페이스를 다른 인터페이스를 변환함
....
'Programming' 카테고리의 다른 글
Random Id generator (0) 2022.12.30 [책] 함께 자라기 - 1장 (0) 2021.12.11 [TEST] 1. 테스트를 시작하게 된 계기 (0) 2021.10.23