Home 예제에 객체 지향 원리 적용
Post
Cancel

예제에 객체 지향 원리 적용

이 게시글은 인프런의 유료 강의 스프링 핵심 원리 - 기본편을 수강하며 공부한 내용을 담고있습니다.
저작권에 의해 강의내용에 해당하는 전체 코드는 공개할 수 없습니다.
저작권에 의해 모든 내용을 담을 수 없습니다. 중복 및 반복되는 내용은 생략했습니다.

새로운 할인 정책

할인 정책이 미확정일 때는 일단 VIP 는 주문 금액에 상관없이 고정적으로 1,000원을 할인해주기로 했었다.
갑자기 할인 정책이 바뀌어서 이번에는 주문 금액의 10%를 할인해주기로 했다.
사진1
객체 지향적으로 잘 설계를 했다면, 정률 할인 정책을 개발해서 정액 할인 정책 자리에 바꿔 끼워주면 된다.

개발

사진2
간단하게 할인 금액을 리턴하는 메서드를 작성한다.

테스트

사진3
방금 만든 메서드가 정상적으로 동작하는지 테스트한다.
테스트는 성공 테스트와 실패 테스트 두 개를 만들어 검증해야한다.
정상인걸 확인했다.

적용

사진4
할인 정책 인터페이스에 정률 할인 정책을 작성해준다.

문제점

  1. 역할과 구현을 분리했다 -> 문제 없음
  2. 다형성도 활용하고, 인터페이스과 구현 객체를 분리했다 -> 문제 없음
  3. OCP, DIP 객체 지향 설계 원칙을 준수했다 -> 준수한 것처럼 보이지만 준수하지 않았다.

하나씩 살펴본다.
DIP
DIP(의존관계역전법칙)는 구현에 의존하지 말고 추상에 의존해야한다는 원칙이다.
그럼 구현체가 아닌 인터페이스를 의존해야한다. OrderServiceImpl 을 보면 discountPolicy = new FixDiscountPolicy 또는 RateDiscountPolicy 로 되어있는게 보인다.
DIP 원칙을 잘 준수하였다면 OrderServiceImpl 은 FixDiscountPolicy 또는 RateDiscountPolicy 와 같은 구현체를 알수없어야 한다. 하지만 알고 있으므로 DIP 원칙을 지키지 못한것이다.

OCP
OCP(개방폐쇄원칙)는 확장에 열려있고 변경에 닫혀있어야 한다는 원칙이다.
기능은 확장할 수 있으면서 코드 변경은 하면 안된다.
OrderServiceImpl 에서 new FixDiscountPolicy 를 new RateDiscountPolicy 로 변경한 순간부터 OCP 원칙을 지키지 못한것이다.
코드를 변경하지 않으면서도 기능을 변경할 수 있어야한다.

위 사례를 통해 DIP와 OCP에 대한 개념을 알 수 있다.

OCP와 DIP 원칙 준수하는 방법

위에서 말했듯 OrderServiceImpl 이 구현 객체를 알고있기 때문에, DIP 원칙을 지키지 못한것이라고 했다.
따라서 간단하게 바꿀 수 있다.

1
2
3
4
5
// OrderServiceImpl 클래스에서..
// DIP 위배
private DiscountPolicy discountPolicy = new RateDiscountPolicy();
// DIP 준수
private DiscountPolicy discountPolicy;

이렇게 고치면 된다. OrderServiceImpl 은 구현체를 알 수 없다!
그리고 아까 작성한 테스트 코드를 실행하면 당연히 NullPointerException이 발생한다.
그럼 어떻게 해야할까? DIP 를 지킬수 없는 것일까? 그렇지 않다.
OrderServiceImpl 의 discountPolicy 객체에 다른 누군가 구현체를 만들어 넣어주면된다. 즉 외부에서 구현체를 생성하여 주입해주면 된다.
또, 이렇게 코드를 작성하면 OrderServiceImpl에서 FixDiscountPolicy 에서 RateDiscountPolicy 로 수정할 필요도 없어진다. 즉 OCP 원칙도 지켜지게 된다.

생성자 주입

DIP 원칙을 잘 지키면 OrderServiceImpl 은 discountPolicy 가 누구인지 모른다. 정률 할인 정책인지, 정액 할인 정책인지, 그밖에 다른 할인 정책인지 전혀 알 수 없다.
OrderServiceImpl 은 자신의 역할에 충실하게 그저 discountPolicy 에서 discount 를 요청할뿐이다.
하지만 현재 코드로는 discountPolicy 에 아무것도 들어있지 않다.(null 인 상태)
discountPolicy 에 구현체를 집어넣기 위해 생성자를 사용할 것이다.

1
2
3
4
5
6
7
// OrderServiceImpl 클래스에서...
// memberRepository 는 생략
private final DiscountPolicy discountPolicy;

public OrderServiceImpl(DiscountPolicy discountPolicy) {
  this.discountPolicy = discountPolicy;
}

이렇게 생성자를 통해 외부에서 구현체를 받아오는 방법이 있다. 이 방법은 생성자를 통해 구현체를 주입했기 때문에 생성자 주입 이라고 부른다.
그리고 구현체를 주입하는 코드를 총체적으로 관리하기 위해 패키지 밑에 AppConfig 라는 클래스를 하나 만든다.
애플리케이션 전반에 대한 설정을 작성하는 파일이기 때문에 AppConfig 라는 이름을 붙인다.

1
2
3
4
5
// AppConfig 클래스에서...
// MemberService 는 생략
public OrderService orderService() {
    return new OrderServiceImpl(new RateDiscountPolicy());
}

AppConfig 에서 MemberServiceImpl, MemoryMemberRepository, OrderServiceImpl, RateDiscountPolicy 를 만들고 필요한 곳에 연결시켜주니 각 객체는 어떤 구현체를 사용하는지 전혀 몰라도 된다. 이제 이렇게 DIP 를 준수하게 되었다.

의존관계 주입(DI)

생성자 주입은 DI(Dependency Injection) 의존관계 주입(의존성 주입) 중 한 가지 방법이다.
의존관계 주입은 외부에서 의존관계를 주입 시켜준다는 의미이다.
생성자 주입 코드를 보자.

1
2
3
4
5
6
7
8
9
public class OrderServiceImpl implements OrderService{
    MemberRepository memberRepository;
    DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}

OrderServiceImpl 입장에서는 memberRepository 와 discountPolicy 를 내부에서 생성한 것이 아닌 외부에서 주입받은 것처럼 보인다.
여기서 주입 방법이 생성자를 통해서 이루어졌기 때문에 생성자 주입이라고 부르는 것이다. DI 는 생성자 주입 말고도 다른 방법이 존재한다.

AppConfig 리팩터링

1
2
3
4
5
6
7
8
public class AppConfig {
    public MemberService memberService() {
        return new MemberServiceImpl(new MemoryMemberRepository());
    }
    public OrderService orderService() {
        return new OrderServiceImpl(new MemoryMemberRepository(), new RateDiscountPolicy());
    }
}

AppConfig 코드를 보면 역할과 구현이 분리되지 않은 모습과 중복된 코드가 보인다.
memberService 는 어떤 역할과 관계를 맺고 있는지 잘 보여야하는데, 구현체가 와서 잘 보이지 않는다.
또한 MemoryMemberRepository 는 memberService 와 orderService 메서드 두 곳에서 사용되고 있다. 추후 MemoryMemberRepository 대신 다른 DB로 결정되면 일일히 수정해줘야한다.
따라서 다음과 같이 수정하여 유지보수성도 높이고 관계를 잘 보여줄 수 있도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AppConfig {
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }
    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }
    private MemberRepository memberRepository() {
        // 여기만 수정하면 memberService 와 orderService 가 참조하는 DB가 바뀜
        return new MemoryMemberRepository(); 
    }
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }
}



이전 포스팅 : 기본 예제 만들기
다음 포스팅 : 객체지향 5원칙 SOLID

This post is licensed under CC BY 4.0 by the author.

기본 예제 만들기

객체지향 5원칙 SOLID