반응형
# 객체 지향 원리 적용_2
- 기존에 적용했던 정액할인 정책(고정 금액 할인)에서 새로운 할인 정책인 정률 할인 정책(금액에 따른 비율로 할인)으로 변경.
- 할인 정책을 변경하게 되면 클라이언트 코드인 주문 서비스 구현체를 변경해야 하는 문제점이 발생했었다. (DIP 위반)
- 문제점을 해결하기 위해 관심사를 분리(AppConfig 활용)
## 좋은 객체 지향 설계의 5가지 원칙 적용
- 현재 작업된 소스에서 SRP, DIP, OCP 가 적용되어있다.
SRP : 단일 책임의 원칙
- 핵심 : 한 클래스는 하나의 책임만 가져야 한다.
- 기존에 클라이언트 객체는 직접 구현 객체를 생성하는 등 많은 책임이 존재했다.
- SRP 적용하여 관심사를 분리 -> AppConfig 가 구현 객체 생성, 연결의 책임을 가지게 됨.
- 클라이언트 객체의 경우 실행에 대한 책임만 담당.
DIP : 의존관계 역전의 원칙
- 핵심 : 프로그래머는 추상화에 의존, 구체화에 의존하면 안된다. (의존성 주입은 해당 원칙을 따르는 방법 중 하나임)
- AppConfig 생성하여 외부에서 객체에 대한 의존 관계를 주입. (객체가 추상화에 의존해야 가능한 것)
OCP
- 핵심 : 소프트웨어의 요소는 확장에는 열려있고, 변경에는 닫혀있어야 한다.
- 다형성을 사용, 클라이언트가 DIP를 지킴.
- 애플리케이션을 사용 영역, 구성 영역으로 분리.
- APpConfig 가 의존관계를 FixDiscountPolicy 에서 RateDiscountPolicy 로 변경하여 클라이언트에 주입하여 클라이언트의 코드는 변경하지 않아도 되었음. (소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경은 닫혀있게 됨(변경할 필요 없었음))
## IoC, DI 그리고 컨테이너
IoC (Inversion of Control) : 제어의 역전
- 제어권이 뒤바뀜.
- 기존 : 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성, 연결, 실행. (프로그램의 제어 흐름을 스스로 조종)
- 변경 : AppConfig 등장으로 구현 객체는 자신의 로직을 실행만 하는 역할을 함. (프로그램 제어 흐름이 AppConfig 에 존재)
- 정리 : 프로그램의 제어 흐름을 직접 제어하는 것이 아닌, 외부에서 관리하는 것을 의미.
- 프레임워크 vs 라이브러리
프레임워크가 내가 작성한 코드를 제어, 대신 실행하면 프레임워크가 맞음 (JUnit)
내가 작성한 코드가 직접 제어의 흐름을 담당하는 경우는 라이브러리.
DI (Dependency Injection) : 의존 관계 주입
- 구현체가 인터페이스에 의존, 실제 어떤 구현 객체가 사용될지 모름.
- 정적 클래스 의존 관계, 실행 시점에 결정되는 동적 객체(인스턴스) 의존 관계를 분리해서 생각해야 함.
- 정적 클래스 의존관계
클래스가 사용하는 import 코드만 보고 의존관계 판단 가능.
정적 의존관계는 애플리케이션 실행 없이 분석 가능. (클래스 다이어그램만 보고 판단가능)
- 동적인 객체 인스턴스 의존 관계
애플리케이션 실행 시점에서 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계를 의미. (객체 다이어그램)
- 의존관계 주입 : 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성, 클라이언트에 전달하여 클라이언트와 서버의 실제 의존관계가 연결되는 것.
- 객체 인스턴스를 생성, 그 참조값을 전달해서 연결함.
- 정리 : 의존관계 주입 사용 시 클라이언트 코드 변경 없이 클라이언트가 호출하는 대상의 타입 인스턴스 변경 가능하다. 그리고 정적 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경 가능하다.
DI 컨테이너 (= IoC 컨테이너)
- AppConfig 처럼 객체 생성, 의존관계 연결, 관리해주는 것을 의미.
- 의존관계 주입에 초점을 맞춰 DI 컨테이너라고 함. (또는 어샘블러, 오브젝트 팩토리 등으로 불리기도 함)
## 스프링으로 전환 (스프링 컨테이너 사용)
- 지금까지의 작업물은 순수 자바 코드로 작업 한 것.
- 순수한 자바 코드만으로 DI 적용한 것을 스프링으로 변경.
AppConfig
- @Configuration
- @Bean
package hello.core;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
// memberService 역할
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
// memberRepository 역할
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
// orderService 역할
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
// discountPolicy 역할
@Bean
public DiscountPolicy discountPolicy() {
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
MemberApp
- 스프링의 컨테이너 : ApplicationContext
- 아래와 같이 해주면 AppConfig에 있는 것들을 스프링 컨테이너에 객체 생성 해서 관리하게 함.
// 스프링 컨테이너 : ApplicationContext
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
// 기존
// AppConfig appConfig = new AppConfig();
// MemberService memberService = appConfig.memberService();
// 스프링 컨테이너 : ApplicationContext
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MemberApp {
public static void main(String[] args) {
// 기존
// AppConfig appConfig = new AppConfig();
// MemberService memberService = appConfig.memberService();
// 스프링 컨테이너 : ApplicationContext
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
// MemberService memberService = new MemberServiceImpl();
Member memberA = new Member(1L, "memberA", Grade.VIP);
memberService.join(memberA);
Member findMember = memberService.findMember(1L);
System.out.println("member : " + memberA.getName());
System.out.println("findMember : " + findMember.getName());
}
}
OrderApp
- MemberApp 에 적용한 것처럼 ApplicationContext 사용.
package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.order.Order;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class OrderApp {
public static void main(String[] args) {
// 기존
// AppConfig appConfig = new AppConfig();
// MemberService memberService = appConfig.memberService();
// OrderService orderService = appConfig.orderService();
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
// MemberService memberService = new MemberServiceImpl();
// OrderService orderService = new OrderServiceImpl();
Long memberId = 1L;
Member member = new Member(memberId, "memberA", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "itemA", 10000);
System.out.println("주문 = " + order.toString());
}
}
스프링 컨테이너
- 스프링 컨테이너 : ApplicationContext
- 기존 : AppConfig 사용해서 직접 객체를 생성 및 의존성 주입 (DI) 진행. (AppConfig 통해서 진행)
- 변경 : 스프링 컨테이너를 활용. (AppicationContext 통해서 진행)
- 스프링 빈의 경우 @Bean 이 붙은 메서드 명을 스프링 빈의 이름으로 사용.
- 스프링 빈 찾기 : appcationContext.getBean("bean이름", 타입)
반응형
'인프런 강의 학습 > 스프링 핵심 원리(기본편)' 카테고리의 다른 글
재학습_스프링 컨테이너와 스프링 빈_2 (0) | 2022.09.12 |
---|---|
재학습_스프링 컨테이너와 스프링 빈_1 (0) | 2022.09.07 |
재학습_객체 지향 원리 적용_1 (0) | 2022.09.02 |
재학습_예제 프로젝트 진행_2 (0) | 2022.08.27 |
재학습_예제 프로젝트 진행_1 (0) | 2022.08.24 |