알림(Notification)이라는 도메인을 설계할 때,
단순히 메서드 하나로 퉁치기보다는 다음과 같은 철학적 고민이 필요했다.
“알림의 본질은 무엇인가?
단순히 알리는 것일까, 아니면 더 복합적인 흐름을 가진 도메인인가?”
이번 설계에서는 알림 시스템을 구성할 때,
Notification 객체의 책임과 경계를 어디까지 잡을지에 대해 집중적으로 고민했다.
Notification 도메인은 다음과 같은 세 가지 책임만 갖는다:
makeMessage())send())SendResult)트리거(trigger)와 저장 여부 결정,
즉 알림을 “언제” 보내고, “기록할지 말지”는 Notification 외부에서 처리하는 것으로 책임을 분리했다.
public abstract class Notification<T extends NotificationPayload> {
protected T payload;
public Notification(T payload) {
this.payload = payload;
}
public SendResult executeSend() {
Message message = makeMessage();
return send(message);
}
protected abstract Message makeMessage();
protected abstract SendResult send(Message message);
}
public class EmailNotification extends Notification<EmailNotificationPayload> {
public EmailNotification(EmailNotificationPayload payload) {
super(payload);
}
@Override
protected Message makeMessage() {
// 이메일 제목, 본문 등을 조합
return new Message("제목", "본문", payload.getEmailAddress());
}
@Override
protected SendResult send(Message message) {
// 이메일 발송 처리 로직
return new SendResult(true, "EMAIL", "전송 성공");
}
}
Map이나 Object로 파라미터를 넘기면 유연성은 생기지만 타입 안정성이 깨질 수 있음 → 전용 Payload 모델 도입이 필요executeSend() 내 템플릿 메서드 패턴은 흐름 제어에 좋았고, 확장 가능성도 확보됨“얼마나 유연하게 구조화하되,
필요한 책임만 정확히 잡고 있는가”
에 대한 밸런스 문제라는 걸 다시 느꼈다.
오늘은 실무에서 자주 활용되는 Template Method 패턴을 중심으로,
그 한계 상황과 Strategy Pattern으로의 전환 필요성에 대해 탐구했다.
알고리즘의 골격(Flow)을 상위 클래스에서 정의하고,
세부 동작은 하위 클래스에서 구현하도록 강제하는 구조
public abstract class Notification<T extends NotificationPayload> {
public SendResult executeSend() {
Message message = makeMessage();
return send(message);
}
protected abstract Message makeMessage();
protected abstract SendResult send(Message message);
}
→ 템플릿 패턴의 고정된 흐름이 오히려 발목을 잡는 구조로 전락
| 역할 | 기존 Template 구조 | Strategy 구조로 전환 |
|---|---|---|
| 전처리 | 추상 메서드 오버라이드 | PreProcessor 전략 객체 |
| 실행 | 추상 메서드 오버라이드 | ApiCaller 전략 객체 |
| 후처리 | 추상 메서드 오버라이드 | PostProcessor 전략 객체 |
public class DataCollector {
private final PreProcessor pre;
private final ApiCaller caller;
private final PostProcessor post;
public void collect(Request request) {
pre.prepare(request);
String response = caller.call(request);
post.handle(response);
}
}
템플릿 메서드 패턴은 단순하고 반복적인 흐름에 강력하다.
하지만 복잡도와 예외 흐름이 늘어나는 순간,
전략 기반 설계로의 전환이 필수적이라는 걸 실감했다.
내일은 실제 코드 리팩토링을 통해
이 이론을 실전 구조로 바꿔볼 예정이다.