ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [파이썬] 전략 패턴, 의존성 주입
    Python/이것저것 파이썬 2022. 8. 16. 10:26
    반응형

    0.

    '기차는 레일로 이동하고,
    버스는 차로로 이동한다.'
    는 결과를 만들기 위해
    다음과 같이 코드를 작성했습니다.  

    from abc import ABCMeta, abstractmethod
    
    
    class Vehicle(metaclass=ABCMeta):
        @abstractmethod
        def move(self):
            pass
    
    
    class Train(Vehicle):
        def move(self):
            print("레일로 이동")
    
    
    class Bus(Vehicle):
        def move(self):
            print("차로로 이동")
    
    
    train = Train()
    bus = Bus()
    
    train.move()
    bus.move()

    그런데 버스가 하늘을 나는 시대가 된다면...
    다음과 같이 수정을 해야겠죠?

    class Bus(Transport):
        def move(self):
            print("하늘로 이동")

    이렇게 수정하지 않고
    시스템을 확장해서
    처리할 방법은 없을까요?

    이런 경우에 전략 패턴을 사용합니다. 

    전략 패턴은
    여러 알고리듬을
    각각의 클래스 안에 넣어서
    교체하기 쉽도록 만들어진
    패턴입니다. 

    위 예제에서는 이동 방법이
    알고리듬에 해당되겠죠. 

     

    1.

    각 운송 수단(Vehicle)에 코딩되어 있던 이동 방법(move())을
    TransportStrategy(운송 전략)이라는 클래스로 묶어내고, 
    SkyTransport 클래스를 추가합니다. 

    from abc import ABCMeta, abstractmethod
    
    
    class TransportStrategy(metaclass=ABCMeta):
        @abstractmethod
        def move(self):
            pass
    
    
    class RailRoadTransport(TransportStrategy):
        def move(self):
            print("레일로 이동")
    
    
    class RoadTransport(TransportStrategy):
        def move(self):
            print("도로로 이동")
    
    
    class SkyTransport(TransportStrategy):
        def move(self):
            print("하늘로 이동")

     

    2.

    각 운동 수단의 move() 메서드를
    Vehicle(운송수단) 클래스에 묶어 내고
    운송 전략을 설정하는 메서드
    (set_transport_strategy())를 만듭니다. 

    class Vehicle:
        def __init__(self):
            self._transport_strategy = None
    
        def set_transport_strategy(self, transport_strategy: TransportStrategy):
            self._transport_strategy = transport_strategy
    
        def move(self):
            self._transport_strategy.move()
    
    
    class Train(Vehicle):
        pass
    
    
    class Bus(Vehicle):
        pass

     

    3.

    전체 코드

    from abc import ABCMeta, abstractmethod
    
    
    class TransportStrategy(metaclass=ABCMeta):
        @abstractmethod
        def move(self):
            pass
    
    
    class RailRoadTransport(TransportStrategy):
        def move(self):
            print("레일로 이동")
    
    
    class RoadTransport(TransportStrategy):
        def move(self):
            print("도로로 이동")
    
    
    class SkyTransport(TransportStrategy):
        def move(self):
            print("하늘로 이동")
    
    
    class Vehicle:
        def __init__(self):
            self._transport_strategy = None
    
        def set_transport_strategy(self, transport_strategy: TransportStrategy):
            self._transport_strategy = transport_strategy
    
        def move(self):
            self._transport_strategy.move()
    
    
    class Train(Vehicle):
        pass
    
    
    class Bus(Vehicle):
        pass
    
    
    train = Train()
    bus = Bus()
    
    train.set_transport_strategy(RailRoadTransport())
    bus.set_transport_strategy(RoadTransport())
    
    train.move()  # 레일로 이동
    bus.move()  # 도로로 이동
    
    bus.set_transport_strategy(SkyTransport())
    bus.move()  # 하늘로 이동

     

    4.

    전략 패턴 = 의존성 주입. 

    객체 지향 프로그래밍에서는,
    객체의 상호 작용을 통해 프로그램을 만듭니다.

    그렇기 때문에
    객체 간의 의존성이 발생할 수 밖에 없습니다. 

    의존성 주입은
    이 의존성을 약하게(=결합도를 느슨하게) 만들어
    프로그램을 유연하게 만듭니다. 

    의존성 주입이란,
    구체적인 클래스에 의존하지 않고,
    인터페이스에 의존하도록 만드는 것입니다. 

    위 코드에서...

    Vehicle 클래스가 TransportStrategy 클래스에 의존성이 있지만, 
    Vehicle 클래스는 TransportStrategy의 구체적인 클래스에 의존하지 않고,  
    TransportStrategy 클래스의 인터페이스(파이썬에서는 추상 클래스)에 의존합니다. 

    다른 표현으론....  

    외부에서 Vehicle과 
    구체적인 TransportStrategy
    (RailRoadTransport, RoadTransport, SkyTransport)
    의 의존 관계를 설정할 수 있도록 만드는 것(=의존의 역전)이
    의존성 주입입니다.  

     

    5.

    장점

    기존 클래스의 내부를 수정하지 않아도 됩니다,
    클래스의 추가로 기능을 추가할 수 있습니다. 

    => 최소한의 수정으로 기능을 추가할 수 있습니다

    => 유연합니다. 

    주된 제어 흐름에서 세부적인 요소까지 설정할 수 있습니다. 

    => 가독성이 좋아집니다.

    ==> 유지보수가 편리해집니다. 

     

    ------------------------------------------------

    엄격한 객체 지향 방법론은 아니지만,
    제 개인 프로젝트라면 이 정도로...

    from typing import Callable  # 함수의 타입 어노테이션(type annotation)을 위해 
    
    
    class TransportStrategy:
        @staticmethod
        def rail():
            print("레일로 이동")
    
        @staticmethod
        def road():
            print("도로로 이동")
    
        @staticmethod
        def sky():
            print("하늘로 이동")
    
    
    class Vehicle:
        def __init__(self, transport_strategy_method: Callable):  # 함수도 일등 시민
            self._move = transport_strategy_method
    
        def move(self):
            self._move()
    
    
    class Train(Vehicle):
        pass
    
    
    class Bus(Vehicle):
        pass
    
    
    train = Train(TransportStrategy.rail)  # 함수도 일등 시민
    bus = Bus(TransportStrategy.road)
    
    train.move()
    bus.move()
    
    bus = Bus(TransportStrategy.sky)
    bus.move()

    전략 패턴에서는
    다양한 전략(알고리듬)을 인수로 전달하여,
    다양한 전략을 선택할 수 있도록 합니다. 

    자바는 함수를 인수로 전달할 수 없기 때문에,
    (알고리듬을 담고 있는) 함수를 담고 있는 클래스를
    인수로 전달합니다. 

    파이썬에서는 함수도 인수로 사용할 수 있습니다. 
    따라서,
    알고리듬을 담고 있는 함수를 인수로 직접 전달하면 
    좀 더 단순하게 전략 패턴을 이용할 수 있습니다. 

    반응형
Designed by Tistory.