인프런/스프링핵심원리(기본)

7) ★ 관심사의 분리, AppConfig(리팩토링 전) , 생성자 주입,의존성 주입(의존관계 주입)

backend dev 2022. 12. 9.

 

AppConfig.class

어플리케이션의 전체 동작 방식을 구성(config -> configuration -> 구성,환경설정)하기 위해 , 

구현 객체를 생성하고, 연결하는 책임을 가지는 클래스

 

 

이전에는 오더서비스구현체클래스 안에서 

public class OrderServiceImpl implements OrderService {

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

다음과 같이 구현체를 생성하고 적용해줬는데 이것은 구현체를 의존하는것이므로 

위의 배우 예시를 들면

배우(오더서비스임플)가 다른 배역의 배우(메모리멤버리포지토리)를 선택하는것이므로 올바르지 않은 코드이다.

 

그래서 AppConfig (공연 기획자) 안에서 어떤 구현체를 써야할것인지를 설정해준다.

 AppConfig안에서 어플리케이션 환경구성,환결설정에 관한건 다 한다.

 

 

 

현재 AppConfig

public class AppConfig {

    public MemberService memberService() {
        return new MemberServiceImpl();
    }

}

AppConfig안에 다음과 같이 멤버서비스 구현체를 생성해서 리턴해주는 멤버서비스 생성 메소드를 적어주었다.

 

그리고 

public class MemberServiceImpl implements MemberService {
 
    private final MemberRepository memberRepository = new MemoryMemberRepository();

현재 멤버서비스 구현클래스는 다음과 같이 멤버리포지토리 인터페이스 변수를 이용해서 구현체를 생성해서 넣고있는데

 

그 부분을 다음과 같이 수정한다. (멤버서비스 구현클래스 수정)

public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

이렇게 수정하기 전에는 멤버서비스 구현 클래스안에서 인터페이스와 구현체 둘다 의존했지만

이렇게 수정한 후로는 멤버서비스 구현 클래스안에서는 인터페이스만 의존하게 된다.

이제 MemberServiceImpl에서는 메모리리포지토리의 구현체는 몰라도 된다. 즉 추상화에만 의존한다. == DIP를 준수함

 

구현체는 생성자를 통해 들어온다 (멤버리포지토리 인터페이스 변수에 담긴 구현체 객체를 이용해서)

 

 

 

 

그렇다면 다시 AppConfig를 수정해주자

(멤버서비스임플의 생성방식이 바뀌었으니 ( 멤버리포지토리를 전달인자로 가지게끔 바뀌었으니))

public class AppConfig {

    public MemberService memberService() {
        return new MemberServiceImpl(new MemoryMemberRepository());
    }

}

이렇게 AppConfig에서 멤버서비스구현체를 생성할때 멤버리포지토리 구현체를 넣어주니까

MemberServiceImpl.java(멤버서비스구현체)에서는 코드상 멤버리포지토리의 인터페이스만 의존하면된다. == 추상화에만 의존한다 == DIP를 준수한다.

 

생성자를 통해서 객체를 넣어주는것 ==> 생성자 주입이라고 한다.

 

 

 

단축키

해당 객체파일로 이동하고싶다면 그 객체위에 커서를 냅두고

컨트롤 + B  => 어떤 객체의 파일로 이동하고싶을때 컨트롤 + 마우스왼쪽클릭하는것과 같은 동작을 한다.

 

 

 

 

OrderService 구현클래스도 수정

오더서비스 구현클래스도 인터페이스만 의존하게끔 == 추상화만 의존하게끔 수정하자

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;
    }

다음과 같이 인터페이스만 의존하고 , 해당 인터페이스의 구현체는 생성자로서 넣어주게끔 하였다. (밖에서 넣어주게끔)

 

final은 변수를 생성할때 초기화 시켜주거나 , 생성자로 받을때 오류가 나지않는다.

어떻게 주입되는지 밑에 설명

 

 

AppConfig에 오더서비스 구현체 생성해주는 메소드 추가

public class AppConfig {

    public MemberService memberService() {
        return new MemberServiceImpl(new MemoryMemberRepository());
    }

    public OrderService orderService() {
        return new OrderServiceImpl(new MemoryMemberRepository(), new RateDiscountPolicy());
    }

}

 

 

AppConfig 추가하고 , 코드 수정 후 클래스 다이어그램

이제 멤버서비스임플은 추상화에만 의존하고 , 구현체 생성 및 연결은 AppConfig가 담당한다.

 

의존성 주입 방식 그림,설명

참조값 == 해당 객체의 주소값 

참조변수 == 객체를 저장하는 변수 == 포인터 변수 ,배열처럼 주소값을 저장함  (c언어의 포인터 느낌)

 

 

위의 그림 설명 [ 의존성 주입되는 방식 설명]

 AppConfig가 멤버서비스구현체를 만들기전에 , 매개변수로 필요한 메모리멤버구현체를 생성한다. (생성)

-> 생성된 메모리구현체의 참조값(주소값)을 주입해서 멤버서비스구현체를 생성해준다.  ( 생성 + 주입 )

 

의존관계를 외부에서 주입 ==> 외부에서 메모리멤버의 구현체를 주입해서 생성해주니까 자동적으로 멤버서비스 구현체가 생성될때 자신은 인터페이스만 의존하니까(추상화만 의존) 어떤 구현체를 의존해야할지 몰랐는데 

밖에서 구현체를 주입해서 자신을 생성해주니 어떤 구현체를 의존해야할지 알게됨 -> 의존관계 주입, 의존성 주입

 

연극 예시로 들면

어떤 배우(멤버서비스 구현체)는 다른 역할(멤버리포지토리인터페이스)의 배우(멤버리포지토리 구현체)가 어떤 사람이던간에 자신의 역할을 하면된다. -> 멤버서비스 구현체(멤버서비스임플)이 추상화를 의존하고 , 외부에서 의존관계를 주입받기에  멤버리포지토리 구현체가 바뀌어도 코드 수정이 필요없고 , 자신의 역할만(자신의 메소드 구현) 잘하면 된다. 

 

 

테스트!

AppConfig를 만들고 관련된 코드를 수정해주었다.

이전에 MemberApp(클라이언트)로 테스트했던것처럼 테스트를 해보자.

 

MemberApp.java

public class MemberApp {

    public static void main(String[] args) {
        AppConfig appConfig = new AppConfig(); //AppConfig객체생성
        MemberService memberService = appConfig.memberService(); //AppConfig에서 멤버서비스를 만들어준다. (AppConfig에서 설정한 멤버서비스임플구현체가 들어갈것이다)
        //그래도 밑에 동작이 잘 작동하는지 체크

        Member memberA = new Member(1L, "memberA", Grade.VIP); //테스트용 멤버생성
        memberService.join(memberA); //가입메소드 사용

        Member findMember = memberService.findMember(1L); // 정상 가입됬는지 findMember 메소드를 사용해본다.
        System.out.println("new member = " + memberA.getName());
        System.out.println("find Member = " + findMember.getName());
    }

}

잘 동작하는것을 확인할 수 있다.  (OrderApp도 똑같이 수정해주면 잘 동작한다)

 

OrderApp.java

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);
    }

}

 

MemberApp, OrderApp 둘다 이제 구현체에 의존하지않고 인터페이스에 의존한다. (AppConfig를 사용함으로서)

 

 

 

테스트코드도 수정

public class MemberServiceTest {

    MemberService memberService;

    @BeforeEach
    public void beforeEach() {
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
    }
public class OrderServiceTest {

    MemberService memberService;
    OrderService orderService;

    @BeforeEach
    public void beforeEach() {
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
        orderService = appConfig.orderService();
    }

 

 

 

정리

 

 

다음 강의는 AppConfig 리팩토링

댓글