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

Lee·2023년 3월 20일
0

spring

목록 보기
2/8

김영한님 스프링 핵심원리 강의 개인 정리입니다.

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

위 코드의 문제점은 할인 정책을 바꿀 때 마다 FixDiscountPolicy, RateDiscountPolicy 객체를 생성하는 코드를 고쳐야 한다.

DiscountPolicy 인터페이스와 구현체인 FixDiscountPolicy, RateDiscountPolicy객체를 생성해서 사용하는 클라이언트 쪽에서는 다형성을 활용하였지만 OCP, DIP 원칙을 어겼다.

DIP : 주문 서비스 클라이언트 OrderServiceImpl은 DiscountPolicy 인터페이스에 의존하지만 실질적으로 클래스 내에서 new 연산자를 통해서 직접 생성했기 때문에, 인터페이스와 구현체 모두에게 의존하고 있다.

OCP : 변경하지 않고 확잘할 수 있어야하지만, 새로운 할인정책이나 기존에 정의해둔 할인정책을 교체해야될 일이 생기면 코드를 수정해야 한다.

이런 문제를 해결하기 위해서는 클라이언트 코드인 OrderServiceImpl 클래스가 할인정책의 인터페이스인 DiscountPolicy에만 의존하는 관계를 가지게 해야한다.

클래스 내부에서 직접 new 연산자를 통해 객체를 생성하는 방법이 아닌

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

OrderServiceImpl 클래스가 DiscountPolicy 인터페이스만을 의존시킨 채 외부에서 주입받는 방식으로 해결해야 DIP를 위반하지 않을 수 있다.

의존성을 주입받는 방법으로는 일반적으로 3가지가 있다.

1. 생성자 주입

  • 생성자를 호출 시점에 딱 1번만 호출되는 것이 보장된다.
  • 불변, 필수 의존관계에 사용.
// @RequiredArgsContructor
@Component
public class OrderServiceImpl implements OrderService {
	
    private final DiscountPolicy discountPolicy;
    
    @AutoWired
    public OrderServiceImpl(DiscountPolicy discountPolicy) {
	    this.discountPolicy = discountPolicy;
    }

discountPolicy에 final 키워드를 붙여서 OrderServiceImpl 객체를 생성할 때 그와 동시에 DiscountPolicy를 외부에서 주입받을 수 있도록 한다. (Lombok의 필수 생성자를 자동으로 만들어주는 @RequiredArgsContructor 어노테이션을 사용하면 밑의 생성자 부분을 작성할 필요가 없다. 또 생성자가 단 하나만 존재한다면 @AutoWired 어노테이션을 생략해도 자동 주입 된다. -> 스프링 빈에만 해당한다.)

2. 수정자 주입(Setter)

필드값을 수정하는 setter 메서드를 통해서 의존관계를 주입하는 방법.

  • 선택, 변경 가능성이 있는 의존관계에 사용.
  • 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방법.
  @Component
  public class OrderServiceImpl implements OrderService {
      private MemberRepository memberRepository;
      private DiscountPolicy discountPolicy;
      
      @Autowired
      public void setMemberRepository(MemberRepository memberRepository) {
	      this.memberRepository = memberRepository;
      }
      
      @Autowired
      public void setDiscountPolicy(DiscountPolicy discountPolicy) {
    	  this.discountPolicy = discountPolicy;
      }
}

참고: @Autowired 의 기본 동작은 주입할 대상이 없으면 오류가 발생한다. 주입할 대상이 없어도 동작하게 하려면 @Autowired(required = false) 로 지정하면 된다.

3. 필드 주입

  • 코드가 간결하지만 외부에서 변경이 불가능해서 테스트하기 힘들다는 단점이 있다.
  • DI 프레임워크가 없으면 아무것도 할 수 없다.
  • 애플리케이션의 실제 코드와 관계없는 테스트 코드에는 사용해도 상관없다.
  • 웬만하면 사용하지 말되, 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용
@Component
public class OrderServiceImpl implements OrderService {
	@Autowired
    private MemberRepository memberRepository;
    @Autowired
    private DiscountPolicy discountPolicy;
}

참고: 순수한 자바 테스트 코드에는 당연히 @Autowired가 동작하지 않는다. @SpringBootTest 처럼 스프링 컨테이너를 테스트에 통합한 경우에만 가능하다.
참고: 다음 코드와 같이 @Bean 에서 파라미터에 의존관계는 자동 주입된다. 수동 등록시 자동 등록된 빈의 의존관계가 필요할 때 문제를 해결할 수 있다.

    @Bean
    OrderService orderService(MemberRepository memberRepoisitory, DiscountPolicy
    discountPolicy) {
        new OrderServiceImpl(memberRepository, discountPolicy)
    }

생성자 주입을 권장하는 이유

  • 불변

    대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다.(불변해야 한다.)
    수정자 주입을 사용하면, setXxx 메서드를 public으로 열어두어야 한다.
    누군가 실수로 변경할 수 도 있고, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다. 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없다. 따라서 불변하게 설계할 수 있다.

  • 누락

    생성자 주입을 사용하면 주입 데이터를 누락했을 때 컴파일 오류가 발생하고, 컴파일러가 오류를 잡아주기 때문에 오류를 빨리 알아챌 수 있다.

  • final 키워드

    생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있고, 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아준다.

참고: 수정자 주입을 포함한 나머지 주입 방식은 모두 생성자 이후에 호출되므로, 필드에 final 키워드를 사용할 수 없다. 오직 생성자 주입 방식만 final 키워드를 사용할 수 있다.

0개의 댓글