Java Spring

객체 지향 설계 원칙 5가지(SOLID)를 지킨 프로그램 제작

pearl.k 2023. 5. 29. 18:39

좋은 백엔드 개발자라면, SOLID 원칙을 잘 지킨 코드를 짤 수 있어야 한다. Spring이 만들어진 이유도 이 5가지 원칙을 어떻게 하면 잘 지킬 수 있을지 고민하다가 생긴 것이라고 한다. 특히 이 5가지 원칙 중에서도 OCP, DIP 를 가장 중요하게 봐야 한다.

객체 지향 설계 원칙 5가지(SOLID)는 다음과 같다. 앞 글자를 따서 SOLID 원칙이라고 부른다.

  • SRP: 단일 책임 원칙(single responsibility principle)
  • OCP: 개방-폐쇄 원칙 (Open/closed principle)
  • LSP: 리스코프 치환 원칙 (Liskov substitution principle)
  • ISP: 인터페이스 분리 원칙 (Interface segregation principle)
  • DIP: 의존관계 역전 원칙 (Dependency inversion principle)

 

실제로 클래스를 만들어 보면서 클래스 의존 관계를 분석한 후, 원칙을 위반한 것이 있는지 확인해보는 연습을 했다. 이번에는 정해진 할인 정책을 상품에 적용하는 코드를 작성했다. 기존의 할인 정책을 변경하기 위해서는 클라이언트의 OrderServiceImpl 클래스를 수정해야 했다. 

public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
 private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}

 

여기서 문제점을 발견할 수 있었다.

  - 역할과 구현을 충실하게 분리했나? OK

  - 다형성도 활용하고, 인터페이스와 구현 객체를 분리했나? OK

  - OCP, DIP 같은 객체지향 설계 원칙을 준수했나? => 준수한 것 처럼 보이지만 사실은 X

  - DIP: 주문서비스 클라이언트( OrderServiceImpl )는 DiscountPolicy 인터페이스에 의존하면서 DIP 원칙을 지켰나?

  • 클래스 의존관계를 분석해 보자. 추상(인터페이스) 뿐만 아니라 구체(구현) 클래스에도 의존하고 있다.
  • 추상(인터페이스) 의존: DiscountPolicy
  • 구체(구현) 클래스: FixDiscountPolicy , RateDiscountPolicy

DIP 위반을 설명하는 다이어그램

또한, 지금 코드는 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 준다. 따라서 OCP를 위반한다. (OCP: 변경하지 않고 확장할 수 있다는 원칙)

그렇다면 어떻게 해결할 수 있을까? => 의존 관계를 바꾸면 된다.

구체 클래스를 함께 의존하지 않고, 추상 인터페이스에만 의존하도록 하면 DIP를 위반하지 않는다.

 

스프링 컨테이너 Spring Container?

스프링 컨테이너는 스프링 프레임워크의 핵심 컴포넌트이며, 자바 객체의 생명 주기 관리 / 추가 기능을 제공한다. 스프링에서는 자바 객체를 빈(Bean)이라고 칭한다. 즉, 스프링 컨테이너는 Bean의 생성, 관리, 제거, 추가 기능 제공을 한다.

자바 애플리케이션 안에는 수많은 객체가 존재하고, 서로를 참조하는 경우도 많다. 이렇게 참조가 많아질 수록 의존성이 높아지게 된다. 이는 낮은 결합도와 높은 캡슐화를 지향하는 객체 지향 프로그래밍과 멀어지게 된다는 뜻이다.이를 막기 위해서 스프링 컨테이너가 사용되는데, 객체 간의 의존성을 낮추고 캡슐화를 더 진행하기 위해 스프링 컨테이너가 사용된다. 스프링 컨테이너를 사용하면, 구체 클래스에 대한 의존을 제거하고, 추상 인터페이스에 의존하도록 설계할 수 있다. (객체 지향 프로그래밍 원칙 SOLID를 지킬 수 있다.)

 

스프링 빈 Spring Bean?

빈(Bean)은 스프링 컨테이너에 의해 관리되는 재사용 가능한 소프트웨어 컴포넌트이다. 스프링 컨테이너에 등록된 인스턴스화 된 객체를 스프링 빈이라고 한다. @Bean 어노테이션을 통해 메소드로부터 반환된 객체를 스프링 컨테이너에 등록한다.

Bean에는 충돌이 일어나지 않게 항상 다른 이름을 부여해야 한다. (실무에서 개발 시, Bean 이름 절대 중복되게 만들면 안됨. 무조건 단순하고 명확하게 개발하기!)