스프링을 사용하는 이유 (1) - OCP와 DIP를 지키자!

Chan Young Jeong·2023년 1월 13일
0

스프링

목록 보기
1/7
post-thumbnail

"본 게시글은 김영한님의 '스프링 핵심 원리 기본편'을 기반으로 작성된 글입니다."

자바의 가장 큰 특징은 객체 지향 언어라는 점입니다. 그렇기 때문에 자바를 이용하는 스프링 또한 객체 지향적으로 개발을 할 수 있게 됩니다.

"객체 지향적" 프로그램은 무엇을 의미할 까요? 객체를 레고 블록에 비유하면 여러 레고 블록을 조립하는 것처럼 객체를 조립하여 하나의 프로그램을 만드는 것이라고 볼 수 있습니다.

하지만 객체 지향적으로 프로그램을 설계하는 것은 쉬운 일이 아닙니다. 그래서 이를 위한 것이 바로 SOLID입니다. OCP와 DIP는 특히 객체 지향 언어와 연관이 많습니다.

  • OCP: Open/Close Princliple , 확장에는 열려야 있어야 하고, 변경에는 닫혀 있어야 한다는 원칙입니다.

  • DIP: Dependency Inversion Principle , 구현체에 의존하지 말고, 역할(인터페이스)에 의존해야 한다는 원칙입니다.

그래서 OCP, DIP를 지키면서 개발을 하려고 노력하지만 실상은 쉽지 않습니다. 그럼 한번 객체지향적으로 잘 작성된 것 같지만 실상은 그렇지 않은 구체적인 코드를 알아보겠습니다.

먼저 현재 상황은 다음과 같습니다. 옷 장사를 하는 A씨가 인터넷 쇼핑몰을 만들려고 하고 있습니다. 그래서 주문 서비스를 만들기 위해 A씨는 먼저 회원 데이터를 어디다가 저장할지 고민하고 있는 상태입니다. 자체 DB를 구축할 수 있고 외부 시스템과 연동할 수도 있는 상황입니다. 그리고 매출과 바로 연관되는 할인 정책에 대해 고민 중인데 일단 고정(정액) 할인 정책과 정률 할인 정책 둘을 모두 고려하고 있는 상황입니다.

이 상황에 대한 현재 클래스 다이어그램은 다음과 같습니다.

interface MemberRepository

public interface MemberRepository {\
 void save(String Name);
 void findMemberByName(String Name);
}

MemoryMemberRepository

public class MemoryMemberRepository implements MemberRepository{
	
    @Override
    void save(String Name){
    	System.our.println("시스템 메모리에 저장합니다." + "저장하는 멤버 =" + Name);
    }

	@Override
    void findMemberByName(String Name){
    	System.our.println("시스템 메모리에서 멤버를 찾습니다. " + "찾는 멤버 = " + Name);
    }
}

DbMemberRepository

public class DbMemberRepository implements MemberRepository{

	@Override
    void save(String Name){
    	System.our.println("자체 구축 DB에 저장합니다." + "저장하는 멤버 =" + Name);
    }
    
	@Override
    void findMemberByName(String Name){
    	System.our.println("자체 구축 DB에서 멤버를 찾습니다. " + "찾는 멤버 = " + Name);
    }
}

interface DiscountPolicy

public interface DiscountPolicy {
 void discount(int Price);
}

FixDiscountPolicy

public class FixDiscountPolicy implements DiscountPolicy{
	
    // 구매 금액에 대해 1000원 할인 적용
    private int discountFixAmount = 1000;
    
    @Override
    void discount(int Price){
    	System.our.println("할인 금액은 " + discountFixAmount + "입니다.");
    }
}

RateDiscountPolicy

public class RateDiscountPolicy implements DiscountPolicy{
	//구매 금액에 대해 10% 할인 적용
    private int discountRatio = 10;
    
    @Override
    void discount(int Price){
    	System.our.println("할인 금액은 " + price * discountRatio / 100 + "입니다.");
    }
}

interface OrderService

public interface OrderService {
	void createOrder(String memberName, String itemName, int itemPrice);
}

OrderServiceImpl

일단 A씨는 시스템 메모리에 회원 정보를 저장하고 고정 할인 정책을 이용하려고 합니다.

public class OrderServiceImpl implements OrderService{

	private final MemberRepository memberRepository = new MemoryMemberRepository();
 	private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    
    void createOrder(String memberName, String itemName, int itemPrice){
    	
        memberRepository.findByName(memberName);
        discountPolicy.discount(itemPrice);
        System.out.println( itemName + "주문이 완료 되었습니다.")
    
    }
}

그런데 갑자기 A씨는 시스템 자체 메모리가 너무 불안정해 DB로 저장소를 바꾸려고 합니다. 그리고 쇼핑몰 개업 이벤트로 10% 할인을 진행할려고 합니다.

public class OrderServiceImpl implements OrderService{

	private final MemberRepository memberRepository = new DbMemberRepository();
 	private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
    
    void createOrder(String memberName, String itemName, int itemPrice){
    	
        memberRepository.findByName(memberName);
        discountPolicy.discount(itemPrice);
        System.out.println( itemName + "주문이 완료 되었습니다.")
    
    }
}

얼핏보면 객체지향적으로 프로그램을 잘 작성한 것 같습니다. 그렇지만 이 코드는 OCP와 DIP를 위반하고 있습니다.

OCP 위배
OCP는 확장에는 열려있고 변경에는 닫혀 있어야 하는데 MemoryMemberRepository에서 DbMemberRepository로 변경하려면 코드를 변경해야 합니다. 그리고 FixDiscountPolicy에서 RateDiscountPolicy로 변경하려면 마찬가지로 코드를 변경해야 합니다.

DIP 위배
DIP는 역할에 의존하고 구현에는 의존하지 않아야 하는데 OrderServiceImp은 new DbMemberRepository(); new RateDiscountPolicy(); 처럼 구현 클래스를 직접적으로 의존하고 있습니다.

김영환님의 말을 인용하면

"로미오와 줄리엣 공연을 하면 로미오 역할을 누가 할지 줄리엣 역할을 누가 할지는 배우들이 정하는게 아니다. 이전 코드는 마치 로미오 역할(인터페이스)을 하는 레오나르도 디카프리오(구현체, 배우)가 줄리엣 역할(인터페이스)을 하는 여자 주인공(구현체, 배우)을 직접 초빙하는 것과 같다. 디카프리오는 공연도 해야하고 동시에 여자 주인공도 공연에 직접 초빙해야 하는 다양한 책임을 가지고 있다."

실제 의존 관계

스프링의 핵심 원리는 DIP, OCP 위배 문제를 해결하는데 있다고 생각합니다. 다음 시간에는 스프링에서의 DI(의존성 주입)에 대해 알아보고 이를 어떻게 해결하는지에 대해 알아보겠습니다.


출처

김영한-스프링 핵심 원리 기본편
https://kingofbackend.tistory.com/224

0개의 댓글