객체지향의 꽃 다형성으로 부터 DI까지

진성대·2023년 9월 18일
0

java spring

목록 보기
12/15

다형성

  • 스프링은 다형성을 극대화하여 이용할 수 있다.

역할과 구현

  • spring 의 다형성은 쉽게 interface 와 class(구현 객체) 로 설명할 수 있는데,
    interface 로 구현된 역할을 class를 통해서 구현할 수 있어서 서로간에 기능에 연관을 크게 미치지 않을 수 있다.

예시)

@mapper
public interface ExampleMapper {
	List<ExampleDto> findExampleList();
    ExampleDto findExampleById(String id);
}

구현체

<select id="findExampleList">
	SELECT *
	FROM EXAMPLE
</select>

<select id="findExampleById">
	SELECT *
	FROM EXAMPLE
    WHERE ID = #{id}
</select>

의존관계 역전 원칙 - DIP

  • 객체를 구현할때 인터페이스를 봐라봐야지 인터페이스를 상속받은 구현객체를 바라보게 하면 안된다
  • 모든 설계에 역할과 구현을 분리하자!
private final DiscountPolicy = new FixDiscountPolicy();
  • DiscountPolicy라는 인터페이스에 FixDiscountPolicy 라는 구현객체를 동적 할당했다.
  • 다형성으로 DiscountPolicyimplementsFixDiscountPolicy를 동적 할당 할 수 있고 DiscountPolicyimplements한 다른 구현객체를 동적 할당 할 수 도 있다.

회원객체 다이어그램

클라이언트 - > 회원 서비스 -> (메모리)회원 저장소
  • 역할로 회원 서비스나, 회원 저장소들의 연관관계를 매핑할 수 있고 이러한 역할들이 정확히 어떤 기능들로 구현되어있는지는 모른다.
  • 그렇기에 역할과 매핑된 기능들의 확장성을 증가시킬 수 있다.

주문 도메인 협력 역할 책임을 통해서 상세하게 설명하자면

  1. 주문생성: 클라이언트는 주문서비스에 주문생성을 요청한다
  2. 회원 조회 : 할인을 위해서는 회원등급이 필요하다. 그래서 주문 서비스는 회원 저장소에서 회원을 조회한다.
  3. 할인적용: 주문 서비스는 회원 등급에 따른 할인 여부를 할인 정책에 위임한다.

private final DiscountPolicy = new FixDiscountPolicy();

하지만 이 방식은 interface를 통해서 역할과 기능을 분리하는 줄 알았는데
FixDiscountPolicy 대신 DiscountPolicy 로 정책을 변경하게 된다면 *orderServiceImpl에서 코드를 수정해 줘야하는 상황이 발생한다.

//private final DiscountPolicy = new FixDiscountPolicy();
private final DisocuntPolicy = new RateDiscountPolicy();
  • 따라서 이는 DIP(의존관계 역전 원칙) 위반, OCP(개방 - 폐쇄 원칙) 위반하는 코드가 되는 것이다

해결 방안

  • 구현체에 의존하지 않도록 동적할당 하는 부분을 삭제해주면 되지 않을까?
  • 하지만 기존 코드에서 구현체를 삭제하면 Null Point Exception 발생!!
  • 누군가가 클라이언트인 OrderServiceImpldiscountPolicy의 구현객체를 대신 생성하고 주입 해주면 되지 않을까??

관심사 분리

  • 기존 코드는 OrderServiceImpl 이라는 객체가 상대 배우를 자신의 상대역할(줄리엣)로 초빙하는 것
  • 즉, 자신이 하는 일 제외하고 다른 역할도 있는 "다양한 책임"을 가지고 있다.

    관심사를 분리해야 한다!!
    "기획자의 등장" '누가' 어떤 역할에 배우를 넣어줄지 정하는 일은 배우가 하는 것이 아닌 어떤 '무엇'인가가 하는 일

기획자의 등장

  • AppConfig
    -> 생성자를 생성시켜서 그 안에서 구현체를 주입한다.
    -> 생성자를 통해 어떤 구현 객체가 들어올지는 알 수 없다. 어떤 구현객체가 들어 올지는 객체에서 정하는 것이 아닌 AppConfig에서 정해준다.

AppConfig Code

@Configuration
public class AppConfig {

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

    @Bean
    public static MemoryMemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

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

    @Bean
    public static FixDiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }
}

OrderApp Code

public class OrderApp {
    public static void main(String[] args) {
        AppConfig appConfig = new AppConfig();
        MemberService memberService = appConfig.memberService();
        OrderService orderService = appConfig.orderService();



        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);
    }
}
  • AppConifg 객체는 각 Interface 어떤 구현 객체를 넣어줄지 동적으로 할당해 주었다.
  • OrderApp 객체는 AppConfig 를 호출해서 MemberService InterfaceOrderService Interface가 어떤 구현 객체를 사용할 지는 생성자를 호출하는 시점, Runtime(실행시점) 에 알 수 있게 한다. 즉, 오직 기능에 초점을 두었다.
  • DIP 완성: MemoryServiceImplmemberRepository인 추상화에만 의존하면 된다. 다시 말해 AppConfig를 통해서 구현 객체는 그 기능 자체에만 집중을 할 수 있게 되었다.

SRP - 단일 책임 원칙

  • 한 클래스는 하나의 책임만 가진다

    구현객체를 생성하고 연결하는거는 AppConfig가 담당
    클라이언트 객체는 실행만 담당

DIP - 의존관계 역전 원칙

  • 생성자를 주입시킴으로써 객체가 코드안에 다른 구현객체를 의존하는 것이 아니라 AppConfig라는 구현 객체를 생성하고 연결하는 객체를 통해서 (그 클래스를 가짐으로써) 외부에서 객체를 주입시켜준다.

OCP

  • 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀있다.

    AppConfig가 의존 관계를
    FixDiscountPolicy -> RateDiscountPolicy
    로 변경해서 클라이언트 코드에 주입하므로 클라이언트 코드를 변경하지 않아도 된다.

profile
신입 개발자

0개의 댓글