이 글은 헤드퍼스트 디자인 패턴을 읽고 정리한 것입니다.
상속만으로 해결할 수 없는 것
Subclassing을 통해 클래스를 상속하면 complie time에 해당 클래스의 행동이 static 하게 고정 된다. 즉, 자식 클래스의 행동이 고정 되어 부모 클래스와 같은 행동을 하게 된다.
그런데 만약 composition(구성)을 통해 runtime에 객체의 행동을 다양화 할 수 있다면 변경에는 닫혀 있지만 확장에는 열려 있는 구조를 만들 수 있을 것이다.
또한, 이를 통해 자식 클래스에 여러 책임을 부여할 수 있기 때문에 기존 코드를 수정함으로써 생기는 오류 발생을 줄일 수 있다.
상황 가정해 보기
카페에서 커피를 팔고 있다. 이 때 커피의 종류는 다음과 같다.
이 때 고객은 여러 가지 종류의 토핑을 얹을 수 있다.
또한 커피 사이즈는 tall/grande/venti로 3가지이며 사이즈에 따라 가격이 달라진다.
이를 만약 전략 패턴을 이용 한다면 다음과 같은 방법을 생각해 볼 수 있다.
토핑의 종류가 증가할 때마다 variation이 수도 없이 생길 수 있을 것이다.
이렇게 되면 확장성이 매우 떨어진다.
이를 해결할 수 있는 방법 중 하나가 앞서 말했던 데코레이터 패턴이다.
아래는 어떤 문제 풀이 방식을 사용하는 지에 따라서 행동이 달라지는 케이스에 대한 예시이다. 파이썬으로는 데코레이터를 사용할 수 있어 조금 더 편하게 작성한 것 같다.
Python
from abc import *
from functools import wraps
"""
데코레이터
"""
def quest_update(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"{func.__name__}에서 퀘스트 업데이트 완료")
return func(*args, **kwargs)
return wrapper
def give_point(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"{func.__name__}에서 포인트 지급 완료")
return func(*args, **kwargs)
return wrapper
def premium_validated(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"{func.__name__}는 프리미엄만 가능합니다^^")
return func(*args, **kwargs)
return wrapper
"""
문제
"""
class Problem(metaclass=ABCMeta):
@abstractmethod
def solved_info(self):
raise NotImplementedError("반드시 만들어라.....")
class LevelExam(Problem):
@give_point
@quest_update
def solved_info(self):
print("진단고사 풀이 완료")
class ProblemBasedLearn(Problem):
@quest_update
@give_point
def solved_info(self):
print("문제 기반 풀이 완료")
class RetryProblem(Problem):
@quest_update
def solved_info(self):
print("복습 완료")
class SimilarProblem(Problem):
@premium_validated
@quest_update
@give_point
def solved_info(self):
print("유사 문제 풀이 완료")
java
public abstract class Problem {
String solveProblem = "Solve your problem";
public String solveProblem() {
return solveProblem;
}
}
public class LevelExam extends Problem {
public LevelExam() {
solveProblem = "진단고사 완료";
}
}
public class ProblemBasedLearn extends Problem {
public ProblemBasedLearn() {
solveProblem = "문제 기반 풀이 완료";
}
}
public class RetryProblem extends Problem {
public RetryProblem() {
solveProblem = "복습 완료";
}
}
public class SimilarProblem extends Problem {
public SimilarProblem() {
solveProblem = "유사 문제 풀이 완료";
}
}
// Decorators
// Decorator class should have the same type of object as its superclass
public abstract class ProblemDecorator extends Problem {
public abstract String solveProblem();
}
public class QuestUpdateDecorator extends ProblemDecorator {
Problem problem;
public QuestUpdateDecorator(Problem problem) {
this.problem = problem;
}
public String solveProblem() {
return "퀘스트 업데이트 완료" + problem.solveProblem();
}
}
public class GivePointDecorator extends ProblemDecorator {
Problem problem;
public GivePointDecorator(Problem problem) {
this.problem = problem;
}
public String solveProblem() {
return "포인트 지급 완료" + problem.solveProblem();
}
}
public class PremiumValidationDecorator extends ProblemDecorator {
Problem problem;
public PremiumValidationDecorator(Problem problem) {
this.problem = problem;
}
public String solveProblem() {
return "프리미엄만 사용 가능합니다" + problem.solveProblem();
}
}
// 사용하기
public class SolveYourProblem {
public static void main(String args[]) {
Problem problem = new SimilarProblem();
problem = new GivePointDecorator(problem);
problem = new QuestUpdateDecorator(problem);
problem = new PremiumValidatonDecorator(problem);
System.out.println(problem.solveProblem());
}
}
단점
단점으로 제기된 것들은 모두 '복잡성'에 관한 것들이며 이를 감안하거나 또는 감당 가능할 정도의 수 만큼 행동을 다양화 하고 싶은 경우 데코레이터를 사용하는 것은 좋은 방법인 것 같다.