회원 도메인 설계
아래와 같은 다이어그램은 기획자들도 볼 수 있는 그림이다.
위의 다이어그램을 개발자가 구체화해서 클래스 다이어그램을 만들어낸다. (인터페이스랑 구현체들이 다 보이는거)
그리고 아래 다이어그램은 실제 서버를 실행하지 않고, 클래스를 분석해서 볼 수 있는 객체 다이어그램이다.
구현체는 동적으로(예를 들어 어떤 DB를 넣을지) 결정된다.(서버가 뜰때 new해서 뭘 넣을건지)
그래서 클래스 다이어그램만으로 판단하기 어렵다. 그래서 동적인 객체 다이어그램이 필요하다.
서버가 뜰 때, 클라이언트가 실제 사용할때 new한 인스턴스의 참조를 나타낸다.
✔︎ HashMap 보다는 ConcurrentHashMap을써줘야한다. HashMap은 동시성 문제가 발생할 수 있기 때문이다.
✔︎ 관례) 구현체 하나만 있을 때에는 이름을 인터페이스명+Impl
으로 많이 쓴다.
✔︎ 테스트 코드 장점
- 눈으로 콘솔을 보며 출력된 결과를 보면서 검증하는 것이 낫다.
- 테스트 실패하면 빠르게 캐치 가능
- 따라서 테스트를 잘 작성하는 것이 중요하다.
✔︎ 메인메서드를 만들어서 테스트 해보는 것은 좋지 않다. 테스트 케이스를 돌려서 테스트 해보기 !!!
테스트 케이스는 성공 테스트뿐만 아니라 실패 테스트케이스도 꼭 만들어야한다.
✔︎ Assertions.assertThat()은 static import하는 것이 좋다.
public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
이렇게 다형성을 활용하고 인터페이스 구현 객체를 분리해서 코드를 짰으므로, OCP, DIP같은 객체지향 설계 원칙을 충실히 준수했다고 생각한다. 하지만 그렇게 보여도 사실은 아니다.
클래스 의존관계를 분석해보면, 추상(인터페이스) 뿐만 아니라 구체(구현)클래스에도 의존하고 있다.
- 추상(인터페이스) 의존:
DiscountPolicy
- 구체(구현) 클래스:
FixDiscountPolicy
,RateDiscountPolicy
OCP는 변경하지 않고 확장할 수 있다고 했는데, 위의 코드는 기능을 확장해서 변경하면 OrderService
의 코드를 변경해야 하는 것처럼 클라이언트 코드에 영향을 준다. 따라서 OCP를 위반한다고 할 수 있다.
기대했던 의존관계는 아래와 같다.
하지만 실제 의존관계는 아래 다이어그램처럼 클라이언트인 OrderServiceImpl
이 DiscountPolicy
인터페이스 뿐만 아니라 FixDiscountPolicy
인 구체 클래스도 함께 의존하고 있다. 실제 코드를 보면 의존하고 있다! ➡︎ DIP 위반
그래서 FixDiscountPolicy
를 RateDiscountPolicy
로 변경하는 순간 OrderServiceImpl
의 소스 코드도 함께 변경해야 한다! ➡︎ OCP 위반
정리하자면
클라이언트 코드인 OrderServiceImpl
은 DiscountPolicy
의 인터페이스 뿐만 아니라 구체 클래스도 함께 의존한다.
그래서 구체 클래스를 변경할 때 클라이언트 코드도 함께 변경해야 한다.
따라서 이는 DIP와 OCP에 위반하고 있으므로, 추상에만(인터페이스에만 의존) 의존하도록 변경해야한다.
☞ 인터페이스에만 의존하도록 의존관계를 변경하면 된다. ☞ 관심사를 분리하자 (각자의 역할에만 집중하도록)
AppConfig
AppConfig를 이용해 위에서 DIP와 OCP를 위반하는 문제를 해결할 수 있다.
애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스를 만들어야한다. 애플리케이션의 환경 구성에 대한거 AppConfig에서 다하도록!!! (애플리케이션 전체를 설정하고 구성)
AppConfig는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결)해준다.
- 생성자 주입 : 생성자를 통해서 객체가 new 인스턴스 생성된게 들어간다.
이제 설계 변경으로 MemberServiceImpl
은 MemoryMemberRepository
를 의존하지 않고, 단지 MemberRepository
인터페이스만 의존한다.
MemberServiceImpl
입장에서 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다. MemberServiceImpl
의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부( AppConfig )에서 결정된다.MemberServiceImpl
은 이제부터 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다.
이제 MemberServiceImpl
은 MemberRepository
인 추상에만 의존하면 된다. 이제 구체 클래스를 몰라도 된다. ➡︎ DIP 완성
객체를 생성하고 연결하는 역할과 실행하는 역할이 명확히 분리되었다.(관심사의 분리)
AppConfig 객체는 memoryMemberRepository
객체를 생성하고 그 참조값을 memberServiceImpl
을 생성하면서 생성자로 전달한다.
클라이언트인 memberServiceImpl
입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 해서 DI(Dependency Injection) 우리말로 의존관계 주입 또는 의존성 주입이라 한다.
➡︎ 정리하자면
AppConfig를 통해서 관심사를 확실하게 분리했다.
연극에 비유하자면, AppConfig는 공연 기획자다.
AppConfig는 구체 클래스를 선택하고 배역에 맞는 담당 배우를 선택한다. 애플리케이션이 어떻게 동작해야 할지 전체 구성을 책임진다.
이제 각 배우(OrderServiceImpl
)들은 담당 기능을 실행하는 책임만 지면 된다.
AppConfig의 등장으로 애플리케이션이 크게 사용 영역과, 객체를 생성하고 구성(Configuration)하는 영역으로 분리되었다.
AppConfig 리팩터링의 장점
AppConfig에 설계에 대한 그림이 구성정보에 그대로 드러난다.
역할과 구현 클래스가 한눈에 들어온다. 애플리케이션 전체 구성이 어떻게 되어있는지 빠르게 파악할 수 있다.
(리팩터링 전에는 중복이 있고, 역할에 따른 구현도 잘 안보였음)
이 게시물은 인프런 - 김영햔님의 스프링 핵심원리 기본편 강의를 바탕으로 제작된 게시물입니다.
https://www.inflearn.com/course/스프링-핵심-원리-기본편/
'Web > Spring' 카테고리의 다른 글
싱글톤 컨테이너 (0) | 2021.08.07 |
---|---|
스프링 컨테이너와 스프링 빈 (0) | 2021.07.30 |
AOP (0) | 2021.07.25 |
스프링 DB 접근 기술 (0) | 2021.07.25 |
스프링 빈과 의존관계 / 웹 MVC 개발 (0) | 2021.07.25 |