SRP, DIP, OCP 적용
SRP 단일 책임 원칙: "한 클래스는 하나의 책임만 가져야 한다."
- 클라이언트 객체는 직접 구현 객체를 생성하고, 연결하고, 실행하는 다양한 책임을 가지고 있음
- SRP 단일 책임 원칙을 따르면서 관심사를 분리함
- 구현 객체를 생성하고 연결하는 책임은 AppConfig가 담당
- 클라이언트 객체는 실행하는 책임만 담당
개선된 클래스 다이어그램
MemberServiceImpl & OrderServiceImpl - 객체 지정은 AppConfig에게 맡기며 생성자를 둠
AppConfig - 환경설정 모두 담당
package hello.core;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.springframework.core.OrderComparator;
public class AppConfig {
public MemberService memberService(){
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService(){
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
DIP 의존 관계 역전 원칙: "프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안 된다."
- 새로운 할인 정책을 개발하고, 적용하려고 하니 클라이언트 코드도 함께 변경해야 했다.
왜냐하면 기존 클라이언트 코드(OrderServiceImpl)는 DIP를 지키며 DiscountPolicy 추상화 인터페이스에 의존하는 것 같았지만, FixDiscountPolicy 구체화 구현 클래스에도 함께 의존했다.
- 클라이언트 코드가 DiscountPolicy 추상화 인터페이스에만 의존하도록 코드를 변경했다.
- 하지만 클라이언트 코드는 인터페이스만으로는 아무것도 실행할 수 없다.
- AppConfig가 FixDiscountPolicy 객체 인스턴스를 클라이언트 코드 대신 생성해서 클라이언트 코드에 의존 관계를 주입했다.
이렇게 해서 DIP 원칙을 따르면서 문제도 해결했다.
AppConfig
package hello.core;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.springframework.core.OrderComparator;
public class AppConfig {
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
private MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public DiscountPolicy discountPolicy(){
return new FixDiscountPolicy();
}
}
OCP: "소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다."
- 다형성 사용하고 클라이언트가 DIP를 지킴
- 애플리케이션을 사용 영역과 구성 영역으로 나눔
- AppConfig가 의존관계를 FixDiscountPolicy -> RateDiscountPolicy로 변경해서
클라이언트 코드(OrderServiceImpl)에 주입하므로 클라이언트 코드는 변경하지 않아도 됨.
=> 소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경은 닫혀 있다.(OCP)
스프링 빈 등록 방법
다음과 같은 의존관계를 가능케 하는 방법이라고 보면 된다.
스프링 빈으로 등록해서 쓰면 많은 이점이 있기 때문에 보통 등록해서 사용한다.
cf) 스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 기본으로 싱글톤으로 등록한다.
쉽게 말해 helloController, memberService, memberRepository 각각 하나씩 등록한다는 뜻이다.
memberService가 아니라 orderService가 memberRepository를 내놓으라고 하면 같은 객체를 내어놓는다는 것이다.
이렇게 메모리 절약 가능. 메모리 도출할 때 좋음


컨테이너에 빈을 등록하고 빈들은 그 안에서 서로 의존관계를 가진다.
1. 컴포넌트 스캔을 통한 자동 의존관계 설정: @service, @controller, @repository 붙여서 함
-> 스프링이 자동으로 스프링 컨테이너에 스프링 빈으로 등록


@Service 안에 들어가면 @Component 등록돼있음
=> @Component 애노테이션이 있으면 스프링 빈으로 자동 등록됨
2. Java 코드로 직접 스프링 빈 등록하기
1. 컴포넌트 스캔을 통한 자동 의존관계 설정
회원을 repository에 저장하고 꺼내오고 하려면 member controller을 만들어야 함
member controller은 MemberService를 통해 회원가입하고 조회할 수 있어야 함(member controller은 memberservice에 의존)
MemberController 클래스 만들기
@Controller:
Spring 창에서 Container라는 통이 보일 거다.
spring container는 MemberController 객체를 생성해 spring에 넣어둬 관리함
다음과 같이 new 객체를 생성하면 다른 클래스에서도 사용할 수 있기 때문에 ...
private final MemberService memberService = new MemberService;
다음과 같이 생성자로 연결해준다.
@Autowired:스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어줌 (의존관계를 외부에서 넣어주는 Dependency Injection)
ex. memberService라는 객체와 spring container에 있는 MemberService를 연결해줌
@Autowired
public MemberController(MemberService memberService){
this.memberService = memberService;
}
helloController는 spring에 등록돼있다.
MemberService는 @가 없는 그냥 순수한 java 클래스이므로 자신의 역할을 알지 못한다.
@Service를 넣어줌
@Service: 스프링에 서비스 관련된 것이 올라오면 MemberServic와 연결해줌.
마찬가지로 MemoryMemberRepository에는 @Repository를 써준다.
이렇게 정형화된 패턴인 controller, repository, service는 @controller, @repository, @service라는 어노테이션을 붙여주어
관련된 기능이 필요해보일 때 자동으로 연결되도록 해주어야 한다.
다음과 같이 memberService는 memberRepository를 필요로 하기 때문에
스프링이 MemberService를 생성하여 스프링 컨테이너에 등록하면서
생성자인 @Autowired 부분을 호출 -> 매개변수인 MemberRepository도 불러와 주입해주는데
현재는 MemoryMemberRepository가 구현체로 있기 때문에 이를 주입해준다.
2. Java 코드로 직접 스프링 빈 등록하기
하나하나 직접 스프링에 등록하는 방법
@Bean: 스프링 빈에 등록하라는 어노테이션
빈에 등록하기 위해 따로 클래스를 만들어준다 -> "SpringConfig"
-> @Bean을 사용해 코드 작성
memberService와 memberRepository를 스프링 빈에 등록
-> 스프링 빈에 등록돼있는 memberRepository를 MemberService에 넣어줌
그럼 다음 관계가 완성된다.
@Controller은 어쩔 수 없이 써줘야 한다..
<생성자 주입>
이런 식으로 생성자로 주입해주는 방법
package hello.hellospring;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfig {
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
// 다른 종류의 구현체로 바꾸고 싶다면 여기만 바꿔주면 됨
//return new DBMemberRepository();
}
}
<기억해둘 것>
- 실무에서는 주로 정형화된 컨트롤러, 서비스, 리스지토리같은 코드는 컴포넌트 스캔 사용
- 정형화되지 않거나, 상황에 따라 구현 클래스를 변경해야 한다면 설정을 통해 스프링 빈으로 등록한다.
(주의) @Autowired 를 통한 DI는 helloController, MemberService 등과 같이 스프링이 관리하는 객체에서만 동작
스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않음..
'학교 강의 > sw자가학습' 카테고리의 다른 글
[Day14] 스프링 부트 3 자바 백엔드 개발 입문 19장 (1) | 2025.07.16 |
---|---|
[day12] 코딩 자율학습 스프링부트 3 자바 백엔드 개발 입문 15-16장 (2) | 2025.07.14 |
[day11] 코딩 자율학습 스프링부트 3 자바 백엔드 개발 입문 13-14장 (2) | 2025.07.13 |
[day10] 코딩 자율학습 스프링부트 3 자바 백엔드 개발 입문 11-12장 (0) | 2025.07.12 |
[Day9] 스프링 부트 3 자바 백엔드 개발 입문 9~10장 (1) | 2025.07.11 |