본문 바로가기

Development/Spring Boot

[Spring Boot] 스프링 핵심 원리 이해 - 객체 지향 원리 적용 (1) 관심사의 분리

1- AppConfig 활용

- 예시. 배우와 공연 기획자의 분리

- 인터페이스에 어떤 구현체가 들어갈지는 공연 기획자가 해야한다. 

- AppConfig는 구현 객체를 생성하고 연결을 책임지는 별도의 클래스를 칭함

 

- 역할분리와 OCP 준수를 위해 생성자주입[ 생성자주입: 생성자를 통해 객체를 전달하는 것]

 

2- AppConfig의 역할

- 어플리케이션 동작에 필요한 실제 구현 객체 생성

- 생성한 인스턴스의 참조를 생성자를 통해 주입

- AppConfig 객체는 구현체의 객체를 생성하고 그 참조값을 또 다른 생성자(해당 구현체를 받아주는 생성자)로 전달

- 이로 클라이언트 입장에서 의존관계가 외부에서 주입해주는 것다고 해서 DI(Dependency Injection : 의존관계 주입 또는 의존성 주입)라고 함

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;

/**
 * Application 전체를 구성하고 설정하는 역할
 *  - 로미오 줄리엣 공연 예시 중 기획자
 *  - 어플리케이션에 대한 환경 구성을 모두 이곳에서 함
 */
public class AppConfig {

    // 생성자 주입: 생성자를 통해서 객체가 주입되는 것
    public MemberService memberService() {
        return new MemberServiceImpl(new MemoryMemberRepository());
    }

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

}

 

package hello.core.order;

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

public class OrderServiceImpl implements OrderService{

//    private MemberRepository memberRepository = new MemoryMemberRepository();
//    기존 코드: DIP, OCP 원칙 위배
//    private DiscountPolicy discountPolicy = new FixDiscountPolicy();

    /*
        변경 코드: DIP, OCP 준수하도록 변경
            (1) 인터페이스의 변수만 선언 (구체화가 아닌 추상화에만 의존하도록) : DIP 준수

     */
    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

    /**
     * 해당 설계가 잘된 이유: 단일 책임 원칙 (SRP)이 잘 지켜짐
     *  OrderService는 할인 정책에 대해 모름.
     *  좀더 풀어서 이야기하자면, 주문쪽에서 굳이 할인 정책에 대해 확인하고 까보지 않아도됨.
     *
     *  그렇기에 할인이 수정이 이루어져도 주문은 수정할 필요가 없기에 단일 책임 원칙이 잘 지켜짐
     *
     * @param memberId
     * @param itemName
     * @param itemPrice
     * @return
     */
    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {

        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(member.getId(), itemName, itemPrice, discountPrice);

    }

}
package hello.core.order;

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

public class OrderServiceImpl implements OrderService{

//    private MemberRepository memberRepository = new MemoryMemberRepository();
//    기존 코드: DIP, OCP 원칙 위배
//    private DiscountPolicy discountPolicy = new FixDiscountPolicy();

    /*
        변경 코드: DIP, OCP 준수하도록 변경
            (1) 인터페이스의 변수만 선언 (구체화가 아닌 추상화에만 의존하도록) : DIP 준수
            (2) 생성자를 통해 철저한 DIP 준수
                - 할인정책이 무엇이 들어오든 주문은 그 역할만 수행하면 됨
     */
    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

    /**
     * 해당 설계가 잘된 이유: 단일 책임 원칙 (SRP)이 잘 지켜짐
     *  OrderService는 할인 정책에 대해 모름.
     *  좀더 풀어서 이야기하자면, 주문쪽에서 굳이 할인 정책에 대해 확인하고 까보지 않아도됨.
     *
     *  그렇기에 할인이 수정이 이루어져도 주문은 수정할 필요가 없기에 단일 책임 원칙이 잘 지켜짐
     *
     * @param memberId
     * @param itemName
     * @param itemPrice
     * @return
     */
    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {

        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(member.getId(), itemName, itemPrice, discountPrice);

    }

}

 

3- 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 = new MemberServiceImpl();

        // (코드 수정) DIP 원칙 적용된 memberService를 받아옴
        MemberService memberService = appConfig.memberService();    // 여기엔 할당된 MemberServiceImple 구현 객첵 할당됨

        // command + option + v
        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());

    }

}