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

목차

DIP 원칙 위반 - 추상화와 구체화 둘다 의존

MemberServiceImpl 클래스는 MemberRepository 인터페이스를 의존하면서 구현 클래스인 MemoryMemberRepository도 의존하고 있다.

public class MemberServiceImpl implements MemberService{

// MemberServiceImpl 클래스는 MemberRepository 인터페이스와 구현체인 MemoryMemberRepository 둘다 의존하고 있다.
private final MemberRepository memberRepository = new MemoryMemberRepository();

@Override
public void join(Member member) {
memberRepository.save(member);
}

@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
}

OrderServiceImpl 클래스는 MemberRepository 인터페이스와 DiscountPolicy 인터페이스를 의존하면서 구현 클래스인 MemoryMemberRepository 와 RateDiscountPolicy 를 의존하고 있다.

추상화구체화 둘다 의존하는 문제가 있다.
–> DIP 위반

DIP의 원칙을 지키기 위해 의존 class내에서 구현 클래스를 가져오는 부분을 삭제하게 되면 객체가 존재하지 않아서 nullPointException이 발생한다.

public class OrderServiceImpl implements OrderService {

// OrderServiceImpl 클래스는 MemberRepository 인터페이스와 구현체인 MemoryMemberRepository
// DiscountPolicy 인터페이스와 구현체인 RateDiscountPolicy 를 의존하는 문제점이 있다.
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();

@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);


return new Order(memberId, itemName, itemPrice, discountPrice);
}
}

OCP 원칙 위반 - 정책 변경시 client 코드를 수정

DiscountPolicy 의 구현체가 FixDiscountPolicy 에서 RateDiscountPolicy 로 바뀌게 되면 OrderServiceImpl 에서 FixDiscountPolicy를 RateDiscountPolicy로 변경하기 위해서 코드를 수정해줘야 한다.

구현 객체를 변경하기 위해서 클라이언트 코드를 변경
–> OCP 위반

OrderServiceImpl.java

public class OrderServiceImpl implements OrderService {

private final MemberRepository memberRepository = new MemoryMemberRepository();

// FixDiscountPolicy 에서 RateDiscountPolicy 로 구현체를 변경하기 위해서는 Client 코드를 변경해야 하는 문제점이 있다.
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();

@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);


return new Order(memberId, itemName, itemPrice, discountPrice);
}
}

객체의 생성 및 연결과 실행하는 부분을 분리

애플리케이션의 전체 동작방식을 구성하기 위해 구현 객체 생성연결 을 위한 별도의 설정 class 를 만들어준다.

관심사의 분리가 이루어지게 됐다.
–> 객체 생성 및 연결실행하는 부분의 logic이 분리가 됐다.

AppConfig 클래스 내의 memberService 메소드는 MemberService의 구현체인 MemberServiceImpl를 반환한다.
MemberServiceImpl 객체를 생성할 때 MemberRepository의 구현체인 MemoryMemberRepository 객체를 주입해준다.

MemberServiceImpl 생성자를 통해 MemoryMemberRepository 객체를 주입해주는 것을 생성자를 통한 의존성 주입이라 한다.

AppConfig.java

public class AppConfig {

// memberService 메소드에서 MemberServiceImpl 객체 생성 및 MemberRepository 객체에 대한 의존성 주입을 해준다.
// 의존성 주입시 인터페이스 및 추상 클래스에 대한 구현 객체가 들어가기 된다. MemoryMemberRepository
public MemberService memberService(){
return new MemberServiceImpl(new MemoryMemberRepository());
}

// orderService 메소드에서 OrderServiceImpl 객체 생성 및 MemberRepository 객체와 DiscountPolicy 객체에 대한 의존성 주입을 한다.
public OrderService orderService(){
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
  • 의존성을 주입 받기 위한 방법으로 생성자를 만들어 줬다.
  • MemberServiceImpl 는 객체 생성시 외부로부터 MemberRepository 에 대한 의존성을 주입 받게 된다.
  • MemberRepository 에 대한 구현 클래스 관리는 더 이상 MemberServiceImpl 에서 관리하지 않고 외부에서 관리하게 됐다.
  • MemberServiceImpl 는 이제 MemberRepository 인터페이스에만 의존하게 되므로 DIP 원칙을 지키게 됐다.

MemberServiceImpl.java

public class MemberServiceImpl implements MemberService{

private final MemberRepository memberRepository;

public MemberServiceImpl(MemberRepository memberRepository){
this.memberRepository = memberRepository;
}

@Override
public void join(Member member) {
memberRepository.save(member);
}

@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
}
  • OrderServiceImpl 클래스 역시 객체 셍성시 의존 객체를 주입 받기 위해 생성자를 만들어 줬다.
  • OrderServiceImpl 객체 셍성시 MemberRepository 와 DiscountPolicy 에 대한 구현 클래스를 주입 받게 된다.
  • OrderServiceImpl 클래스 내에서 구현 클래스를 곤리하는 부분이 없어졌으므로 DIP 원칙 을 지키게 됐다.

OrderServiceImpl.java

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

@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);


return new Order(memberId, itemName, itemPrice, discountPrice);
}
}

MemberServiceImpl와 OrderServiceImpl는 인터페이스에만 의존하게 함으로 코드상으로는 어떤 구현 객체가 들어올지는 알 수 없고, 생성자를 통해 외부에서 구현객체를 주입 받게 된다.

AppConfig를 통해 객체의 생성과 연결을 진행 하고
MemberServiceImpl와 OrderServiceImpl는 실행만 하면 되므로 역할이 분리가 됐다.
또한 외부에서 MemberServiceImpl와 OrderServiceImpl에 구현객체를 넣어주는 것을 보고 의존성 주입이라 한다.

AppConfig 리펙터링

AppConfig.java

public class AppConfig {

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

private MemberRepository memberRepository() {
return new MemoryMemberRepository();
}

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

private DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
}

메소드명과 return 타입을 통해 역할을 할 수 있게 됐고, 반환 객체를 통해 구현 객체를 알기 쉽게 됐다.

구현 객체 변경

AppConfig.java

public class AppConfig {

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

private MemberRepository memberRepository() {
return new MemoryMemberRepository();
}

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

private DiscountPolicy discountPolicy() {
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}

구현 객체를 변경하게 되면 AppConfig만 변경하면 된다.

AppConfig의 등장으로 구현 객체들은 자신의 로직을 실행하는 역할만 담당하게 됐다. 프로그램의 제어 흐름은 이제 AppConfig에서 담당한다.

Share