[Spring Core #1] 스프링의 객체 지향 원리 적용 (스프링 핵심 원리 강의정리)
Index
📌 새로운 요구사항의 추가
📌 관심사 분리
📌 AppConfig 리팩토링
📌 좋은 객체 지향 설계 5가지 원칙 적용
📌 IoC, DI, 컨테이너
📌 정리
해당 내용은 강의 내용을 기억하기 위한 정리글입니다. 자세한 내용은 강의에서 확인하실 수 있습니다.
(저는 코틀린 베이스로 강의를 진행하였고 게시글의 코드 예시는 대부분 코틀린으로 이루어져 있습니다.)
스프링 핵심 원리 - 기본편(김영한님) #광고아님, #내돈내산, #적극추천
📌 새로운 요구사항의 추가
주문서비스가 있고 주문에 따른 할인 정책을 적용해야 하는 비즈니스 요구사항이 있습니다. 할인 정책에는 고정할인 정책만 적용해보려합니다. 다음과 같이 서비스 클래스를 구성할 수 있습니다.
class OrderServiceImpl: OrderService {
private val discountPolicy: DiscountPolicy = FixDiscountPolicy()
//...
}
위와 같이 OrderServiceImpl 서비스 코드는 DiscountPolicy에 의존하고 있고 해당 정책에는 고정할인정책 구현체를 적용하였습니다.
여기서 객체지향 관점에서 생각해볼 지점이 몇 가지 있습니다.
- 역할과 구현을 충실하게 분리
DiscountPolicy라는 역할과 FixDiscountPolicy라는 구현을 분리하였습니다. - 다형성 활용, 인터페이스/구현체 분리
역할과 구현 내용과 비슷하게 DiscountPolicy는 인터페이스 FixDiscountPolicy는 구현체입니다.
역할과 구현, 다형성 활용, 인터페이스 구현체 분리 등 객체 지향 원리를 잘 활용했다고 볼 수 있습니다. 하지만 또 생각해볼 것들이 있습니다.
class OrderServiceImpl: OrderService {
//private val discountPolicy: DiscountPolicy = FixDiscountPolicy()
private val discountPolicy: DiscountPolicy = RateDiscountPolicy()
//...
}
- DIP(의존 역전 원칙) 준수 X
OrderServiceImpl은 추상(인터페이스, DiscountPolicy)과 구현 클래스(FixDiscountPolicy) 둘다 의존하고 있습니다. - OCP(개방 폐쇄 원칙) 준수 X
기능을 확장하면(또 다른 할인 정책 적용) 이것을 사용하는 클라이언트 코드(OrderServiceImpl)에 영향을 주게 됩니다.
즉 새로운 요구사항의 추가로 인해 기능을 확장해야 하는 상황에서는 한계에 부딪히게 됩니다.
(확장성을 고려못한 설계, 객체지향스럽지 않은 냄새나는 코드라 할 수 있습니다.)
📌 관심사 분리
위의 서비스 코드(OrderServiceImpl)에서는 두 가지 책임과 역할을 수행하고 있습니다. 하나는 서비스 코드 자체의 비즈니스 로직 수행 역할, 또 하나는 객체 생성과 참조변수 할당의 역할입니다. 이러한 설계는 객체지향스럽지 못합니다. 객체 지향은 기본적으로 하나의 코드에 하나의 책임과 역할을 수행해야 한다는 원칙을 가지고 있습니다. 즉 여기서 관심사를 분리해야 될 필요가 생긴 것입니다.
여기서 설정파일을 통해 객체 생성과 할당하는 역할을 분리해보겠습니다.
@Configuration
class AppConfig {
@Bean
fun orderService(): OrderService {
return OrderServiceImpl(MemoryMemberRepository(), FixDiscountPolicy())
}
}
class OrderServiceImpl(
private val memberRepository: MemberRepository,
private val discountPolicy: DiscountPolicy
) : OrderService {
//...
}
스프링 IoC와 DI를 통해서 관심사 분리를 간단하게 구현할 수 있습니다. AppConfig 설정파일을 만들고 OrderService 구현체를 스프링 빈으로 등록합니다. 이 때 생성자를 통해 MemberRepository와 DiscountPolicy 인터페이스의 구현체를 주입합니다.
스프링 IoC는 AppConfig 설정 내용을 토대로 대신 객체를 생성해주고 필요한 구현체들을 주입시켜줍니다. 서비스 코드인 OrderServiceImpl에서는 바로 위 코드처럼 순수하게 비즈니스 로직만 수행하면 됩니다.
만약 FixDiscountPolicy에서 RateDiscountPolicy로 기능을 변경해야 한다면 AppConfig만 코드를 수정하면 됩니다. 서비스 코드에서는 변경지점이 사라지게 됩니다. (OCP, DIP 원칙을 준수하게 됩니다.)
- AppConfig: 구현체 클래스 선택 및 전체 구성 책임만 수행
- OrderServiceImpl: 기능을 실행하는 책임만 수행
이렇게 관심사를 확실하게 분리할 수 있습니다.
📌 AppConfig 리팩토링
지금은 Bean 생성시 필요한 구성 클래스들을 직접 생성자를 통해서 주입을 했습니다.
@Configuration
class AppConfig {
@Bean
fun orderService(): OrderService {
return OrderServiceImpl(MemoryMemberRepository(), FixDiscountPolicy())
}
@Bean
fun memberService(): MemberService {
return MemberServiceImpl(MemoryMemberRepository())
}
}
위와 같이 MemberRepository 구현체가 여러 bean 생성 때 필요로 한다면 생성자를 통한 객체 생성 부분이 중복이 될 수 있습니다.
이를 하나로 묶을 수 있습니다.
@Configuration
class AppConfig {
@Bean
fun orderService(): OrderService {
return OrderServiceImpl(memberRepository(), discountPolicy())
}
@Bean
fun memberService(): MemberService {
return MemberServiceImpl(memberRepository())
}
@Bean
fun memberRepository(): MemberRepository {
return MemoryMemberRepository()
}
@Bean
fun discountPolicy(): DiscountPolicy {
return FixDiscountPolicy()
}
}
- @Configuration: 스프링에게 해당 클래스가 설정파일이라는 것을 알려줍니다. (Bean들의 설계도)
- @Bean: 스프링 컨테이너에 스프링 빈으로 등록을 시켜줍니다.
📌 좋은 객체 지향 설계 5가지 원칙 적용
5가지 중 SRP, DIP, OCP에 대해서 생각해볼 수 있습니다.
- SRP(단일 책임 원칙)
- 구현 객체 생성 및 연결은 AppConfig, 실행에 대한 책임은 클라이언트 객체(OrderServiceImpl)가 담당
- 관심사 분리 - DIP(의존 관계 역전 원칙)
- 의존성 주입을 통해 추상화에 의존하고 구체화에 의존하지 않는 구조를 따름
- OrderServiceImpl > DiscountPolicy(인터페이스)에 의존, FixDiscountPolicy에 의존 X - OCP(개방 폐쇄 원칙)
- 확장에는 열려있고 변경에는 닫혀있음
- FixDiscountPolicy -> RateDiscountPolicy
- 다른 할인정책으로 확장할 때 클라이언트 코드(OrderServiceImpl)는 변경지점이 없음
위의 사례를 통해 객체 지향 설계를 잘 준수하고 있음을 확인할 수 있었습니다.
📌 IoC, DI, 컨테이너
IoC, DI, 컨테이너는 강의 내용을 다음과 같이 요약할 수 있습니다.
- IoC, Inversion of Control, 제어의 역전
- 기존에는 클라이언트 코드에서 구현 객체 생성, 연결, 실행 모든 역할을 수행
- 클라이언트 코드가 제어의 흐름을 컨트롤한다고 볼 수 있음
- AppConfig 등장으로 클라이언트 코드에서는 로직을 실행하는 역할만 담당
- 제어의 흐름에 대한 권한은 AppConfig가 가지게 됨
- 프로그램 제어의 흐름을 외부에서 관리하는 것이 IoC 핵심
- DI, Dependency Injection, 의존관계 주입
- OrderServiceImpl은 DiscountPolicy에 의존
- 의존관계는 정적인 클래스 의존관계, 동적인 객체 의존 관계 둘로 나뉨
> 정적인 클래스 의존관계
import만 보고 쉽게 파악 가능, 실제 실행시점에서 어떤 구현체가 주입되는지 알 수 없음
OrderServiceImpl > MemberRepository, DiscountPolicy
두 개 인터페이스의 실제 구현체가 어떤게 주입되는 지 알 수 없음
> 동적인 객체 의존관계
애플리케이션 실행시점에 실제 생성된 객체 참조가 연결된 의존관계
- IoC, DI 컨테이너
- AppConfig 같이 객체 생성, 관리, 의존관계 연결해주는 역할 수행
- 최근에는 DI 컨테이너라 불림(어샘블러, 오브젝트 팩토리 등으로도 불림)
의존관계 주입(DI)을 사용하면 정적인 클래스 의존관계 변경하지 않고 동적인 객체 인스턴스 의존관계를 쉽게 변경 가능
이게 이번 챕터 강의 내용 핵심인 것 같습니다.
📌 정리
이번 강의 내용은 스프링 핵심 개념인 IoC, DI를 코드로 예시를 보여주며 잘 습득할 수 있어서 좋았습니다. 강의 핵심은 다음과 같습니다.
- 스프링은 객체지향 5가지 원칙을 잘 준수하는 프레임워크 그 중 SRP, DIP, OCP에 주목
- 관심사 분리를 통해 SRP 준수
- 객체 생성, 연결하는 제어 관점 & 기능을 실행하는 사용 관점
- AppConfig (설정파일), IoC 개념 등장 - 의존성 주입(DI)를 통해 DIP 준수
- 추상화에 의존하고 구체화에 의존하지 않는 코드 구성 (OrderServiceImpl과 DiscountPolicy 관계 생각) - IoC와 DI를 통해 OCP 준수
- 다른 할인 정책을 적용할 때(확장) AppConfig 코드만 수정하면 됨 - 의존관계 주입(DI)을 사용하면 정적인 클래스 의존관계 변경하지 않고 동적인 객체 인스턴스 의존관계를 쉽게 변경 가능
'Spring' 카테고리의 다른 글
[Spring] 설정파일과 Bean 사이의 순환참조(circular references) 이슈 및 해결 (0) | 2022.10.31 |
---|---|
[Spring Core #2] 스프링 컨테이너와 스프링 빈 (스프링 핵심 원리 강의정리) (0) | 2022.07.12 |
[Spring] 객체 지향 설계를 극대화한 스프링의 핵심 개념 정리 (0) | 2022.06.20 |
[Spring] 2. jackson을 이용한 data binding 이해하기(생성자, constructor) (7) | 2022.04.26 |
[Spring] 1. jackson을 이용한 data binding 이해하기(ObjectMapper, field & getter/setter) (2) | 2022.04.01 |
댓글
댓글(Github)
다른 글
-
[Spring] 설정파일과 Bean 사이의 순환참조(circular references) 이슈 및 해결
[Spring] 설정파일과 Bean 사이의 순환참조(circular references) 이슈 및 해결
2022.10.31 -
[Spring Core #2] 스프링 컨테이너와 스프링 빈 (스프링 핵심 원리 강의정리)
[Spring Core #2] 스프링 컨테이너와 스프링 빈 (스프링 핵심 원리 강의정리)
2022.07.12 -
[Spring] 객체 지향 설계를 극대화한 스프링의 핵심 개념 정리
[Spring] 객체 지향 설계를 극대화한 스프링의 핵심 개념 정리
2022.06.20 -
[Spring] 2. jackson을 이용한 data binding 이해하기(생성자, constructor)
[Spring] 2. jackson을 이용한 data binding 이해하기(생성자, constructor)
2022.04.26