[스프링 핵심원리] - 2.스프링 핵심 원리 이해2(객체지향 원리 적용) (2)

Chooooo·2022년 10월 6일
0
post-thumbnail

이 글은 강의 : 김영한님의 - "스프링 핵심원리 - 기본편"을 듣고 정리한 내용입니다. 😁😁


관심사의 분리

다시 처음에 생각했던 부분을 생각해보자.
공연(애플리케이션)에서 각각 인터페이스는 배역이라고 할 수 있다. 그렇다면 이 배역을 연기하는 배우는 구현 객체일 것이다.
그런데 실제 배역을 맞는 배우를 선택하는 것은 누가 하는가?

현재까지 작성한 코드는 로미오와 줄리엣이라는 공연(애플리케이션)에서, 로미오 역할(인터페이스)을 하는 배우인 레오나르도 디카프리오(구현객체) 가 줄리엣 역할(인터페이스)을 하는 여자 주인공(구현객체)를 직접 캐스팅하는 것과 마찬가지인 내용이다. 디카프리오는 직접 공연도 하고, 여자 주인공도 캐스팅하는 다양한 역할을 하고 있는 셈이다.

분리

  • 배우는 자신이 맡은 배역을 수행하는 역할만 수행하는 데 집중하는 것이 맞다.
  • 디카프리오는 상대 여자 주인공이 어떤 사람이 되더라도 동일하게 공연을 할 수 있어야 한다.
  • 공연을 구성하고, 담당 배우를 섭외하고, 역할에 맞는 배우를 지정하는 것 책임을 갖는 공연 기획자가 필요하다
  • 공연 기획자를 만들어서, 배우와 공연 기획자의 책임을 분리해서 작성해보도록 하자.

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

public class AppConfig {
    // 생성자 주입
    public MemberService memberService() {
        return new MemberServiceImpl(new MemoryMemberRepository());
    }
    // 생성자 주입
    public OrderService orderService() {
        return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
    }
}

AppConfig는 애플리케이션의 동작에 필요한 구현객체를 생성한다.

  • MemberServiceImpl
  • MemoryMemberRepository
  • OrderServiceImpl
  • FixDiscountPolicy

그리고 생성한 객체의 인스턴스 참조(레퍼런스)을 생성자를 통해서 주입(연결한다).

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

MemberServiceImpl - 생성자 주입

package hello.core.member;

public class MemberServiceImpl implements MemberService{
    // MemberServiceImpl 은 MemberRepository 인터페이스에만 의존
    private final MemberRepository memberRepository;

    // 의존성 주입(DI)
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Override
    public void join(Member member) {
        memberRepository.save(member);

    }

    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

기존에는 MemberServiceImpl이 MemoryMemberRepository를 직접 생성했지만 설계 변경 후,
MemberServiceImpl → MemoryMeberRepository 구현 객체 의존X
MemberServiceImpl → MemberRepository 인터페이스에만 의존으로 변경!
MemberServiceImpl은 생성자를 통해서 어떤 구현객체가 들어오는지는 알 수 없다. 이제 구현객체는 외부 ( AppConfig ) 를 통해서만 결정된다.
MemberServiceImpl 은 이제부터 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다!

클래스 다이어그램

클래스 다이어그램을 보면 더 이상 구현 객체를 의존하지 않게 된다. 객체의 생성과 연결은 외부인 AppConfig를 통해서 이루어지게 된 모습을 볼 수 있다.
이렇게 구현 객체를 의존하지 않고, 인터페이스만 의존하게 되어 DIP가 완성된 모습이다.

즉 여기서 관심사의 분리 : 객체를 생성, 연결하는 역할, 실행하는 역할이 분리됨을 볼 수 있다.

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

AppConfig는 MemoryMemberRepository를 생성해서 참조값을 MemberServiceImpl를 생성하면서 생성자에 전달한다.

  • 클라이언트인 memberServiceImpl 입장에서 보면 의존관계를 외부에서 주입해주는 것 같다고 해서 DI(Dependency Injection) 우리말로 의존관계 주입 또는 의존성 주입이라 한다.

OrderServiceImpl 생성자 주입

이렇게 주문 서비스도 회원 서비스와 동일하게 생성자를 통해서 의존성을 주입받도록 한다. 더 이상 할인 정책이나 저장에 대한 부분을 직접 결정하지 않아도 된다.

package hello.core.order;

import hello.core.discount.DiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;

public class OrderServiceImpl implements OrderService{
    // 인터페이스에만 의존하도록 변경(DIP 만족)
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    // 의존성 주입(DI)
    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);            // 해당 id를 가진 회원 조회
        int discountPrice = discountPolicy.discount(member, itemPrice); // 회원의 등급에 따른 할인 금액

        return new Order(memberId, itemName, itemPrice, discountPrice); // 최종 생성된 주문 반환
    }
}

기존에는 OrderServiceImpl이 MemoryMemberRepository, FixDiscountPolicy 구현 객체를 직접 생성했지만 설계 변경 후에,
OrderServiceImpl → MemoryMeberRepository, FixDiscountPolicy 구현 객체 의존X
OrderServiceImpl → MemberRepository, DiscountPolicy 인터페이스에만 의존으로 바뀜.

  • OrderServiceImpl 의 생성자를 통해 어떤 구현 객체를 주입할지는 오직 외부(AppConfig)에서 결정된다.
    OrderServiceImpl 은 이제부터 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다!

AppConfig 실행

먼저 회원서비스

package hello.core;

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

public class MemberApp {

    public static void main(String[] args) {
        AppConfig appConfig = new AppConfig();
        MemberService memberService = appConfig.memberService();
        //MemberService memberService = new MemberServiceImpl();
        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("findMember = " + findMember.getName());
    }
}

MemberApp 클래스에서 MemberServiceImpl을 직접 생성하지 않고 생성한 AppConfig를 이용하여 MemberServiceImpl을 생성하고 MemberService에 연결해준다.

주문 서비스

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;

public class OrderApp {
    public static void main(String[] args) {

        AppConfig appConfig = new AppConfig();
        MemberService memberService = appConfig.memberService();
        OrderService orderService = appConfig.orderService();
        //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 = " + order); // order.toString() 출력됨
        //System.out.println("order.calculatePrice = " + order.calculatePrice()); // 할인된 최종 가격
    }
}

OrderApp클래스에서 MemberServiceImpl을 직접 생성하지 않고 생성한 AppConfig을 이용하여 MemberServiceImpl을 생성하고 MemberService에 연결해준다
OrderServiceImpl을 직접 생성하지 않고 생성한 AppConfig을 이용하여 OrderServiceImpl을 생성하고 OrderService에 연결해준다.
(둘다 결국 구현객체를 만든걸 추상화(인터페이스)로 받아서 사용하는거야 ! DIP만족하는거지.)

정리해보면,
_AppConfig을 통해서 관심사를 확실하게 분리했다. 각자 역할은 자신이 맡은 실행만 하면 되고, 전체 구성은 외부의 기획자인 AppConfig가 책임을 지게 되는 것이다._

  • AppConfig는 구체 클래스를 선택한다. (배역에 맞는 담당 배우를 선택하는 것과 동일)
  • OrderServiceImpl은 기능을 실행하는 책임만 지면 된다.
profile
back-end, 지속 성장 가능한 개발자를 향하여

0개의 댓글