개발/Spring Core

Spring 핵심원리 객체 지향 원리 적용 (1)

Debin 2021. 12. 29.
반응형

 

본 게시글은 인프런 김영한 선생님 강의 스프링 핵심 원리를 완강하고 배운 것을 남기고자 적은 포스팅입니다.

강의 링크는 아래와 같습니다.

 

(강의 예제 코드는 웬만하면 적지 말라고 강의 공지사항에 적혀 있어서 이해 가능하게 코드를 짧게 남기겠습니다)

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 개발자가 되어보세요! 📢 수강 전

www.inflearn.com

 

 

먼저 우리는 주문 서비스를 관리하는 OrderServiceImpl이라는 클래스를 가지고 있다.

우리의 주문 서비스는 회원 등급에 따른 할인 여부를 할인 정책에 위임한다. 현재 주문 서비스는 고정 할인 정책을 채택하고 있다. 또한 우리의 구현체는 OrderService라는 인터페이스를 상속받는다.

 

public class OrderServiceImpl implements OrderService{
	private final MemberRepository memberRepository= new MemoryMemberRepository();
	private final DiscountPolicy discountPolicy =new FixDiscountPolicy();

// 코드들
}

 

우리의 할인 정책은 언제든지 바뀔 수 있으므로 DiscountPolicy라는 인터페이스가 있다.

인터페이스를 만든 이유는 다형성을 이용해 기능을 확장하면 구현 클래스를 쉽게 깔아 끼우기 위함이다. 

 

public interface DiscountPolicy {
// 추상 메소드들이 있다고 생각
}

 

우리는 고정 할인 정책과 비율 할인 정책 2가지가 있다.

 

public class RateDiscountPolicy implements DiscountPolicy{
//오버라이딩한 메소드들
}

 

public class FixDiscountPolicy implements DiscountPolicy{
//오버라이딩한 메소드들
}

 

우리는 인터페이스를 이용한 다형성으로 RateDiscountPolicy와 FixDiscountPolicy를 정말 쉽게 교체할 수 있다.

객체지향 5대 원칙을 지키면서 코드를 정말 잘 짠 것으로 생각할 수 있다.

 

그러나!!! 우리는 객체지향 원칙을 엄밀히 말하면 지키지 못했다. 아래 코드를 직접 살펴보자.

아까 코드로 짠 OrderServiceImpl 코드다. 왜 객체지향 5대 원칙을 준수하지 못했는지는 코드 블록에서 적겠다.

 

public class OrderServiceImpl implements OrderService{
	private final MemberRepository memberRepository= new MemoryMemberRepository();
	private final DiscountPolicy discountPolicy =new FixDiscountPolicy();
	
    /* 엄밀히 말하면 우리는 추상화에만 의존하지 않고 구체 클래스에도 의존하고 있다.
    이는 DIP 위반이다.
    
    또한 FixDiscountPolicy를 RateDiscountPolicy로 변경하는 순간 OrderServiceImpl의 코드도 변경!
    이는 OCP 위반이다. 수정에는 닫혀있어야하고 확장은 열려있는 원칙을 지키지 못했다.
    */
    
    
// 코드들
}

 

위와 같은 이유로 DIP와 OCP를 위반했다.

DIP와 OCP를 위반하지 않으려고 하면 무엇을 해야 할까? 우선 인터페이스에만 의존하면서 코드를 수정하면 안 된다. 

우선 인터페이스에만 의존하면 구현체가 존재하지 않아서 코드가 실행되지 않는다. 아래와 같다.

 

public class OrderServiceImpl implements OrderService{
	private final MemberRepository memberRepository;
	private final DiscountPolicy discountPolicy;
    
    //이제 인터페이스에만 의존한다. 그러나 이는 실행할 수 없다.
}

 

이러면 어찌해야하는가? 간단하다. 누군가 OrderServiceImpl 필드 DicountPolicy의 구현 객체를 대신 생성하고 주입해야 한다. 생성자를 통해서 필드의 구현체들을 주입받을 것이다. 코드는 아래와 같다.

 

 

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

 

OrderServiceImpl 은 본인의 고유 기능만 수행해야 한다. 필드 값에 어떤 객체가 들어올지 OrderServcieImpl는 정할 필요가 없다. 만약 정한다면 이는 단일 책임 원칙에도 위배된다. 이제 위와 같은 코드로 완성이 되면서 코드를 수정할 필요도 없고 의존 관계를 걱정할 일도 없어졌다. 이제 그러면 구현 객체를 넣어줄 클래스를 만들자.

 AppConfig라는 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스를 만든다.

이제 AppConfig가 생성자를 통해 구현체들을 주입시킬 것이다. 이를 생성자 주입이라고 한다.

 

public class AppConfig {

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

 

이제 앞으로 AppConfig 에서 구현 객체를 생성하고, 연결하는 책임을 가질 것이다. (설정 클래스)

이러면 AppConfig 등장으로 애플리케이션이 크게 사용 영역과, 객체를 생성하고 구성하는 설정 영역으로 구분되었다.

우리 개발자는 추상화에 의존해야하며, 구체화에 의존하면 안 된다. 

이제 우리의 사용 영역 클래스는 순수하게 인터페이스에 의존하며, 클라이언트 코드를 변경하며 구현체를 갈아 끼울 필요가 없다.

 

원래는 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고, 연결하고, 실행하며 프로그램의 제어 흐름을 스스로 다뤘다. 이제 AppConfig 같은 설정 클래스가 등장한 이후에는 클라이언트 구현 객체는 자신의 로직을 실행하는 역할만 한다. 이제 제어 흐름은 AppConfig가 가지고 있고, OrderServiceImpl은 필요한 인터페이스를 호출하지만 어떤 구현 객체들이 들어올지 모른다. 이렇게 AppConfig 가 제어 흐름에 대한 권한을 모두 가지고 있다.

이렇게 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전 (IOC)라고 한다.

 

OrderServiceImpl은 DiscountPolicy 인터페이스에 의존한다. 실제 어떤 구현 객체가 들어올지는 모른다.

의존 관계는 정적인 클래스 의존 관계와, 실행 시점에 결정되는 동적인 객체(인스턴스) 의존 관계 둘을 분리해서 생각해야 한다.

 

  • 정적인 의존 관계 : OrderServiceImpl은 MemberRepository와 DiscountPolicy에 의존
  • 동적인 의존 관계 : 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계. 즉 생성자 주입을 통해 들어오는 구현 객체와의 의존 관계.

 

애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결되는 것을 의존관계 주입(DI == Dependency Injection )이라 한다.

의존관계 주입을 사용하면 클라이언트 코드를 변경하지 않고, 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할 수 있다.

 

AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 IoC 컨테이너, DI 컨테이너라 한다.

여기까지 순수한 자바코드를 통해 DI를 적용했다. 이제 다음으로는 스프링을 시작해보겠다.

 

이상입니다. 감사합니다.

반응형

댓글