[Spring-기본] AppConfig를 통한 객체지향 설계원칙 지키기

DANI·2023년 11월 22일

Spring[김영한T]

목록 보기
15/31
post-thumbnail

❓ 인터페이스를 의존하는 것으로 바꾸면 구현체가 없는데 어떻게 할인정책을 구현할까?



지난 시간에 OCP와 DIP 객체 지향 설계 원칙을 지키기 위해 구현체를 의존하는 것이 아닌 인터페이스를 의존하는 것으로 코드를 수정하였다.

하지만 구현체가 없는데 어떻게 구현할 수 있을까?



쉽게 생각해서 공연(프로그램)을 하는데 역할(인터페이스)와 배우(구현체)가 있다고 가정을 하자.

코드를 수정하기 전 인터페이스에도 의존하고 구현체에도 의존하는 코드였을때를 비유해서 말하자면,


🔍 주문 클래스 다이어그램

🔍 회원 클래스 다이어그램


남자주인공 역할(인터페이스)의 남자배우(구현체)가 여자 주인공역할(인터페이스)의 여자배우(구현체)도 캐스팅하는 것과 같다.

이와 같은 상황에서는 공연기획자(AppConfig)가 별도로 있어야하며, 공연기획자는 각 역할(인터페이스)의 배우(구현체)를 캐스팅 해야한다.

코드로 알아보자.





✅ OrderServiceImpl 구현체를 수정


package hello.core.Order;

import hello.core.Discount.DiscountPolicy;
import hello.core.Discount.FixedDiscountPolicy;
import hello.core.Discount.RateDiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;

public class OrderServiceImpl implements OrderService{

    private MemberRepository memberRepository;
    
    // private final MemberRepository memberRepository = new MemoryMemberRepository();

    // private final DiscountPolicy discountPolicy = new FixedDiscountPolicy();
    // private final DiscountPolicy discountPolicy = new RateDiscountPolicy();

    private DiscountPolicy discountPolicy;

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

    @Override
    public Order createOrder(Long id, String name, int price) {
        Member member = memberRepository.findById(id);
        int discount = discountPolicy.fixedDiscount(member, price);
        return new Order(id, name, price, discount);
    }
}



✅ 다음과 같이 구현체를 수정하였다.

public class OrderServiceImpl implements OrderService{

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

}

👉 수정

public class OrderServiceImpl implements OrderService{

   private MemberRepository memberRepository;

   private DiscountPolicy discountPolicy;

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

인터페이스만 호출한 뒤 생성자를 생성했다.
MemberServiceImpl 파일도 똑같이 수정해보자!





✅ MemberServiceImpl 구현체를 수정


package hello.core.member;

public class MemberServiceImpl implements MemberService{

    // private final MemberRepository memberRepository = new MemoryMemberRepository();
    private MemberRepository memberRepository;

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

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

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


✅ 다음과 같이 구현체를 수정하였다.

public class MemberServiceImpl implements MemberService{

  private final MemberRepository memberRepository = new MemoryMemberRepository();
  ...

}

👉 수정

public class MemberServiceImpl implements MemberService{

    private MemberRepository memberRepository;

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


💾 AppConfig 파일 생성

package hello.core;


import hello.core.Discount.RateDiscountPolicy;
import hello.core.Order.OrderService;
import hello.core.Order.OrderServiceImpl;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;

// 실제 동작에 필요한 구현 객체를 생성한다.
public class AppConfig {


    // 주문 서비스
    // OrderService(역할) OrderService(구현체)
    // 역할에 어떤 구현체를 넣을 것인지?
    public OrderService OrderService(){
        return new OrderServiceImpl(new MemoryMemberRepository(), new RateDiscountPolicy());
    }

    // 멤버 서비스
    // MemberService(역할) MemberService(구현체)
    // 역할에 어떤 구현체를 넣을 것인지?
    public MemberService MemberService(){
        return new MemberServiceImpl(new MemoryMemberRepository());
    }
}




✨ 테스트 파일 수정하기


💾 OrderApp 테스트파일 수정

package hello.core;

import hello.core.Order.Order;
import hello.core.Order.OrderService;
import hello.core.Order.OrderServiceImpl;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;

public class OrderApp {
    public static void main(String[] args) {
        // MemberService memberService = new MemberServiceImpl();
        AppConfig appConfig = new AppConfig();
        MemberService memberService = appConfig.MemberService();
        // OrderService orderService = new OrderServiceImpl();
        OrderService orderService = appConfig.OrderService();

        // 멤버 생성
        Long id = 1L;
        Member member = new Member(id, "홍길동", Grade.VIP);
        memberService.join(member);

        // 주문 생성
        Order order = orderService.createOrder(1L, "새우깡", 1700);


        // 주문 내용 출력
        System.out.println(order);
        System.out.println(order.calprice());

    }
}

💾 MemberApp 테스트파일 수정

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) {
        // MemberService service = new MemberServiceImpl();
        AppConfig appConfig = new AppConfig();
        MemberService service = appConfig.MemberService();
        Member member = new Member(1L, "홍길동", Grade.VIP);
        service.join(member);
        Member member1 = service.findByMember(member.getId());
        if(member1.equals(member)){
            System.out.println("같은 객체 입니다");
        }else{
            System.out.printf("다른 객체 입니다");
        }
    }
}


💾 MemberServiceImplTest 수정

package hello.core.member;


import hello.core.AppConfig;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

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

class MemberServiceImplTest {

    // MemberService memberService = new MemberServiceImpl();
    private MemberService memberService;

    @BeforeEach
    void config(){
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.MemberService();
    }

    @Test
    void join(){
        // given
        Member member = new Member(1L, "홍길동", Grade.VIP);

        // when
        memberService.join(member);

        // then
        assertEquals(member, memberService.findByMember(member.getId()));
        Assertions.assertThat(member).isEqualTo(memberService.findByMember(member.getId()));
    }
}

🔵 실행결과


💾 OrderServiceTest 수정

package hello.core.Order;

import hello.core.AppConfig;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class OrderServiceTest {

    // MemberService memberService = new MemberServiceImpl();
    // OrderService orderService = new OrderServiceImpl();

    private MemberService memberService;
    private OrderService orderService;

    @BeforeEach
    void config(){
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.MemberService();
        orderService = appConfig.OrderService();
    }

    @Test
    void createOrder(){
        Long id = 1L;
        Member member = new Member(id, "홍길동", Grade.VIP);
        memberService.join(member);

        Order order = orderService.createOrder(1L, "새우깡", 1800);
        Assertions.assertThat(order.getDisPrice()).isEqualTo(180);
    }
}

🔵 실행결과






✨ 이번 챕터에서 배운 부분

✅ DIP & OCP
✅ 스프링에서 항상 말했던 의존성 주입에 대해 이해하게 되었다.
✅ 설정파일에서만 구현체를 변경하면 되므로 코드수정에 용이하다.

📝 공부할 부분

✅ 객체 지향 설계 원칙 알아보기

0개의 댓글