GOF의 디자인패턴중 하나로 행위 패턴에 속한다.
스프링의 ApplicationEventPublisher의 동작 원리다.
if (paymentSuccess) {
order.setStatus(OrderStatus.PAID);
// 4단계: 알림 발송
notificationService.notifyOrderPaid(order);
} else {
order.setStatus(OrderStatus.CANCELLED);
notificationService.notifyOrderCancelled(order);
}
현재 주문을 하고 알림을 발송한다.
public class OrderService {
private final PaymentService paymentService;
private final DiscountService discountService;
private final NotificationService notificationService;
public OrderService() {
this.paymentService = new PaymentService();
this.discountService = new DiscountService();
this.notificationService = new NotificationService();
}
public void updateOrderStatus(Order order, OrderStatus newStatus) {
OrderStatus oldStatus = order.getStatus();
order.setStatus(newStatus);
System.out.println("[상태 변경] " + oldStatus + " -> " + newStatus);
switch (newStatus) {
case PAID -> notificationService.notifyOrderPaid(order);
case SHIPPED -> notificationService.notifyOrderShipped(order);
case CANCELLED -> notificationService.notifyOrderCancelled(order);
default -> {}
}
}
새로운 상태가 추가될때마다 이 오더서비스를 수정해야한다. 오더 서비스와 알림 서비스가 강결합 되어있다.
// 알림 채널이 추가될 때마다 여기에 메서드가 계속 늘어납니다...
public void notifyOrderCreated(Order order) {
sendEmail(order, "주문이 생성되었습니다.");
sendSms(order, "주문이 생성되었습니다.");
// TODO: Slack 알림 추가하면? Push 알림 추가하면?
}
public void notifyOrderPaid(Order order) {
sendEmail(order, "결제가 완료되었습니다.");
sendSms(order, "결제가 완료되었습니다.");
}
public void notifyOrderShipped(Order order) {
sendEmail(order, "상품이 배송되었습니다.");
sendSms(order, "상품이 배송되었습니다.");
}
public void notifyOrderCancelled(Order order) {
sendEmail(order, "주문이 취소되었습니다.");
sendSms(order, "주문이 취소되었습니다.");
}
알림 채널이 추가 될때마다 메서드가 늘어나게 된다. 슬랙이나 푸시 알림 추가하면 더 늘어난다.
옵저버 패턴 구현
package event;
import entity.Order;
import entity.OrderStatus;
import java.util.List;
public class OrderEventPublisher {
private final List<OrderEventListener> orderEventListeners;
public OrderEventPublisher() {
this.orderEventListeners = List.of(
new SlackNotificationListener(),
new SmsNotificationListener(),
new PushNotificationListener(),
new EmailNotificationListener());
}
public void publishEvent(Order order, OrderStatus status, String message) {
for (OrderEventListener listener : orderEventListeners) {
listener.onEvent(order, status, message);
}
}
}
발행자를 정의한다 발행자는 옵저버의 리스트를 가지고 있다.
public interface OrderEventListener {
void onEvent(Order order, OrderStatus status, String message);
}
public class PushNotificationListener implements OrderEventListener{
@Override
public void onEvent(Order order, OrderStatus status, String message) {
System.out.println("[EMAIL] " + order.getCustomerEmail() + " -> " + message);
}
}
public class SlackNotificationListener implements OrderEventListener{
@Override
public void onEvent(Order order, OrderStatus status, String message) {
System.out.println("[SLACK] " + order.getCustomerEmail() + " -> " + message);
}
}
public class SmsNotificationListener implements OrderEventListener{
@Override
public void onEvent(Order order, OrderStatus status, String message) {
System.out.println("[SMS] " + order.getCustomerName() + " -> " + message);
}
}
public class EmailNotificationListener implements OrderEventListener {
@Override
public void onEvent(Order order, OrderStatus status, String message) {
System.out.println("[EMAIL] " + order.getCustomerEmail() + " -> " + message);
}
}
옵저버 인터페이스와 옵저버 구현체를 정의한다 여기서 발행자와 옵저버는 1대다 관계로 하나의 발행자를 여러 옵저버가 구독한다.
스프링의 많은 리스너가 하나의 이벤트를 구독하는 것을 생각하면 된다.
public class OrderService {
private final PaymentService paymentService;
private final DiscountService discountService;
private final OrderEventPublisher orderEventPublisher;
public OrderService() {
this.paymentService = new PaymentService();
this.discountService = new DiscountService();
this.orderEventPublisher = new OrderEventPublisher();
}
if (paymentSuccess) {
order.setStatus(OrderStatus.PAID);
// 4단계: 알림 발송
orderEventPublisher.publishEvent(order, order.getStatus(), "결제가 완료되었습니다.");
} else {
order.setStatus(OrderStatus.CANCELLED);
orderEventPublisher.publishEvent(order, order.getStatus(), "주문이 취소되었습니다.");
}
return order;
public void updateOrderStatus(Order order, OrderStatus newStatus) {
OrderStatus oldStatus = order.getStatus();
order.setStatus(newStatus);
System.out.println("[상태 변경] " + oldStatus + " -> " + newStatus);
switch (newStatus) {
case PAID -> orderEventPublisher.publishEvent(order, order.getStatus(), "결제가 완료되었습니다.");
case SHIPPED -> orderEventPublisher.publishEvent(order, order.getStatus(), "상품이 배송 되었습니다.");
case CANCELLED -> orderEventPublisher.publishEvent(order, order.getStatus(), "주문이 취소 되었습니다.");
default -> {
}
}
}
}
오더 서비스와 알림 서비스의 결합이 끊어졌다. 이전에는 알림 서비스의 메서드 변화에 따라 오더 서비스도 따라 변화해야하는 강결합 즉, 결합도가 높은 상태였다. 이제는 결합이 끊어지면서 알림서비스 자체가 필요없어졌다.
//public class NotificationService {
//
// // 알림 채널이 추가될 때마다 여기에 메서드가 계속 늘어납니다...
// public void notifyOrderCreated(Order order) {
// sendEmail(order, "주문이 생성되었습니다.");
// sendSms(order, "주문이 생성되었습니다.");
// // TODO: Slack 알림 추가하면? Push 알림 추가하면?
// }
//
// public void notifyOrderPaid(Order order) {
// sendEmail(order, "결제가 완료되었습니다.");
// sendSms(order, "결제가 완료되었습니다.");
// }
//
// public void notifyOrderShipped(Order order) {
// sendEmail(order, "상품이 배송되었습니다.");
// sendSms(order, "상품이 배송되었습니다.");
// }
//
// public void notifyOrderCancelled(Order order) {
// sendEmail(order, "주문이 취소되었습니다.");
// sendSms(order, "주문이 취소되었습니다.");
// }
//
// private void sendEmail(Order order, String message) {
// System.out.println("[EMAIL] " + order.getCustomerEmail() + " -> " + message);
// }
//
// private void sendSms(Order order, String message) {
// System.out.println("[SMS] " + order.getCustomerName() + " -> " + message);
// }
//}
이 알림 서비스 메서드는 전부 필요가 없어졌다.
오더 서비스와 알림 서비스가 끊어져서 결합도가 낮아졌다.
옵저버 패턴은 대상의 상태가 바뀔때 옵저버에게 응답을 보내어 옵저버가 저마다의 로직을 수행하는 것이다.
자세하게 대상과 발행자와 옵저버 3가지로 나뉜다. 발행자(퍼블리셔)는 옵저버의 인터페이스를 의존하며 구현체를 리스트로 가진다.
많은 옵저버들은 발행자를 지켜보고있다. 그리고 관찰대상은 발행자에 의존한다. 관찰대상이 발행자에 응답을 보내면 발행자는 옵저버들에게 응답을 보낸다. 옵저버들은 각자의 로직을 수행한다.