스프링 핵심 원리(3) - 객체지향 원리 적용

박정민·2021년 1월 25일
0

spring

목록 보기
11/16

1. 새로운 할인 정책 개발, 적용, 문제점

새로운 할인 정책 개발

새로운 할인 정책 적용

  • OrderServiceImpl코드 수정
     public class OrderServiceImpl implements OrderService {
         // private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
         private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
     }

문제점

  • 다른 할인정책으로 변경할 때 OCP를 위반 한다.
    • OrderServiceImpl의 코드를 변경해야한다.

  • 인터페이스만 의존하는게 아니라 구현체도 의존하고 있어서 DIP를 위반한다.
    • 인터페이스 의존: DiscountPolicy
    • 구현체 의존: FixDiscountPolicy, RateDiscountPolicy

2. 관심사의 분리

문제 해결 - 관심사 분리

  • OrderServiceImpl코드
    • 주문 서비스에 관련된 로직을 수행하는데 집중해야 한다.
    • 할인정책이 바뀌어도 똑같이 수행되어야 한다.
    • 할인정책을 지정하는 별도의 코드가 필요 => Config

AppConfig

  • 애플리케이션의 전체 동작 방식을 구성(Config)

  • 구현 객체를 생성, 연결하는 책임을 가지는 별도의 설정 클래스

  • AppConfig.java
    • 애플리케이션동작에 필요한 구현 객체를 생성
    • 생성한 객체 인스턴스의 참조를 생성자를 통해서 주입(연결)

구현체 코드 수정

  • OrderServiceImpl(생성자 주입)
   public class OrderServiceImpl implements OrderService{

       private final DiscountPolicy discountPolicy;

       public OrderServiceImpl(DiscountPolicy discountPolicy){
           this.discountPolicy = discountPolicy;
       }
   }
  • 할인정책 구현체해(RateDiscountPolicy)에 의존하지 않게되고, 인터페이스에만 의존한다.
    • DIP 위반X

  • 다른 할인정책으로 변경 시 코드를 변경할 필요가 없다.
    • OCP 위반X

  • 관심사의 분리: 객체를 생성하고 연결하는 역할과 실행하는 역할이 명확히 분리되었다.

  • OrderServiceImpl생성자를 통해서 어떤 구현 객체가 주입될지는 AppConfig에서 결정한다.

  • 생성자 주입 적용 코드

AppConfig를 통한 실행

정리

  • AppConfig를 통해서 관심사를 확실하게 분리했다.

  • 각각의 구현체는 담당 기능을 실행하는 책임만 가지게 된다.

  • AppConfig는 애플리케이션의 전체 구성(의존 관계 등)을 책임진다.

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

새로운 구조

  • 사용, 구성의 분리


할인 정책 적용

  • 할인 정책의 변경
  • 할인 정책을 변경해도 구성 영역(AppConfig)만 영향을 받고, 사용 영역은 영향을 받지 않는다.

  • 할인 정책 변경 코드(AppConfig)
public class AppConfig {
    public DiscountPolicy discountPolicy() {
        // return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }
}

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

  • 현재 예제에서 SRP, DIP, OCP 적용

SRP 단일 책임 원칙

  • 관심사 분리(App Config)를 통해 클라이언트 객체(OrderServiceImpl)는 실행하는 책임만 담당한다.

  • 구현 객체를 생성하고 연결하는 책임은 App Config가 담당

DIP 의존관계 역전 원칙

  • AppConfig를 통해 OrderServiceImpl에서 추상화(인터페이스)에만 의존하게 코드를 수정했다.

OCP

  • AppConfig를 통해 OrderServiceImpl에서 다른 할인 정책으로 바꿀 때 코드를 변경할 필요가 없다.

5. IOC, DI, 그리고 컨테이너

IoC(Inversion of Control)

  • AppConfig 등장 전

    • OrderServiceImpl가 스스로 필요한 구현 객체를 생성하고, 연결하고 실행했다.
    • OrderServiceImpl가 제어 흐름을 스스로 조종했다.

  • AppConfig 등장 후

    • OrderServiceImpl은 자신의 로직을 수행하는 역할만 담당한다.
    • 프로그램에 대한 제어 흐름은 AppConfig가 가지고 있다.

  • IoC: 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부(AppConfig)에서 관리하는 것

  • 프레임워크 vs 라이브러리

    • 제어의 흐름을 누가 가지고 있나?
      • 프레임워크: 직접 작성한 코드를 제어, 대신 실행
      • 라이브러리: 직접 작성한 코드가 제어의 흐름 담당

DI(Dependency Injection)

  • 의존관계
    • 정적인 클래스 의존 관계
      • 클래스가 사용하는 import 코드로 의존관계 파악 가능
      • 정적인 의존관계는 애플리케이션을 실행하지 않아도 파악 가능

    • 실행 시점에 결정되는 동적인 객체 의존 관계
      • 실행시점에 객체 인스턴스를 생성하고, 그 참조값을 전달해서 연결된다.

      • ex) AppConfig
      public OrderService orderService(){
          return new OrderServiceImpl(new RateDiscountPolicy());
      }
  • 의존관계 주입: 런타임에 외부(AppConfig)에서 실제 구현 객체를 생성하고 클라이언트에
    전달해서 클라이언트와 서버의 실제 의존관계가 연결되는 것

DI컨테이너

  • DI컨테이너: AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것

6. 스프링으로 전환

  • AppConfig: 순수한 자바 코드만으로 DI 적용

SpringConfig

  • SpringConfig: 스프링을 통한 DI 적용

  • SpringConfig.java
@Configuration
public class SpringConfig {
    
    @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: SpringConfig에 설정을 구성한다.

  • @Bean: 스프링 컨테이너에 스프링 빈으로 등록된다.

MemberApp에 스프링 컨테이너 적용

  • OrderApp.java - main()
public static void main(String[] args) {
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
    
    MemberService memberService = applicationContext.getBean("memberService", MemberService.class); 
    OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
 
    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);
}
  • ApplicationContext: 스프링 컨테이너

    • 스프링 컨테이너를 통해서 DI
    • 스프링 컨테이너는 @Configuration이 붙은 SpringConfig를 설정 정보로 사용
    • @Bean이 붙은 모든 메서드를 호출
      • 메서드에서 반환된 객체들을 모두 스프링 컨테이너에 스프링빈으로 등록
      • 호출된 메서드의 이름을 스프링 빈의 이름으로 사용

  • applicationContext.getBean()

    • 필요한 스프링 빈(객체)을 찾을 때 사용
    • ex) applicationContext.getBean("memberService", MemberService.class)
      • "memberService": 필요한 스프링 빈 이름(해당 객체를 반환하는 메서드 이름는
      • MemberService.class: 타입

정리

  • 직접 자바코드(AppConfig)로 의존관계를 설정해줬지만, 스프링을 이용하여(SpringConfig)스프링 컨테이너에 객체를 스프링 빈으로 등록하고, 스프링 컨테이너에서 스프링 빈을 찾아서 사용하도록 변경했다.
profile
화이팅!

0개의 댓글

관련 채용 정보