내가 공부하려고 올리는/스프링
객체 지향 설계와 스프링
결딴력
2022. 2. 1. 20:44
반응형
다형성의 한계
- 다형성만으로는
OCP
와DIP
를 지킬 수 없다. - 다형성을 만족하면서 OCP와 DIP를 동시에 만족하기 위해서는 스프링이 필요하다.
- 이때 스프링이 제공하는 기술은 다음과 같다.
DI(Dependency Injection)
: 의존관계, 의존성 주입DI 컨테이너 제공
- 위의 기술로 클라이언트 코드의 변경 없이 기능을 확장할 수 있다.
자바 프로젝트 생성
- 자바만을 사용해 간단한 프로젝트를 만들어 본다.
- 이후 변경에 유연하게 대처가 가능한지 확인해보고, 스프링을 도입해본다.
비즈니스 요구사항과 설계
- 회원
- 회원을 가입하고 조회할 수 있다
- 회원은 '일반'과 'VIP' 두 가지 등급이 있다.
- 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다.
- 주문과 할인 정책
- 회원은 상품을 주문할 수 있다.
- 회원 등급에 따라 할인 정책이 다르게 적용될 수 있다.
- 나중에 변경 가능한 사항-> 추후에 할인 정책은 변경될 가능성이 높다.
- "모든 VIP는 1000원을 할인해주는 고정 금액 할인이 적용된다."
회원 도메인 설계
- 도메인은 소프트웨어를 설계하는 대상 영역을 의미
- 회원 도메인 요구사항
- 회원을 가입하고 조회할 수 있다.
- 회원은 일반과 VIP 두 가지 등급이 있다.
- 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다.(미확정)
- 회원 도메인 협력 관계
클라이언트
/회원 서비스
/회원 저장소
= 역할메모리 회원 저장소
/DB 회원 저장소
/외부 시스템
= 구현
- 회원 클래스 다이어그램
- 회원 클래스에는 회원 서비스와 회원 저장소에 관한 두 개의 인터페이스가 존재
- 회원 서비스의 구현 클래스는 'MemberServiceImpl'
- 회원 저장소의 구현 클래스는 'MemoryMeberRepository', 'DbMemberRepository' 등
- 회원 저장소의 구현 클래스는 언제든지 변경될 수 있다.
회원 도메인 설계의 문제점
- 다음 코드를 보자
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
... 중략
- MemberServiceImpl은 MemberService 인터페이스를 구현하는 클래스이다.
- MemberServiceImpl에서는 '회원가입', '회원조회' 기능을 구현해야 한다.
- 두 기능은 DB와 연결되어야 하는데, 현재
MemoryMeberRepository
를 데이터 저장소로 사용하고 있으므로
데이터 저장소와 연결하기 위해 위와 같이 new 연산자로 구현 클래스를 생성해준다. - 이 코드의 문제점은?
- 외부 시스템이나 다른 데이터 저장소로 구현 객체를 변경하고 싶을 때 OCP 원칙을 준수할 수 있을까?
- 구현 객체가 아닌 인터페이스에 의존하고 있을까? (DIP를 잘 지키고 있을까?)
- 위 코드는 데이터 저장소가 바뀌게 되면 구현 객체를 변경해줘야 한다. => OCP 위반
- 위 코드는
인터페이스(MemberRepository)
뿐만아니라구현 객체
도 의존한다.(MemoryMemberRepository)
=> DIP 위반
주문과 할인 도메인 설계
- 주문과 할인 정책
- 회원은 상품을 주문할 수 있다.
- 회원 등급에 따라 할인 정책을 적용할 수 있다.
- 할인 정책은 모든 VIP에게 1000원을 고정 할인해주는 정책을 일단 사용한다.
-> 추후 변경 가능성이 높다.
주문 도메인 협력, 역할, 책임
주문 생성
: 클라이언트는 주문 서비스에 주문 생성을 요청회원 조회
: 할인을 위해서 회원 등급이 필요하기 때문에 주문 서비스는 회원 저장소에서 회원을 조회할인 적용
: 확인된 회원 등급에 따라 할인 정책을 결정주문 결과 반환
: 주문 서비스는 할인 결과를 포함한 주문 결과를 반환
주문 도메인 클래스 다이어그램
OrderService
/MemberRepository
/DiscountPolicy
: 역할 (인터페이스)OrderServiceImpl
/MemoryMemberRepository
/FixDiscountPolicy
등 : 구현 (구현 클래스)- 회원 저장소의 데이터 저장소는 언제든지 변경이 가능하다
- 할인 정책은
FixDiscountPolicy
,RateDiscountPolicy
등으로 언제든지 변경이 가능하다.
주문 도메인 설계의 문제점
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
... 중략
- 앞서 회원 도메인 설계의 문제와 비슷하다.
- 주문 서비스의 구현 객체는 직접 구현 객체를 선택하고 있다. -> DIP 위반
- 이때 데이터 저장소가 변경되거나 할인 정책이 변경될 경우 OCP를 위반하게 된다.
새로운 할인 정책 개발
- 주문한 금액의 '%'를 할인해주는 새로운 정률 할인 정책이 추가되었다고 가정해보자
- 새로운 할인 정책을 적용하는 과정을 통해 실제 문제점을 눈으로 확인해보자
- 새로운 구현 객체로
RateDiscountPolicy
클래스를 만들었다고 가정해보자. OrderServiceImpl
의 코드는 다음과 같이 변경된다.
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
... 중략
- 문제점
- 역할과 구현을 충분히 분리했다.
- 다형성도 활용했다.
- BUT,
OCP
,DIP
와 같은 객체지향 설계 원칙을 충실히 준수하지 못했다.
기대했던 의존관계
- DIP를 위반하지 않기 위해 인터페이스에만 의존하는 관계
실제 의존관계
- 실제로는 할인 정책이 변경될 때마다 new 연산자로 직접 할인 정책 구현 클래스를 생성하고 있다.
- 따라서
DIP
가 위반된다.
할인 정책 변경
- 할인 정책이 변경되는 순간 클라이언트 쪽 코드인
OrderServiceImpl
의 코드를 변경해줘야 한다. - 따라서
OCP
를 위반한다.
문제 해결 방법
- 클라이언트 코드인 OrderServiceImpl은 DiscountPolicy의 인터페이스뿐만 아니라
구현 클래스도 함께 의존하고 있다. - 그래서 구현 클래스를 변경하면 클라이언트 코드도 함께 변경해야만 한다.
- 따라서 문제를 해결하기 위해서는 인터페이스에만 의존하도록 설계를 변경해야 한다.
인터페이스에만 의존하도록 코드 변경
public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
- 인터페이스에만 의존할 수 있도록 구현 객체를 직접 생성하지 않는다.
- 하지만 구현체가 없는데 어떻게 코드가 실행될 수 있을까?
- 실제로 이 코드를 실행하면
Null Point Exception
이 발생한다.
해결방안
- 이러한 문제를 해결하기 위해서는 누군가 대신 구현 객체를 생성하고 주입해줘야 한다.
다음글에서
- 다음 글에서 어떻게 대신 구현 객체를 생성하고 주입할 수 있는지 알아보자
출처 : 인프런 김영한 님 스프링 강의
반응형