ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 디자인 패턴
    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
Designed by Tistory.