지난 시간에는 회원 / 회원 서비스에 대한 도메인 설계 및 구현을 스프링 기능을 제외하고 순수 자바코드로 진행했었는데,
이렇게 짠 코드에는 어떤 문제가 있는지 살펴보겠습니다.
어떠한 문제가 있는지 알아보기 위해서는 객체지향 SOLID 원칙에 대해서 먼저 알아봐야합니다.
SOLID는 객체지향 프로그래밍에서 다섯 가지 설계 원칙을 나타냅니다.
이 원칙은 소프트웨어 설계의 유지보수성, 확장성, 가독성, 재사용성 등을 향상시키는데 목적을 두고 있습니다.
1. 단일 책임 원칙(SRP)
- 클래스는 하나의 책임만 가져야 한다.
2.개방/폐쇄 원칙(OCP)
- 소프트웨어 엔티티는 확장에는 열려있고, 변경에는 닫혀있어야 한다.
-새로운 기능을 추가할 때 기존의 코드를 변경하지 않고 확장할 수 있어야 한다.
여기서 보면 Shape 인터페이스를 구현하는 새로운 도형 클래스를 추가할 때 코드 변경이 필요하지 않습니다.
기존의 코드(shape)를 변경하지 않고 Rectangle 클래스를 확장한 것이죠.
3.리스코프 치환 원칙(LSP)
-서브타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다.
-즉, 상속 받은 클래스는 기반 클래스를 대체할 수 있어야 하며,
이를 통해 클라이언트 코드는 변경없이 새로운 서브클래스를 사용할 수 있어야 한다.
여기서는 Bird 클래스의 하위 클래스인 Sparrow 클래스가 Bird 클래스를 대신할 수 있어야 합니다.
4.인터페이스 분리 원칙(ISP)
- 클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안됩니다.
- 즉, 인터페이스를 클라이언트에 특화된 작은 단위로 분리하여 사용해야합니다.
여기서 보면, Human 클래스는 Worker와 Eater에 있는 work와 eat 메서드를 각각 의존하고 있습니다.
이렇게 사용하는 메서드가 존재하는 클래스/인터페이스에만 의존해야합니다
5.의존성 역전 원칙 (DIP)
-고수준 모듈은 저수준 모듈에 의존하면 안 되며, 두 모두 추상화에 의존해야 한다.
- 추상화는 구체적인 것에 의존해서는 안 되며, 구체적인 것이 추상화에 의존해야 한다.
이 예시를 보면. 추상화인 Switchable 인터페이스에 의존하고 있는 Light , Fan 클래스를 볼 수 있습니다.
이처럼 상위 수준 모듈이 하위 수준 모듈에 의존하는 것이 아니라, 둘 다 추상화에 의존해야합니다.
주문/할인 도메인 설계
SOLID 원리를 먼저 알아보았고, 주문과 할인 도메인 설계까지 마무리 한 후 각 원칙에 위배되는 점을 알아보겠습니다.
주문 도메인을 보면
1. 주문 생성: 클라이언트는 주문 서비스에 주문 생성을 요청합니다.
2. 회원 조회: 할인을 위해서는 회원 등급이 필요합니다. 그래서 주문 서비스는 회원 저장소에서 회원을 조회합니다.
3. 할인 적용: 주문 서비스는 회원 등급에 따른 할인 여부를 할인 정책에 위임합니다.
4. 주문 결과 반환 : 주문 서비스는 할인 결과를 포함한 주문 결과를 반환합니다.
결과적으로 전체적인 그림을 보면
위와 같이 완성됩니다.
이제 주문 도메인 클래스의 다이어그램을 보겠습니다.
OrderService 인터페이스는 OrderServiceImpl 구현체에 의해 동작되고
OrderServiceImpl 구현체는 MemberRepository , DiscountPolicy 인터페이스에 의해 동작됩니다.
MemberRepository는 저장 정책에 따라 'MemoryMemberRepository' 또는 'DbMemberRepository'에 의존하고
DiscountPolicy도 할인 정책에 따라 'FixDiscountPolicy'(정액할인), 'RateDiscountPolicy'(정률할인)에 의존합니다.
MemberRepository 인터페이스와 DiscountPolicy 인터페이스는 각 구현체에 따라서 알맞는 클래스를
마치 조립하듯 구현체를 끼워넣기만 하면 됩니다 .
할인 정책 인터페이스
DiscountPolicy 인터페이스를 작성해보았습니다.
DiscountPolicy는 discount 메서드만 사용하게 됩니다.
때문에 껍데기만 생성해두고 , 매개변수로 회원 등급을 알아야하므로 member와 price를 넘깁니다.
FixDiscountPolicy
정액할인을 위한 코드입니다.
정액할인은 1000원으로 결정하였다고 가정을하고 discountFixAmount 값을 1000으로 설정합니다.
그 후 앞서 인터페이스에서 정의한 discount 메서드를 재정의하므로
@Overide 어노테이션을 붙여주고 discount 메서드를 정의합니다,
만약 회원의 등급이 VIP라면 discountFixAmount값을 반환해주고
그렇지 않다면 (일반 회원이라면 0 을 반환합니다)
즉, 회원의 등급이 일반이라면 0원 할인, VIP라면 1000원을 할인해줍니다.
정률 할인은 다음시간에 구현하도록 하고 다음으로는 주문 엔티티를 만들어보겠습니다.
주문 엔티티
Order 엔티티에는 주문에 필요한 정보인 memberId, itemName, itemPrice, disxcountPrice 를 설정합니다 .
또한 기본 생성자를 생성해주고 Getter와 Setter도 모두 설정해줍니다 .
마지막으로, 여기서는 따로 뷰를 구현하지 않기 때문에 주문을 확인할 toString() 메서드를 구현합니다.
주문 서비스 인터페이스
OrderService에서는 주문에 관련된 메서드를 선언하는 곳 입니다,
createOrder 메서드를 사용하고 memberId, itemName, itemPrice를 매개변수로 사용합니다.
(어떤 회원이 , 어떤 아이템을 , 얼마에 주문하는지)
주문 서비스 구현체
OrderServiceImpl은 OrderService를 구현하므로 implements를 써주고,
MemberRepository는 Memory 방식을 쓰므로 MemoryMemberRepository()를
DiscountPolicy는 Fix(정액할인)방식을 쓰므로 FIxDiscountPolicy()를 사용합니다.
그리고 인터페이스에서 선언한 createOrder메서드를 구현해줍니다.
1. Member member = memberRepository.findById(memberId); 를 통해 회원을 조회합니다.
2. int discountPrice = discountPolicy.discount(member, itemPrice); 를 통해 회원에 따른 할인가를 계산합니다.
return new Order(memberId, itemName, itemPrice, discountPrice);
->마지막으로 위에서 조회한 회원 정보, 상품 정보, 할인된 가격 등을 사용하여 'Order'객체를 생성하고
'Order'클래스의 생성자를 호출하여 새로운 주문 객체를 생성한 후 반환합니다.
다음시간에는 RateDiscountPolicy 구현체를 만들고 , SOLID에 위배되는 것들과 해결방법에 대해서 알아보겠습니다.
'스프링' 카테고리의 다른 글
스프링 기초 - 스프링 컨테이너 (0) | 2024.02.14 |
---|---|
스프링 기초 - 스프링으로 전환 (0) | 2024.02.13 |
스프링 기초 -AppConfig와 구현체 조립 (0) | 2024.02.13 |
스프링 기본원리 - 객체지향 원리 적용 (1) | 2024.02.13 |
스프링 기본원리 - 김영한 (Entity/Repository/Interface) (0) | 2024.02.12 |