Spring 강의/springMVC

springMVC 기본 - (3) 객체 지향 원리 적용

lxexjx 2022. 3. 10. 00:18

[정리]

클라이언트가 주문서비스에 주문 생성하고 그 주문서비스가 회원조회, 할인 적용해서 결과물을 클라이언트에 반환.

 

 

[할인 정책 추가&테스트]

 

RateDiscountPolicy가 추가됨!

<RateDiscountPolicy>

public class RateDiscountPolicy implements DiscountPolicy {

    private int discountPercent = 10; //10% 할인
    
    @Override
    public int discount(Member member, int price) {
        if (member.getGrade() == Grade.VIP) {
            return price * discountPercent / 100;
        } else {
            return 0;
        }
    }
}

 

<RateDiscountPolicyTest> - RateDiscountPolicy가 10%할인 되는 지 테스트

class RateDiscountPoicyTest {

    RateDiscountPoicy discountPoicy=new RateDiscountPoicy();

    @Test
    @DisplayName("VIP는10%할인이 적용됨") 
    void vip_o(){
        //given 멤버 만들고
        Member member= new Member(1L,"memberVIP", Grade.VIP);
        //when
        int discount=discountPoicy.discount(member,10000);
        //then 결과
        assertThat(discount).isEqualTo(1000);    //alt+emter로 static method로 만들어주는게 좋아=>Assertions.assertThat(discount).isEqualTo(1000);가
    }

    @Test
    @DisplayName("VIP가 아니면 할인이 적용 안됨")
    void vip_x(){
        //given 멤버만들고
        Member member= new Member(2L,"memberBASIC", Grade.BASIC);
        //when
        int discount=discountPoicy.discount(member,10000);
        //then
        assertThat(discount).isEqualTo(0);
    }
}

 

 


 

[OCP, DI 위반]

이제 할인정책을 변경하려면 클라이언트인 OrderServiceImpl 코드를 고쳐야 함

public class OrderServiceImpl implements OrderService {
    // private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}

private final MemberRepository memberRepository = new MemoryMemberRepository();

OrderServiceImpl이 직접 객체를 생성하고 구체까지 선택해서 discountPolicy에 할당함

OrderServiceImpl 이 DiscountPolicy 인터페이스 뿐만 아니라 FixDiscountPolicy 인 구체 클래스도 함께 의존. DIP 위반(추상화에도 의존, 구체화에도 의존)

 

FixDiscountPolicy 를 RateDiscountPolicy 로 변경하는 순간 OrderServiceImpl 의 소스 코드도 함께 변경해야 한다! OCP 위반

 

추상(인터페이스) 의존: DiscountPolicy
구체(구현) 클래스: FixDiscountPolicy , RateDiscountPolicy

역할과 구현을 충실하게 분리 OK
다형성도 활용하고, 인터페이스와 구현 객체를 분리 OK
OCP, DIP 같은 객체지향 설계 원칙을 충실히 준수X

 

 

DIP 위반 추상에만 의존하도록 변경(인터페이스에만 의존)
인터페이스에만 의존하도록 설계를 변경

public class OrderServiceImpl implements OrderService {
    //private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
    private DiscountPolicy discountPolicy;
}

BUT  NPE(null pointer exception)가 발생구현체가 없는데 어떻게 코드를 실행할 수 있을까?

누군가가 클라이언트인 OrderServiceImpl 에 DiscountPolicy 의 구현 객체를 대신 생성하고 주입

 

 


[관심사의 분리]

OrderServiceImpl에서 DiscountPolicy를 FixDiscountPolicy로 하겠다고 직접 객체를 생성하고 선택.

 

환경구성에 관한 것은 AppConfig가 다 함. 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결)

객체에 대한 의존관계를 외부에서(appConfig) 넣어줌

<AppConfig> - 전체를 설정하고 구성, 구현 객체 생성하고 생성한 객체 참조를 생성자를 통해 주입

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

 

<MemberServiceImpl>

public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;
    
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
    public void join(Member member) {
        memberRepository.save(member);
    }
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

MemoryRepository에 뭐가 들어갈지를 생성자 만들어서 생성자를 통해서 구현체가 뭐가 들어갈지..

memberservice를 불러다 쓸 때 memberservice구현 객체가 생성되고 

: MemoryRepository의 구현체에 뭐가 들어갈지 생성자를 통해서 객체가 들어감 -> 생성자 주입

이제 인터페이스(추상화)에만 의존 DIP잘 지킴

 

MemberServiceImpl 은 MemoryMemberRepository 를 의존하지 않는다!
단지 MemberRepository 인터페이스만 의존한다. MemberServiceImpl 입장에서 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다.
MemberServiceImpl 의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부( AppConfig )에서 결정된다. MemberServiceImpl 은 이제부터 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다

 

 

객체의 생성과 연결은 AppConfig 가 담당

1. AppConfig가 MemberService객체를 만들 때 MemoryMemberRepository 를 생성

2.MemberServiceImpl 를 생성할 때 MemoryMemberRepository 의 참조값 001을 생성자에 같이 넘겨

 

 

 

[AppConfig 사용]

<MemberApp> - AppConfig를 이용해서 

public class MemberApp {
    public static void main(String[] args) {
    
        AppConfig appConfig = new AppConfig();
        
        MemberService memberService = appConfig.memberService();	//appConfig가 memberService달라하면 memberService의 인터페이스를 줘
        //memberService에는 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("find Member = " + findMember.getName());
    }
}

<OrderApp>

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

 

<MemberServiceTest>

class MemberServiceTest {
    MemberService memberService;
    
    //테스트 실행 전에 다 항상 실행
    @BeforeEach
    public void beforeEach() {
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
    }
   
   
class OrderServiceTest {
    MemberService memberService;
    OrderService orderService;
    
    @BeforeEach
    public void beforeEach() {
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
        orderService = appConfig.orderService();
    }
}

 


[리팩토링]

역할이 드러나게 하는 AppCofig 리팩토링 

public class AppConfig {
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }
    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(),discountPolicy());
    }
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
    public DiscountPolicy discountPolicy() {
        //return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }
}

멤버서비스 역할, 리포지터리 역할, 오더서비스 역할 다 드러남

 

 

FixDiscountPolicy를 RateDiscountPolicy 로 변경해도 구성 영역만 영향을 받고, 사용 영역은 전혀 영향을 받지 않음

 


 


 

[스프링으로 전환]

<AppConfig>

@Configuration
public class AppConfig {
    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }
    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(
                memberRepository(),
                discountPolicy());
    }
    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
    @Bean
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }
}

@Configuration : 설정정보,구성정보 담당

@Bean : 스프링 컨테이너에 등록됨 

 

 

<MemberApp> - AppConfig에서 스프링 사용 버전으로 변경하기

public class MemberApp {
    public static void main(String[] args) {
    
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        
        MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
        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());
    }
}

 

applicationContext : 스프링 컨테이너. (이전에는 직접 객체 생성하고 DI했지만 스프링컨테이너를 통헤 사용 )
모든 객체(@Bean) 관리, @Bean 이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록

스프링 빈: 스프링 컨테이너에 등록된 객체

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); 
AppConfig에 있는 환경 설정 정보를 가지고 스프링이 @Bean들을 스프링 빈에 객체 생성한걸 집어 넣어서 관리
->MemberService memberService = applicationContext.getBean("memberService", MemberService.class);스프링 컨테이너를 통해서 이 객체를 찾아와                                                     찾을 객체이름 "메서드 이름",      타입  

 

 

찾을 때는 스프링 컨테이너를 통해서 가져와. 스프링이 환경정보를 가지고 필요한 것들 읽어서 관리하고 나중에 스프링 컨테이너에서 거내 써