정책패턴이라고 하며, 여러 알고리즘이 교체가능한 패턴이며, 자주 사용되는 패턴 중 하나이다.
상위클래스나 인터페이스(자바)를 선언하고, 그것을 상속받거나 구현한 클래스가 정책클래스가 된다. 상황에 맞게 정책을 결정해서 사용할 수 있다.
context
실제 정책을 실해하는 부분이다. 화면이나 콘솔, 웹페이지 등이 될수 있다.
옆의 하나의 strategy를 갖게되고, strategy의 메서드를 호출한다.
strategy
strategy의 메서드는 실제 다양한 형태로 구현이 될 수 있다.
각 각을 구현한 클래스가 하위에 존재한다.
정책이 수행되어야 하는 기능들을 인터페이스로 선언한다.
결국 context는 어떤 strategy하위클래스가 수행될 것인지에 따라 정책이 선택된다.
면접을 위한 cs 전공지식 노트의 예에서는 카드를 이용해서 전략패턴에 대해서 설명했다.
웹페이지 상에서 상품을 결제할 때 사용자는 A카드를 사용할수도 있고, B카드를 사용할 수도 있다. 이렇게 경우에 따라서 수행하는 동작이 다르다. 사용자가 더 많은 카드를 등록하게 되면 C카드, D카드 등 유연하게 확장이 가능하다.
그리고 확장을 할 때 새로 등록되는 카드로 인해서 앞서 등록된 다른 카드에 영향을 미치지 않는다.
또한 A,B,C,D카드에 공통적으로 사용되는 소유자, 카드번호, 유효기간, 카드사등은 상위클래스에서 정의해두고, 각각의 카드 클래스에서 상위클래스를 상속받아 카드에 맞게 구현하면 된다.
만약 A카드로 해외결제를 하려고 하는데, 해외결제가 안된다고 가정하자.
이때 A카드사에 전화를 해서 해외결제가 가능하게 바꾸는 것이아닌, 다른 B카드를 사용하면 된다. 상황에 맞게 카드를 사용하는 것이다.
A카드를 해외결제를 위해서 구성을 바꾸는 것은 객체지향설계 원칙 OCP를 위배하는 행위가 된다.
OCP
기존의 코드를 변경하지 않으면서 기능을 추가할 수 있도록 설계가 되어야 한다. 전략 패턴을 이용하면, OCP에 위배되지 않고 시스템이 거대해졌을 때, 메소드의 중복을 해결할 수 있습니다.
객체들이 할 수 있는 행위 각각(pay)에 대해 전략 클래스(상위 PayMethod 클래스)를 생성하고, 유사한 행위들(pay)을 캡슐화 하는 인터페이스를 정의하여, 객체의 행위를 동적으로 바꾸고 싶은 경우 직접 행위를 수정하지 않고 전략을 바꿔주기만 함으로써 행위를 유연하게 확장하는 방법을 말합니다.
간단히 말해서 객체가 할 수 있는 행위들 각각을 전략(A카드, B카드)으로 만들어 놓고, 동적으로 행위의 수정(A카드의 해외결제 변경)이 필요한 경우 전략을 바꾸는 것(B카드를 사용)만으로 행위의 수정이 가능하도록 만든 패턴입니다.
"""
전략패턴
"""
class PayMethodStrategy(ABC):
@abstractmethod
def pay(self):
pass
class ACardStrategy(PayMethodStrategy):
def __init__(self, name, cardNumber, ccv, date):
self.name = name
self.cardNumber = cardNumber
self.ccv = ccv
self.date = date
self.is_visa = False
def pay(self, amount):
print("use ACard")
class BCardStrategy(PayMethodStrategy):
def __init__(self, name, cardNumber, ccv, date):
self.name = name
self.cardNumber = cardNumber
self.ccv = ccv
self.date = date
self.is_visa = True
def pay(self, amount):
print("use BCard")
class Item:
def __init__(self, name, cost, is_visa):
self.name = name
self.cost = cost
self.is_visa = is_visa
def pay(self, paymethod):
if self.is_visa == True :
if paymethod.is_visa== True:
paymethod.pay(self.cost)
else:
print("다른 카드로 결제하세요")
else:
paymethod.pay(self.cost)
print("결제가 완료 되었습니다.")
acard = ACardStrategy("ehdus", 1000, 1000, 1000)
bcard = BCardStrategy("ehdus", 2000, 2000, 2000)
banana = Item("바나나", 100, True)
banana.pay(acard)
banana.pay(bcard)
바나나는 해외카드로만 결제 할 수 있다. 그렇기 때문에 A카드를 해외카드로 변경하기 위해서 is_visa를 수정하는 것이 아닌 B카드를 이용해서 결제를 하는 식으로 전략을 변경해서 코드를 구현할 수 있다.
"""
전략패턴을 사용하지 않은 경우
"""
class NotStrategy:
def __init__(self):
pass
@abstractmethod
def cry(self):
pass
class Dog(NotStrategy):
def __init__(self):
print("나는 개")
def cry(self):
print("왈왈")
class Person(NotStrategy):
def __init__(self):
print("나는 사람")
def cry(self):
print("울지 않아")
person = Person()
dog = Dog()
person.cry()
dog.cry()
위의 예시에서 사람의 울음 소리가 바뀌게 되었다면, Person클래스의 cry메서드를 수정해주어야 한다. 하지만 이는 OCP원칙을 위반한다.
그렇기 때문에 아래와 같이 울음에 대한 전략패턴을 이용해서 사람이 울음소리가 변경되었을 때 클래스의 변경 없이 코드를 구현할 수 있다.
"""
전략패턴
"""
class SoundStrategy(ABC):
@abstractmethod
def cry(self):
pass
class CryStrategy(SoundStrategy):
def cry(self):
print("왈왈")
class NotCryStrategy(SoundStrategy):
def cry(self):
print("울지 않아")
class CryingContext:
def __init__(self):
self.__sound_strategy = None
def cry(self):
self.__sound_strategy.cry()
@property
def sound_strategy(self):
return self.__sound_strategy
@sound_strategy.setter
def sound_strategy(self, strategy):
self.__sound_strategy = strategy
class Dog(CryingContext):
def __init__(self):
super().__init__()
print("나는 개")
class Person(CryingContext):
def __init__(self):
super().__init__()
print("나는 사람")
person = Person()
dog = Dog()
person.sound_strategy = NotCryStrategy()
dog.sound_strategy = CryStrategy()
person.cry()
dog.cry()
person.sound_strategy = CryStrategy() # 사람의 울음전략을 바꾸어 주었다.
person.cry()
"""
전략패턴
"""
from abc import abstractmethod, ABC
class Scheduler(ABC):
@abstractmethod
def get_next_call(self):
pass
# 정책 패턴을 사용하지 않으면 메서드 마다 if-else구문으로 들어갈것이다.
# if RoundRobin
# elif LeastJob
# elif priorityAllocation
# 이후에 정책이 추가되면 계속 해서 if-else구문으로 추가될 것이다.
@abstractmethod
def senf_call_to_agent(self):
pass
# 정책 패턴을 사용하지 않으면 메서드 마다 if-else구문으로 들어갈것이다.
# if RoundRobin
# elif LeastJob
# elif priorityAllocation
# 이후에 정책이 추가되면 계속 해서 if-else구문으로 추가될 것이다.
class RoundRobin(Scheduler):
def get_next_call(self):
print("상담 전화를 순서대로 대기열에서 가져옵니다.")
def senf_call_to_agent(self):
print("다음 순서 상담원에게 배분합니다.")
class LeastJob(Scheduler):
def get_next_call(self):
print("상담 전화를 순서대로 대기열에서 가져옵니다.")
def senf_call_to_agent(self):
print("현재 상담업무가 없거나 상담개기가 가장 작은 삼당원에게 할당합니다.")
class PriorityAllocation(Scheduler):
def get_next_call(self):
print("고객 등급이 높은 고객의 전화를 먼저 가져옵니다.")
def senf_call_to_agent(self):
print("업무 능력이 좋은 상담원에게 우선 배분한다.")
# 이후에 정책이 추가되면, 클래스레벨에서 추가로 구현하면 된다.
# 기존에 있는 클래스에 영향을 주지 않는다.
class SchdulerContext:
def __init__(self):
self.__schdulertest_strategy = None
def get_next_call(self):
self.__schdulertest_strategy.get_next_call()
def senf_call_to_agent(self):
self.__schdulertest_strategy.senf_call_to_agent()
@property
def schdulertest_strategy(self):
return self.__schdulertest_strategy
@schdulertest_strategy.setter
def schdulertest_strategy(self, strategy):
self.__schdulertest_strategy = strategy
class A_company(SchdulerContext):
def __init__(self):
super().__init__()
print("a컴퍼니 입니다.")
class B_company(SchdulerContext):
def __init__(self):
super().__init__()
print("b컴퍼니 입니다.")
a = A_company()
a.schdulertest_strategy = RoundRobin()
a.get_next_call()
a.senf_call_to_agent()
b = B_company()
b.schdulertest_strategy = PriorityAllocation()
b.get_next_call()
b.senf_call_to_agent()
a.schdulertest_strategy = LeastJob()
a.get_next_call()
a.senf_call_to_agent()
https://victorydntmd.tistory.com/292
https://brownbears.tistory.com/574?category=378797
https://fastcampus.co.kr/