[Spring] Spring 핵심 원리(기본) 객체 지향 원리 적용(예제)

RepDay1·2023년 7월 22일

Spring

목록 보기
3/5

김영한 강사님 - 스프링 핵심 원리 기본편
을 수강하고 정리한 글 입니다.

스프링 핵심 원리 이해를 위한 예제 만들기

요구사항과 구현/테스트

  • 회원

    • 회원을 가입하고 조회할 수 있다.
    • 회원은 일반과 VIP 두 가지 등급이 있다.
    • 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다. (미확정)
  • 주문과 할인 정책

    • 회원은 상품을 주문할 수 있다.
    • 회원 등급에 따라 할인 정책을 적용할 수 있다.
    • 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라. (나중에 변경 될 수
      있다.)
    • 할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을
      미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 도 있다. (미확정)
  • 회원 클래스 다이어그램
    회원 클래스 다이어그램

  • 주문/할인 클래스 다이어그램
    주문/할인 클래스 다이어그램

각 설계대로 순수한 자바코드로 개발하고, junit프레임워크를 통한 테스트코드까지 작성함

  • 문제점
MemberServiceImpl.java
public class MemberServiceImpl implements MemberService {
 private final MemberRepository memberRepository = new MemoryMemberRepository();
 ...
}
OrderServiceImpl.java
public class OrderServiceImpl implements OrderService {
 private final MemberRepository memberRepository = new MemoryMemberRepository();
 private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
 ...
 }

다형성도 활용하고 인터페이스와 구현 객체를 분리했지만, OCP와 DIP 넓게보면 SRP까지 지켜지지않았음
MemberServiceImpl은 MemberRepository(인터페이스)도 의존하고, MemoryMemberRepository(구현체)에도 의존함(OrderServiceImpl도 마찬가지)
--> DIP 위반
만약 여기서 MemberRepository의 구현체를 mysql기반 DBMemberRepository로 변경한다 가정하면, 클라이언트인 MemberServiceImpl, OrderServiceImpl 코드에 변경이 발생하게됨
--> OCP 위반
MemberServiceImpl과 OrderServiceImpl은 어떻게 보면 객체 생성까지 담당하고있음 --> SRP 위반

객체 지향 원리 적용하여 문제 해결

DIP를 위반하지 않도록 인터페이스에만 의존하도록 의존관계를 변경하면 해결이됨

  • AppConfig 추가
    애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는
    별도의 설정 클래스인 AppConfig를 추가
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();
 }
  • MemberService, OrderService 구현체가 인터페이스만 의존하도록 변경
public class OrderServiceImpl implements OrderService {
 //private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
 private DiscountPolicy discountPolicy;
 
 public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}

이런식으로 MemverServiceImpl도 인터페이스만 의존하도록 변경하고 생성자를 통해 어떤 구현체를 주입시킴
클라이언트입장에선 어떤 구현체가 주입될지는 모르고 AppConfig만 알고있음

  • 문제점 해결 - SOLID원칙
    • DIP 해결 : 각 클라이언트는(MemberServiceImpl,OrderServiceImpl)은 인터페이스에만 의존하게됨
    • OCP 해결 : 어떤 구현체가 적용되던 클라이언트의 코드에는 변경이 없음(AppConfig에 반환부만 변경하면됨)
    • SRP 해결 : 이제 구현체를 생성하고 주입시키는 역할을 클라이언트가안하고 AppConfig가 하게되므로, 역할이 철저히 구분됨

AppConfig가 각 클라이언트의 생성자를 통해 구현체를 주입시켜줌 이걸 DI(의존관계 주입)이라고하고, 이런 DI를 해주는 AppConfig같은 클래스를 DI컨테이너라고함

  • 정리
    • AppConfig를 통해서 관심사를 확실하게 분리했음.
    • AppConfig는 구체 클래스를 선택, 애플리케이션이 어떻게 동작해야 할지 전체 구성을 책임짐
    • OrderServiceImpl, MemberServiceImpl 은 기능을 실행하는 책임만 지면됨

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

정가할인(FixDiscountPolicy) 에서 정률할인(RateDiscountPolicy)로 변경이 일어났다 가정하면,
AppConfig의 disCountPolicy() 메소드의 반환부를 RateDiscountPolicy로 변경만하면됨
image

위 그림에서 사용영역의 변경은 일절 일어나지않고, 구성영역의 AppConfig에만 변경이 일어남

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

위 3가지 DIP, OCP, SRP 위반했던 문제점이 AppConfig를 통해 해결이 됐음, 이를 다시 최종 정리해보면

  • DIP 의존관계 역전 원칙 프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안된다.” 의존성 주입은 이 원칙을 따르는 방법 중
    하나다.
    • 새로운 할인정책을 적용하려고 하니, 사용 영역의 클라이언트 코드도 변경해야했음, 기존 클라이언트 코드는 구현 클래스도 의존했기 때문
    • 클라이언트코드가 인터페이스만 의존하도록 변경했지만, 인터페이스만으로는 실행 할 수 없음
    • 고로 AppConfig가 객체 인스턴스를 클라이언트 코드 대신 생성해서 클라이언트의 생성자를 통해 주입시켜줌(DI)
    • DIP위반 해결
  • OCP 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다
    • 다형성과 AppConfig라는 DI컨테이너를 통해 클라이언트가 DIP를 지킴
    • 애플리케이션을 사용 영역과 구성 영역으로 나눔
    • AppConfig가 의존관계를 FixDiscountPolicy -> RateDiscountPolicy로 변경해서 클라이언트 코드에 주입하므로 클라이언트 코드는 변경하지 않아도됨
    • 소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경에는 닫혀 있음
  • SRP 단일 책임 원칙 한 클래스는 하나의 책임만 가져야 한다.
    • 이전 클라이언트는 직접 구현 객체를 생성하고, 연결하고, 실행하는 다양한 책임이 있었음
    • SRP에 따르면서 관심사를 분리함
    • 구현 객체를 생성하고 연결하는 책임은 AppConfig가 담당
    • 클라이언트 객체는 실행하는 책임만 담당하게됨

IoC, DI, 컨테이너

  • 제어의 역전 IoC(Inversion Of Control) : 프로그램의 제어 흐름을 직접 제어하는게 아니라 외부에서 관리하는 것을 제어의 역전이라고함

    • 프레임워크 : 프레임워크가 개발자가 작성한 코드를 제어하고, 대신 실행하는것(테스트코드 작성시 사용했던 JUnit같은거)
    • 라이브러리 : 개발자가 작성한 코드가 직접 제어의 흐름을 담당하면 라이브러리
  • 의존관계 주입 DI(Dependency Injection) : 정적인 의존 관계와 실행 시점에 결정되는 동적인 객체(인스턴스) 의존 관계 둘을 분리해서 생각해야함

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

    • AppConfig처럼 객체를 생성하고 관리하면서 의존 관계를 연결해 주는 것을 IoC컨테이너 또는 DI컨테이너라고함
    • 의존관계 주입에 초점을 맞추어 최근에는 주로 DI컨테이너라고 불림
    • 또는 어셈블러, 오브젝트 팩토리 등으로 불리기도함

0개의 댓글