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()

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

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

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

반응형