[파이썬] 전략 패턴, 의존성 주입
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()
전략 패턴에서는
다양한 전략(알고리듬)을 인수로 전달하여,
다양한 전략을 선택할 수 있도록 합니다.
자바는 함수를 인수로 전달할 수 없기 때문에,
(알고리듬을 담고 있는) 함수를 담고 있는 클래스를
인수로 전달합니다.
파이썬에서는 함수도 인수로 사용할 수 있습니다.
따라서,
알고리듬을 담고 있는 함수를 인수로 직접 전달하면
좀 더 단순하게 전략 패턴을 이용할 수 있습니다.