
해당 요구사항에 맞는 객체지향 프로그래밍을 진행하고 해당 코드를 SOLD 원칙에 맞게 제 나름대로 해석 해보았습니다. 아직 배우는 단계라 너무 부족합니다. 지적 사항을 댓글로 남겨주시면 정말로 감사하겠습니다. 위사진은 해당 셈플코드의 간략한 클래스 다이어그램 입니다.
예제 코드
blog-sample
요구사항
- 배송이 완료 됬을 경우 사용자가 등록한 알림을 받는다.
- 알림에는 SMS, Kakao, Email 이 있다
- 알람은 지속적으로 추가 될 가능 성이 높아 보인다.
Delivery Domain
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Delivery { ...
@OneToMany(fetch = FetchType.LAZY) @JoinColumn(name = "transfer_id") private List<DeliveryNotificationType> deliveryNotificationTypes = new ArrayList<>();
@OneToOne(targetEntity = Sender.class) @Column(name = "sender_id") private Sender sender;
@OneToOne(targetEntity = Receiver.class) @Column(name = "receiver_id") private Receiver receiver;
... }
|
- Delivery는 Sender(발송인), Receiver(수신자)와 연관관계를 맺는다.
- Delivery는 배송이 안료되면 수신자에게 보낼 알림 타입의 리스트 DeliveryNotificationType와 연관관계를 맺는다
DeliveryNotificationSender Interface
1 2 3
| public interface DeliveryNotificationSender { void send(DeliveryMessageDto.Message dto); }
|
- 배송 노티를 보내는 인터페이스
- dto 클래스로 유연하게 받을 수 있게 처리
DeliveryKakaoNotificationSender 구현 클래스
1 2 3 4 5 6 7 8 9
| @Component public class DeliveryKakaoNotificationSender implements DeliveryNotificationSender { ... @Override public void send(DeliveryMessageDto.Message dto) { kakaoNotificaionSender.create(buildKaKaoNotificationDto(dto)); } ... }
|
- 배송 관련 카카오 메시지를 담당하는 클래스
DeliveryNotificationSender 인터페이스를 implements 해서 자신이 send 메서드를 구현 해야하는 책임을 명확하게 알 수 있다.
KakaoNotificaionSender 클래스를 이용해서 실제 구체적으로 어떻게 보내지는지 모르더라도 상관 없다.
- 테이블에 insert 되고 그 데이터 기반으로 카카오에서 메시지를 전송해 준다.
DeliverySmsNotificationSender 구현 클래스
1 2 3 4 5 6 7 8 9 10
| @Component public class DeliverySmsNotificationSender implements DeliveryNotificationSender { ... @Override public void send(DeliveryMessageDto.Message dto) { smsNotificationSender.sendSMS(buildSmsMessageDto(dto)); } ... }
|
- 배송 관련 카카오 메시지를 담당하는 클래스이다.
DeliveryNotificationSender 인터페이스를 implements 해서 자신이 send 메서드를 구현 해야하는 책임을 명확하게 알 수 있다.
SmsNotificationSender 클래스를 이용해서 메시지를 보내고 있어 실제 구체적으로 어떻게 보내지는지 모르더라도 상관 없다.
- 해당 업체 API 호출을 통해서 문자전송이 진행된다. 카카오 메시지 전송과는 다르다. 하지만
DeliveryNotificationSender 인터페이스를 통해서 send라는 동일한 책임을 갖게 된다.
DeliveryNotificationSenderManager
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class DeliveryNotificationSenderManager { ... public void sendMessage(Delivery delivery) { final List<DeliveryNotificationType> notifications = delivery.getDeliveryNotificationTypes();
if (!notifications.isEmpty()) for (DeliveryNotificationType type : notifications) getInstanceByType(type).send(buildMessage(delivery)); }
private DeliveryNotificationSender getInstanceByType(DeliveryNotificationType type) { return deliveryNotificationSenderFactory.getInstanceByType(type.getType()); } ... }
|
- 사용자가 등록한 알림을 전송
getInstanceByType 팩토리 메소드를 통해서 해당 타입에 맞는 인스턴스가 의존성 주입
- type이 SMS 일 경우
DeliverySmsNotificationSender 인스턴스 주입
DeliverySmsNotificationSender 인스턴스 send(SMS 전송) 메서드 실행
- 컴파일 단계에서는 알림을 보내는 것이
DeliveryNotificationSender 인터페이스를 바라보지만 런타임 단계에서는 그것이 역전되 IoC 발생
SOLID
아직 배워가는 단계라 많이 부족 하지만 나름대로 SOLD 원측에 의해서 정리 해보겠습니다.
SRP : 단일 책임 원칙
- 객체는 오직 하나의 변경의 이유만을 갖게되 었습니다. 배송 관련 카카오 전송시 세부 메시지 형태나, 포함될 정보가 달라질 경우
DeliveryKakaoNotificationSender 클래스만 변경 하면됩니다.
- 다른 곳에서 다른형태로 카카오 메시지를 전송하고 있는 로직에 영향을 미치지 않습니다.
OCP : 개방-폐쇄 원칙
- 확장에는 열려 있습니다.
- 새로운 배송 라인 알림이 추가 된다고 하면
DeliveryNotificationSender 인터페이스를 implements 하여 send 메서드만 세부 구현 하면됩니다.
- IoC를 이용해서
send 메서드를 다형성으로 해결하지 않았다면 알림이 추가 될때 마다 if 으로 무슨 알림이면 무슨 send를 실행 하라는 반복 적인 코드들이 나오게 됩니다.
if은 직관적으로 이해하기 편하 코드지만 유지보수하기는 어렵습니다. 만약 10개 알림이 있고 11번째 알림을 추가 한다고 가정했을 때 앞에 작성된 if문들을 이해하고 또 11 번째 if문을 추가해야 합니다. 코드는 작성하는 시간보다 읽히는 시간이 더 많습니다.
- 변경에는 닫혀 있습니다.
- 새로운 배송 알림이 추가 되더라도 기존 코드의 변경은 거의 없습니다. 팩토리 메소드에 새로운 배송 알림을 담당하는 인스턴스를 추가 하기만 하면 됩니다.
- 위에서 언급했듯이 새로운 배송 알림이 추가되면 해당 코드에 가서
if을 통한 send 메시지를 작성할 필요가 없습니다.
LSP : 리스코프 치환 원칙
- 서브 타입은 언제나 슈퍼타입을 교체 할 수 있어야 한다.
- 구현 클래스들은 모두
DeliveryNotificationSender인터페이스의 send 메서드를 구현 하고 있어 교체가 가능합니다.
ISP : 인터페이스 분리 원칙
- 하위 클래스들에서 사용하지 않은 의존성을 가지고 있지 않고 있습니다.
DIP : 의존성 역전 원칙
DeliveryNotificationSenderManager 클래스에서 팩토리 메소드를 통해서 DeliveryNotificationSender의 새부 인스턴스를 각 타입에 맞게 변경 해주고 있습니다.