반응형

# 객체 지향 원리 적용_2

  • 기존에 적용했던 정액할인 정책(고정 금액 할인)에서 새로운 할인 정책인 정률 할인 정책(금액에 따른 비율로 할인)으로 변경.
  • 할인 정책을 변경하게 되면 클라이언트 코드인 주문 서비스 구현체를 변경해야 하는 문제점이 발생했었다. (DIP 위반)
  • 문제점을 해결하기 위해 관심사를 분리(AppConfig 활용)

## 좋은 객체 지향 설계의 5가지 원칙 적용

  • 현재 작업된 소스에서 SRP, DIP, OCP 가 적용되어있다.

SRP : 단일 책임의 원칙

  • 핵심 : 한 클래스는 하나의 책임만 가져야 한다.
  • 기존에 클라이언트 객체는 직접 구현 객체를 생성하는 등 많은 책임이 존재했다.
  • SRP 적용하여 관심사를 분리 -> AppConfig 가 구현 객체 생성, 연결의 책임을 가지게 됨.
  • 클라이언트 객체의 경우 실행에 대한 책임만 담당.

DIP : 의존관계 역전의 원칙

  • 핵심 : 프로그래머는 추상화에 의존, 구체화에 의존하면 안된다. (의존성 주입은 해당 원칙을 따르는 방법 중 하나임)
  • AppConfig 생성하여 외부에서 객체에 대한 의존 관계를 주입. (객체가 추상화에 의존해야 가능한 것)

OCP

  • 핵심 : 소프트웨어의 요소는 확장에는 열려있고, 변경에는 닫혀있어야 한다.
  • 다형성을 사용, 클라이언트가 DIP를 지킴.
  • 애플리케이션을 사용 영역, 구성 영역으로 분리.
  • APpConfig 가 의존관계를 FixDiscountPolicy 에서 RateDiscountPolicy 로 변경하여 클라이언트에 주입하여 클라이언트의 코드는 변경하지 않아도 되었음. (소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경은 닫혀있게 됨(변경할 필요 없었음))

## IoC, DI 그리고 컨테이너

IoC (Inversion of Control) : 제어의 역전 

  • 제어권이 뒤바뀜.
  • 기존 : 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성, 연결, 실행. (프로그램의 제어 흐름을 스스로 조종)
  • 변경 : AppConfig 등장으로 구현 객체는 자신의 로직을 실행만 하는 역할을 함. (프로그램 제어 흐름이 AppConfig 에 존재)
  • 정리 : 프로그램의 제어 흐름을 직접 제어하는 것이 아닌, 외부에서 관리하는 것을 의미.
  • 프레임워크 vs 라이브러리
프레임워크가 내가 작성한 코드를 제어, 대신 실행하면 프레임워크가 맞음 (JUnit)

내가 작성한 코드가 직접 제어의 흐름을 담당하는 경우는 라이브러리.

DI (Dependency Injection) : 의존 관계 주입

  • 구현체가 인터페이스에 의존, 실제 어떤 구현 객체가 사용될지 모름.
  • 정적 클래스 의존 관계, 실행 시점에 결정되는 동적 객체(인스턴스) 의존 관계를 분리해서 생각해야 함.
  • 정적 클래스 의존관계
클래스가 사용하는 import 코드만 보고 의존관계 판단 가능. 

정적 의존관계는 애플리케이션 실행 없이 분석 가능. (클래스 다이어그램만 보고 판단가능)
  • 동적인 객체 인스턴스 의존 관계
애플리케이션 실행 시점에서 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계를 의미. (객체 다이어그램)
  • 의존관계 주입 : 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성, 클라이언트에 전달하여 클라이언트와 서버의 실제 의존관계가 연결되는 것.
  • 객체 인스턴스를 생성, 그 참조값을 전달해서 연결함.
  • 정리 : 의존관계 주입 사용 시 클라이언트 코드 변경 없이 클라이언트가 호출하는 대상의 타입 인스턴스 변경 가능하다. 그리고 정적 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경 가능하다.

DI 컨테이너 (= IoC 컨테이너)

  • AppConfig 처럼 객체 생성, 의존관계 연결, 관리해주는 것을 의미.
  • 의존관계 주입에 초점을 맞춰 DI 컨테이너라고 함. (또는 어샘블러, 오브젝트 팩토리 등으로 불리기도 함)

## 스프링으로 전환 (스프링 컨테이너 사용)

  • 지금까지의 작업물은 순수 자바 코드로 작업 한 것.
  • 순수한 자바 코드만으로 DI 적용한 것을 스프링으로 변경.

AppConfig

  • @Configuration
  • @Bean
package hello.core;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberRepository;
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;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

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

    // memberRepository 역할
    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

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

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

MemberApp

  • 스프링의 컨테이너 : ApplicationContext
  • 아래와 같이 해주면 AppConfig에 있는 것들을 스프링 컨테이너에 객체 생성 해서 관리하게 함.
// 스프링 컨테이너 : ApplicationContext
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        // 기존
//        AppConfig appConfig = new AppConfig();
//        MemberService memberService = appConfig.memberService();

        // 스프링 컨테이너 : ApplicationContext
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
package hello.core;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

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

        // 기존
//        AppConfig appConfig = new AppConfig();
//        MemberService memberService = appConfig.memberService();

        // 스프링 컨테이너 : ApplicationContext
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = applicationContext.getBean("memberService", MemberService.class);

//        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

  • MemberApp 에 적용한 것처럼 ApplicationContext 사용.
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;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

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

        // 기존
//        AppConfig appConfig = new AppConfig();
//        MemberService memberService = appConfig.memberService();
//        OrderService orderService = appConfig.orderService();

        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);

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

스프링 컨테이너  

  • 스프링 컨테이너 : ApplicationContext
  • 기존 : AppConfig 사용해서 직접 객체를 생성 및 의존성 주입 (DI) 진행. (AppConfig 통해서 진행)
  • 변경 : 스프링 컨테이너를 활용. (AppicationContext 통해서 진행)
  • 스프링 빈의 경우 @Bean 이 붙은 메서드 명을 스프링 빈의 이름으로 사용.
  • 스프링 빈 찾기 : appcationContext.getBean("bean이름", 타입)
반응형

+ Recent posts