[Spring] 기본편 #3 - 객체 지향 원리 적용

strongmhk·2023년 5월 2일
0

Spring

목록 보기
3/25
post-thumbnail

새로운 할인 정책 개발


만약 다음과 같은 상황이라고 가정해보자

악덕 기획자 : 서비스 오픈 직전에 할인 정책을 지금처럼 고정 금액 할인이 아니라 좀 더 합리적인 주문금액당 할인하는 정률% 할인으로 변경하고 싶어요. 고정 금액인 1000원보다는 금액에 따라서 10%씩 할인해주는거에요!
순진 개발자 : 제가 처음부터 고정 금액 할인은 아니라고 했잖아요.
악덕 기획자 : 애자일 소프트웨어 개발 선언 몰라요? "계획을 따르기보다 변화에 대응하기를"
순진 개발자 : (난 객체지향 설계 원칙을 잘 준수해서 구현만 잘 갈아끼우면 될거야 ㅎㅎ..)







애자일 소프트웨어 개발 선언




RateDiscountPolicy 추가



src.main.java.Hello.core.discount.RateDiscountPolicy


import Hello.core.member.Member;

public class RateDiscountPolicy implements DiscountPolicy {
    private int discountPercent = 10;

    @Override
    public int discount(Member member, int price){
        if(member.getGrade() == Grade.VIP){
            return price * discountPercent / 100;
        } else {
            return 0;
        }
    }
}



테스트 작성


src.test.java.Hello.core.discount.RateDiscountPolicyTest

package Hello.core.discount;


import Hello.core.member.Grade;
import Hello.core.member.Member;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

class RateDiscountPolicyTest{

    RateDiscountPolicy discountPolicy = new RateDiscountPolicy();

    @Test
    @DisplayName("VIP는 10% 할인이 적용되어야 한다.")
    void vip_o(){
        // given
        Member member = new Member(1L, "memberVIP", Grade.VIP);
        // when
        int discount = discountPolicy.discount(member, 10000);
        // then
        assertThat(discount).isEqualTo(1000);
    }

    @Test
    @DisplayName("VIP가 아니면 할인이 적용되지 않아야 한다.")
    void vip_x() {
        //given
        Member member = new Member(2L, "memberBASIC", Grade.BASIC);
        //when
        int discount = discountPolicy.discount(member, 10000);
        //then
        assertThat(discount).isEqualTo(0);
    }
}

테스트 실행시에는 클래스 옆의 초록색 체크버튼을 누르면 vip_o, vip_x 두 개의 메소드가 모두 실행되고, 아래의 각각 메소드 옆의 초록 체크버튼을 누르면 각각의 메소드에 대해서만 테스트가 실행된다 모두 실행해보겠다!



잘 실행이 된다!





새로운 할인 정책의 적용과 문제점



- 적용(OrderServiceImpl 수정)


src.main.java.Hello.core.order.OrderServiceImpl

public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository = new MemoryMemberRepository();
//    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}

- 문제점 발견

  • 역할과 구현을 충실하게 분리 -> OK
  • 다형성 활용, 인터페이스와 구현 객체 분리 -> OK
  • OCP, DIP같은 객체지향 설계 원칙을 충실히 준수? -> X
    - 주문 서비스 클라이언트(OrderServiceImpl)는 추상화(DiscountPolicy 인터페이스)에 의존하고 있지만, 구현 클래스(RateDiscountPolicy)에도 의존하고 있다





기대했던 의존관계(역할에만 의존)

실제 의존관계(역할과 구현에 모두 의존)

역할(인터페이스)뿐만 아니고 구현(구체 클래스)에도 의존하고있다 - DIP 위반




정책 변경시

할인 정책을 FixDiscountPolicy -> RateDiscountPolicy로 변경시
클라이언트 소스코드인 OrderServiceImpl를 변경해야한다 - OCP 위반







- 해결방안

추상화(인터페이스)에만 의존하도록 코드를 변경해주자


인터페이스에만 의존하도록 코드 변경

public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository = new MemoryMemberRepository();
    //    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
    private DiscountPolicy discountPolicy;

}

  • 구현체가 없는데 어떻게 코드를 실행할 수 있을까?
  • 여기서 문제점은 실행하면 NullPointerException이 발생한다.
  • 이 문제점을 해결하려면 누군가가 클라이언트인 OrderServiceImpl에 DiscountPolicy의 구현 객체를 대신 생성하고 주입해주어야 한다.






관심사의 분리

  • 애플리케이션을 하나의 공연, 각각의 배역(로미오와 줄리엣 역할)을 인터페이스라고 생각해보자. 그런데 여기서 실제 배역(인터페이스)에 맞는 배우(구현체)를 선택하는 것은 누가 하는가?
  • 공연을 할 때 로미오 역할을 누가 할지 줄리엣 역할을 누가할지는 배우들이 정하는게 아니다. 별도의 공연 기획자가 필요하다
  • 공연 기획자는 공연을 구성하고, 담당 배우를 섭외하고, 배역을 할당하는 역할을 가진다.
  • 그런데 여기서는 로미오의 역할(인터페이스)을 맡은 배우(구현체)가 직접 줄리엣 역할을 맡을 배우도 섭외해야하는 다양한 책임을 갖고있다
  • 그렇기에 공연 기획자를 만들고, 배우와 공연 기획자의 책임을 확실히 분리해보자






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;

public class AppConfig {
 	public MemberService memberService() {
 		return new MemberServiceImpl(new MemoryMemberRepository());
 	}
 	public OrderService orderService() {
 		return new OrderServiceImpl(new MemoryMemberRepository(),new FixDiscountPolicy());
 	}
}

역할

  • AppConfig(여기서 Configuration : 구성)는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성

    • MemberServiceImpl
    • MemoryMemberRepository
    • OrderServiceImpl
    • FixDiscountPolicy
  • AppConfig는 생성한 객체 인스턴스의 참조값을 생성자를 통해서 주입(연결)

    • MemberServiceImpl -> MemoryMemberRepository
    • OrderServiceImpl -> MemoryMemberRepository, FixDiscountPolicy





MemberServiceImpl - 생성자 주입


package hello.core.member;

public class MemberServiceImpl implements MemberService {
	
    private final MemberRepository memberRepository;
	
    public MemberServiceImpl(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
	}
	public void join(Member member) {
		memberRepository.save(member);
	}
	public Member findMember(Long memberId) {
		return memberRepository.findById(memberId);
	}
}
  • 설계 변경으로 MemberServiceImpl은 MemoryMemberRepository를 의존하지 않는다!(구체화에 의존 x)
  • 단지 MemberRepository 인터페이스에만 의존한다(추상화에만 의존)
  • MemberServiceImpl입장에서는 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다.
  • 어떤 구현 객체를 주입할지는 오직 외부(AppConfig)에서 결정된다
  • MemberServiceImpl은 이제부터 의존관계에 대한 고민외부에 맡기고 실행에만 집중한다!



클래스 다이어그램

  • 객체의 생성과 연결은 AppConfig가 담당
  • DIP 완성 : MemberServiceImpl은 MemberRepository인 추상에만 의존, 어떤 구현 객체가 들어오는지에 대해서는 신경쓰지 않아도 된다
  • 관심사의 분리 : 객체를 생성하고 연결하는 역할(외부 AppConfig가 담당)과 실행하는 역할(MemberServiceImpl)이 명확히 분리



회원 객체 인스턴스 다이어그램

  • AppConfig객체는 MemoryMemberRepository 생성, 그 참조값을 MemberServiceImpl을 생성하면서 생성자로 전달
  • 클라이언트인 MemberServiceImpl입장에서 보면 의존관계를 외부(AppConfig)에서 주입해주는 것 같아 DI(Dependency Injection) 의존관계 주입 또는 의존성 주입이라 함



OrderServiceImpl - 생성자 주입

package hello.core.order;
import hello.core.discount.DiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
	
    public class OrderServiceImpl implements OrderService {
	
    private final MemberRepository memberRepository;
	private final DiscountPolicy discountPolicy;
	
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
		this.memberRepository = memberRepository;
		this.discountPolicy = discountPolicy;
	}
    
	@Override
	public Order createOrder(Long memberId, String itemName,int itemPrice) {
		Member member = memberRepository.findById(memberId);
		int discountPrice = discountPolicy.discount(member, itemPrice);
		return new Order(memberId, itemName, itemPrice, discountPrice);
	}
    
}
  • 설계 변경으로 OrderServiceImpl은 FixDiscountPolicy에 의존하지 않음
  • 단지 DiscountPolicy 인터페이스에만 의존(추상화에만 의존!)
  • OrderServiceImpl은 생성자를 통해 외부(AppConfig)에서 의존관계를 주입(구현 객체를 파라미터로 전달)해주면 실행에만 집중하면됨
  • OrderServiceImpl에는 MemoryMemberRepository , FixDiscountPolicy 객체를 파라미터로 전달받음(의존관계 주입)



AppConfig 실행


사용 클래스 - MemeberApp


package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;

public class MemberApp {
		public static void main(String[] args) {
			AppConfig appConfig = new AppConfig();
			MemberService memberService = appConfig.memberService();
			Member member = new Member(1L, "memberA", Grade.VIP);
			memberService.join(member);
			
            Member findMember = memberService.findMember(1L);
			System.out.println("new member = " + member.getName());
			System.out.println("find Member = " + findMember.getName());
		}
}


사용 클래스 - OrderApp

package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.order.Order;
import hello.core.order.OrderService;

public class OrderApp {
	public static void main(String[] args) {
		AppConfig appConfig = new AppConfig();
		MemberService memberService = appConfig.memberService();
		OrderService orderService = appConfig.orderService();
		
        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 = " + order);
	}
}


테스트 코드 오류 수정


class MemberServiceTest {
	// MemberService memberService = new MemberServiceImpl(); // test 코드에서 직접 의존관계 주입 x  
    MemberService memberService;
	
    @BeforeEach
	public void beforeEach() {
		AppConfig appConfig = new AppConfig();
		memberService = appConfig.memberService();
	}
}


class OrderServiceTest {
	// MemberService memberService = new MemberServiceImpl();
    // OrderService orderService = new OrderServiceImpl();
	MemberService memberService;
	OrderService orderService;
	
    @BeforeEach
	public void beforeEach() {
		AppConfig appConfig = new AppConfig();
		memberService = appConfig.memberService();
		orderService = appConfig.orderService();
	}
}
  • 각 테스트 코드에서 @BeforeEach아래에 있는 메소드는 테스트가 실행되기 전에 호출돼 먼저 실행된다

정리

  • AppConfig를 통해서 관심사를 확실하게 분리 (애플리케이션의 환경설정)
  • AppConfig는 공연 기획자(배역, 즉 인터페이스에 맞는 담당 배우를 선택), 각각의 Impl 클래스들은 배우와 같이 연기만 하면됨(실행에만 집중)



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;

public class AppConfig {
	public MemberService memberService() {
		return new MemberServiceImpl(new MemoryMemberRepository());
	}
	public OrderService orderService() {
		return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
	}
}

현재 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;

public class AppConfig {
	public MemberService memberService() {
		return new MemberServiceImpl(memberRepository());
	}
    
	public OrderService orderService() {
    	return new OrderServiceImpl(memberRepository(), discountPolicy());
	}
    
	public MemberRepository memberRepository() { 
		return new MemoryMemberRepository();
	} // MemberRepository(회원 저장소)를 주입해주는 역할, 
    
	public DiscountPolicy discountPolicy() {
		return new FixDiscountPolicy();
	} // DiscountPolicy(할인 정책)를 주입해주는 역할
    
}
  • new MemoryMemberRepository() 이 부분이 중복 제거, 이제 MemoryMemberRepository를 다른 구현체로 변경할 때 한 부분만 변경하면 됨
  • AppConfig를 보면 역할과 구현 클래스가 한눈에 들어옴, 애플리케이션의 전체 구성을 빠르게 파악할 수 있음




할인 정책 변경

AppConfig를 만듦으로써 애플리케이션이 사용 영역과, 객체를 생성하고 구성하는 영역으로 분리됨



정률 할인 정책으로 변경

  • 정액(FixDiscountPolicy) -> 정률(RateDiscountPolicy) 할인 정책으로 바꾸려면 어떤 부분만 바꾸면 되겠는가?
  • 클라이언트 소스코드인 OrderServiceImpl을 변경하지 않고, AppConfig만 변경하면 된다!




할인 정책 변경 소스코드(AppConfig)

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;

public class AppConfig {
	public MemberService memberService() {
		return new MemberServiceImpl(memberRepository());
	}
	public OrderService orderService() {
		return new OrderServiceImpl(memberRepository(), discountPolicy());
	}
	public MemberRepository memberRepository() {
		return new MemoryMemberRepository();
	}
	public DiscountPolicy discountPolicy() {
		// return new FixDiscountPolicy(); // 이 부분만 변경하면 끝!
		return new RateDiscountPolicy();
	}
}
  • AppConfig에서 할인 정책 역할을 담당하는 구현을 FixDiscountPolicy -> RateDiscountPolicy 객체로 변경
  • 할인 정책을 변경해도, 애플리케이션의 구성 역할을 담당하는 AppCongfig만 변경하면 됨
  • 클라이언트 코드인 OrderServiceImpl를 포함한 사용 영역의 어떤 코드도 변경할 필요 X
  • 구성 영역은 당연히 변경 -> 공연 기획자(AppConfig)는 공연 참여자(구현 객체)들을 모두 알아야 함




좋은 객체 지향 설계의 5가지 원칙 적용


위의 과정들을 통해 3가지 SRP, DIP, OCP 적용됨


SRP 단일 책임 원칙

  • 하나의 클래스는 하나의 책임만 가져야 함
  • 클라이언트 객체는 직접 구현 객체를 생성, 연결, 실행하는 다양한 책임을 가지고 있었음
  • 관심사 분리 -> AppConfig가 구현 객체를 생성, 연결하는 책임을 담당
  • 클라이언트 객체는 실행하는 책임만 담당함으로써 SRP 만족

DIP 의존관계 역전 원칙

  • 추상화에 의존해야지, 구체화에 의존하면 안됨
  • 새로운 할인 정책(DiscountPolicy)을 적용하려고 하니, 클라이언트의 코드(OrderServiceImpl)를 변경해야하는 상황 발생, 역할(인터페이스, DiscountPolicy)에 의존하는 것 같았지만 구체화 구현 클래스(FixDiscountPolicy)에도 의존하고 있었음
  • 하지만 클라이언트 코드는 인터페이스만으로는 아무것도 실행할 수 없음
  • AppConfig가 구체화 구현 클래스(FixDiscountPolicy) 클라이언트 코드 대신 생성해 외부에서 클라이언트 코드에 의존관계를 주입

OCP 개방 폐쇄 원칙

  • 소프트웨어 요소는 확장에는 열려 있으나, 변경에는 닫혀 있어야 한다
  • 애플리케이션을 사용 영역구성 영역으로 나눔
  • AppConfig가 의존관계를 FixDiscountPolicy -> RateDiscountPolicy로 변경해서 클라이언트 코드에 주입하므로 클라이언트 코드는 변경할 필요 x
  • 소프트웨어 요소(할인 정책)를 새롭게 확장(여러가지의 할인 정책으로 나눔)해도 위의 클래스 다이어그램에서 봤던 사용 영역의 변경은 닫혀있음!



IoC, DI, 그리고 컨테이너


IoC(Inversion of Control, 제어의 역전)

  • 기존 프로그램은 이 포스팅의 맨 처음에 마주친 것 처럼, 할인 정책을 클라이언트 코드에서 직접 정해야하고, 연결하고, 실행하는 것까지 제어의 흐름을 클라이언트 코드가 모두 담당했었다
  • 반면에 AppConfig가 등장하면서 구현 객체는 자신의 로직을 실행하는 역할만을 담당한다(프로그램의 제어 흐름은 AppConfig가 가져간다, 예를 들어 OrderServiceImpl은 DiscountPolicy 인터페이스를 호출하지만 정액(Fix) 할인인지, 정률 할인(Rate)이 적용되는지는 모른다
  • 즉 제어의 흐름을 직접 제어하는 것이 아닌 AppConfig가 외부에서 관리해준다 이것을 제어의 역전(IoC)라고 한다

프레임워크 vs 라이브러리

  • 프레임워크 : 프레임워크가 내가 작성한 코드를 제어하고 실행(JUnit)
  • 라이브러리 : 내가 작성한 코드가 직접 제어의 흐름을 담당


DI(Dependency Injection, 의존관계 주입)

  • 클라이언트 코드(OrderServiceImpl)는 어떤 구현 객체(정률할인인지 정액할인인지, 할인 정책의 종류)가 사용될지 모르며 인터페이스(할인 정책이라는 규정)에만 의존한다
  • 의존관계는 정적인 클래스 의존관계실행 시점에 결정되는 동적인 객체(인스턴스) 의존 관계 둘을 분리해서 생각해야한다




정적인 클래스 의존관계

  • 클래스가 사용하는 import 코드만 보고 의존관계를 쉽게 판단가능
  • 애플리케이션이 실행하지 않아도 분석할 수 있음
  • 아래의 클래스 다이어그램을 보면, OrderServiceImpl은 MemberRepositoy와 DiscountPolicy에 의존함을 알 수 있음(회원 저장소의 유형과 할인 유형을 알아야 OrderService 실행 가능)


클래스 다이어그램





동적인 객체 인스턴스 의존관계

  • 애플리케이션 실행 시점(런타임)에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계
  • 객체 인스턴스를 생성, 그 참조값을 전달해서 연결
  • 클라이언트 코드를 변경하지 않고, 클라이언트가 호출하는 대상의 타입 인스턴스(할인 정책의 종류)를 변경할 수 있음
  • 의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고(클래스 다이어그램을 그대로 유지하면서), 동적인 객체 인스턴스를 쉽게 변경할 수 있다(회원 저장소의 유형이나 할인 정책의 종류를 바꿀 수 있다)




IoC 컨테이너, DI 컨테이너

  • AppConfig처럼 객체를 생성하고 관리하며, 의존관계를 연결해주는 것을 IoC 컨테이너 또는 DI 컨테이너라 한다
  • 의존관계 주입에 초점을 맞추어 최근에는 주로 DI 컨테이너라 한다
  • 어샘블러, 오브젝트 팩토리로 불리기도 한다






스프링으로 전환!

지금까지는 순수 자바 코드만으로 DI를 적용했다(DI를 직접 구현해보며 DI가 무엇인지, 적용되는 원리를 이해하기 위함)



AppConfig 스프링 기반으로 변경

package hello.core;
import hello.core.discount.DiscountPolicy;
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 {
	
    @Bean
	public MemberService memberService() {
		return new MemberServiceImpl(memberRepository());
	}
    
	@Bean
	public OrderService orderService() {
		return new OrderServiceImpl(memberRepository(), discountPolicy());
	}
    
	@Bean
	public MemberRepository memberRepository() {
		return new MemoryMemberRepository();
	}

	@Bean
	public DiscountPolicy discountPolicy() {
		return new RateDiscountPolicy();
	}
}
  • AppConfig에 설정을 구성한다는 뜻의 @Configuration을 붙임
  • 각 메소드에 @Bean을 붙여줌 이렇게하면 스프링 컨테이너에 스프링 빈(각각의 객체)으로 등록


MemberApp에 스프링 컨테이너 적용

package hello.core;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
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 = new AnnotationConfigApplicationContext(AppConfig.class);
		MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
    
		Member member = new Member(1L, "memberA", Grade.VIP);
		memberService.join(member);
	
    	Member findMember = memberService.findMember(1L);
		System.out.println("new member = " + member.getName());
		System.out.println("find Member = " + findMember.getName());
	}
}


OrderApp에 스프링 컨테이너 적용

package hello.core;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.order.Order;
import hello.core.order.OrderService;
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);
        
		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 = " + order);
	}
}



스프링 컨테이너

  • ApplicationContext를 스프링 컨테이너라 한다
  • 기존에는 개발자가 AppConfig를 사용해서 직접 객체를 생성하고 DI를 했지만, 이제부터는 스프링 컨테이너를 통해서 사용한다
  • 스프링 컨테이너는 @Configuration 이 붙은 AppConfig를 설정(구성) 정보로 사용하고, 여기서 @Bean이라 적힌 메소드를 모두 호출해 반환된 객체를 스프링 컨테이너에 Key(메소드의 이름) : value(참조 객체) 형식으로 등록한다
  • 기존 : MemberService memberService = appConfig.memberService();
    스프링 컨테이너 : MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
  • 기존 : 개발자가 자바코드로 직접 모든 것을 함
    스프링 컨테이너 : 스프링 컨테이너에 객체 등록, 스프링 컨테이너에서 스프링 빈을 찾아서 사용








이번 강의 총정리

  • 순수 자바 코드로 SRP, DIP, OCP를 지키도록 AppConfig 구현
  • DI의 이해(의존관계의 주입시 역할과 구현의 분리를 위해 클라이언트 코드 내부에서 주입을 해주는 것이 아닌 외부의 AppConfig에서 의존관계를 주입하도록)
  • 스프링 프레임워크를 이용해 제어의 흐름을 스프링 컨테이너에게 맡기면서
    다양한 이점들을 챙겨오자!




한 줄 요약

구현체를 변경시 사용 영역(클라이언트 코드)까지 변경해야되는 상황이 일어나지 않도록 역할과 구현을 외부(AppConfig)에서의 의존관계 주입(DI)으로 해결하고 DI와 객체 관리의 편의성을 위해 스프링 컨테이너를 이용하자!







코드가 약간 더 복잡해진 것 같은데, 스프링 컨테이너를 사용하면 어떤 장점이 있을까?
-> 스프링 컨테이너가 빈을 관리해주면서 얻을 수 있는 장점들은 다음 강의부터 차차 직접해보면서 학습해보도록 하자!

profile
저 커서 개발자가 될래요!

0개의 댓글