반응형

# 객체 지향 원리 적용_1

## 새로운 할인 정책 개발

  • 기존의 고정 금액 할인이 아닌, 주문 금액 당 퍼센트인 정률할인으로 변경.
  • 기존에 DiscountPolicy (인터페이스) 의 구현체로 FIxDiscountPolicy 사용하였는데, 정률할인을 구현체인 RateDiscountPolicy 추가.

RateDiscountPolicy 

package hello.core.discount;

import hello.core.member.Grade;
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;
        }
    }
}

테스트 코드 생성 단축키.

  • 인텔리제이에서 윈도우의 경우 Ctrl + Shift + T 입력 시 Test 생성 팝업 호출됨.

  • 아래와 같이 검증 코드에 Alt + Enter 입력 시 (윈도우의 경우) Add static import 하여 사용 가능하다.

Alt + Enter (윈도우의 경우)
Add static import 적용된 검증코드.

RateDiscountPolicyTest

package hello.core.discount;

import hello.core.member.Grade;
import hello.core.member.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

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
        Assertions.assertThat(discount).isEqualTo(1000);
    }

    @Test
    @DisplayName("VIP가 아니면 할인이 적용되지 않아야 함.")
    void vip_x() {
        // given
        Member member = new Member(1L, "memberBasic", Grade.BASIC);

        // when
        int discount = discountPolicy.discount(member, 10000);

        // then
        Assertions.assertThat(discount).isEqualTo(1000);
    }
}

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

  • OCP 위반 : 현재 작업한 소스에서 할인 정책 변경 시 OrderServiceImpl 의 코드를 고쳐야 함.
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository = new MemoryMemberRepository();
    //    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();    // 고정할인 
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy(); // 정률할인
}
  • 역할과 구현 분리되었고, 다형성 활용 및 인터페이스와 구현 객체 분리 됨.
  • 하지만, OCP, DIP 같은 객체지향 설계 원칙을 준수하지 않음. (문제점)
  • DIP 위반 : 주문 서비스 클라이언트 (OrderServiceImpl) 의 경우 DiscountPolicy 인터페이스에 의존하면서 DIP를 지킨 것 같지만, 추상(인터페이스) 뿐만 아니라 구체(구현) 클래스에도 의존 중.
여기서.. 
추상(인터페이스) 의존 : DiscountPolicy
구체(구현) 클래스 : FixDiscountPolicy, RateDiscountPolicy

## 관심사의 분리 (DIP, OCP 위반 문제 해결)

  • 공연을 예로 들면 배우는 배우의 역할에만 집중, 배우가 공연을 구성하고 다른 배역을 섭외 하는 등의 업무를 해서는 않된다. 배우는 상대 배우가 누구라도 동일한 공연을 할 수 있어야 함. 이를 위해 공연을 구성하고 배역을 섭외하는 역할을 하는 별도의 기획자가 필요. (AppConfig)

AppConfig (중요!!!!) - 객체의 생성&&주입(연결) 담당. 

  • 구현 객체 생성, 연결하는 책임을 갖는 별도의 설정 클래스.
  • 애플리케이션의 실제 동작에 필요한 구현 객체 생성, 생성한 객체 인스턴스의 참조(래퍼런스)를 생성자를 통해 주입(연결) 함.
public class AppConfig {

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

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

    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = 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;
    }

}
  • 위와같이 설계 변경을 함으로써 Impl이 인터페이스에 의존하게 됨.
  • 오직 외부(AppConfig)에서 어떤 구현 객체가 주입될지 결정 됨. (DI (Dependency Injection, 의존관계 주입))

MemberApp

  • 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 memberA = new Member(1L, "memberA", Grade.VIP);
        memberService.join(memberA);

        Member findMember = memberService.findMember(1L);
        System.out.println("member : " + memberA.getName());
        System.out.println("findMember : " + findMember.getName());
    }
}

OrderApp

  • AppConfig 로 의존관계 주입하여 적용.
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.toString());
    }
}

MemberServiceTest

  • AppConfig 로 의존관계 주입하여 적용.
  • @BeforeEach : 실행 전 실행됨.
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;

public class MemberServiceTest {

    MemberService memberService;

    @BeforeEach
    public void beforeEach() {  // 실행 전 실행됨.
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
    }

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

        // when
        memberService.join(member);
        Member findMember = memberService.findMember(1L);

        // then
        Assertions.assertThat(member).isEqualTo(findMember);
    }
}

OrderServiceTest

  • AppConfig 로 의존관계 주입하여 적용.
  • @BeforeEach : 실행 전 실행됨.
public class OrderServiceTest {

    MemberService memberService;
    OrderService orderService;

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

}

## AppConfig 리팩터링 

  • 중복 및 역할에 따른 구현이 잘 안보이는 현상 발생하여 이를 개선.

기존 AppConfig

public class AppConfig {

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

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

변경 AppConfig

  • new MemoryMemberRepository() 중복 제거.
  • 역할과 구현 클래스가 한눈에 보이도록 분리.
public class AppConfig {

    // memberService 역할
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    // memberRepository 역할
    private MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    // orderService 역할
    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    // discountPolicy 역할
    public DiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }
}

## 새로운 구조와 할인 정책 적용

  • 기존 정액할인 적용 -> 정률할인 정책 적용으로 변경.
  • 정책 변경을 위해 AppConfig 에서만 변경해주면 됨. -> AppConfig로 클라이언트 코드에 대한 변경 없이 정책 변경 가능해짐. (사용영역 전혀 변경할 필요 없음)
public class AppConfig {

    // memberService 역할
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    // memberRepository 역할
    private MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    // orderService 역할
    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    // discountPolicy 역할
    public DiscountPolicy discountPolicy() {
//        return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }
}
반응형

+ Recent posts