좋은 객체 지향 설계의 5가지 원칙(SOLID)과 Spring의 관계

Wonho Kim·2025년 3월 6일

Spring Boot

목록 보기
1/4

해당 게시글은 김영한 강사님의 스프링 핵심 원리 강의를 바탕으로 작성하였습니다.
https://www.inflearn.com/courses/lecture?courseId=325969&tab=curriculum&type=LECTURE&unitId=55396&subtitleLanguage=ko&audioLanguage=ko

좋은 객체 지향 설계의 5가지 원칙(SOLID)란 클린코드로 유명한 로버트 마틴이 정리한 원칙으로, SRP, OCP, LSP, ISP, DIP의 5가지 원칙에 대해 앞글자를 따서 SOLID라고 명명하였다.

SRP(Single Responsibility Principle)

단일 책임 원칙이라고 하며, 한 클래스는 하나의 책임만 가져야 한다는 의미이다. 이렇게 말하면 하나의 책임이 뭔지 궁금할 것이다.

사실 여기서 가장 중요한 기준은 변경 사항에 따른 파급효과 정도이다.
예를 들어 UI 변경 작업이 필요할 떄 파급효과가 적다면 단일책임원칙을 잘 따랐다고 볼 수 있다.

OCP(Open-Closed Principle)

개방-폐쇄 원칙이라고 한다. 확장에는 열려 있으나 변경에는 닫혀 있어야 한다는 어려운 말을 자주 하는데, 여기서는 다형성을 떠올리면 된다.

인터페이스를 구현하는 구현체를 하나 더 새롭게 만들어서 새로운 기능을 구현하는 방식으로 접근하면 된다. 아래 코드를 생각해보자.

public class MemberService {
// private MemberRepository memberRepository = new MemoryMemberRepository();
 private MemberRepository memberRepository = new JdbcMemberRepository();
}

기존 코드는 MemberRepository의 구현체인 MemoryMemberRepository를 통해 저장소를 만들었지만, 새로운 구현체인 JdbcMemberRepository 구현체를 만들어주는 상황이 필요한 것이다.

언뜻보면 OCP를 지킨거 아닌가? 생각할 수 있는데, 기존의 클라이언트 코드를 주석처리 하고 새롭게 변경한 것이므로 OCP 원칙을 위배한 것이 된다.

이 말은 즉슨 분명 다형성을 사용했지만 OCP 원칙을 지킬 수 없다는 말이다.

그렇다면 객체를 생성하고 연관관계를 맺어주는 별도이 조립, 설정자가 필요하다!!

그래서 Spring에서는 DI 컨테이너라는 매우 중요한 개념을 통해 이 역할을 수행한다.

DI 컨테이너, DI 등의 개념은 나중에 후술하도록 하겠다.

LSP(Liskov Substitutioin Principle)

리스코프 치환 원칙으로, 프로그램 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스터스로 바꿀 수 있어야 한다.

다시 말해 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 원칙이다.

예를 들어 자동차 인터페이스의 엑셀은 앞으로 가라는 기능을 규약으로 정해놓았다. 만약 구현체에서 뒤로 가게 구현하면 LSP 위반이 된다는 말이다.

ISP(Interface Segregation Principle)

인터페이스 분리 원칙으로, 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.

예를 들어 자동차 인터페이스 대신, 운전 인터페이스와 정비 인터페이스로 분리하거나, 사용자 클라이언트 대신 운전자 클라이언트와 정비사 클라이언트로 분리하는 등이 있을 것이다.

분리하게 되면 인터페이스가 명확해지고, 대체 가능성도 높아진다.

DIP(Dependency Inversion Principle)

의존관계 역전 원칙으로, 프로그래머는 "추상화에 의존해야지, 구체화에 의존하면 안된다." 라고 표현한다.

스프링의 중요한 개념 중 하나인 의존성 주입(DI) 역시 이 원칙을 따르는 방법 중 하나이다.

쉽게 이야기해서 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻이다.

객체지향과 Spring

객체지향 설계 중 가장 중요한 요소 중 하나는 역할구현을 분리하는 것이다. 이를 자바 언어에서 사용하는 다형성이라는 개념을 사용하면 역할은 인터페이스로 구현은 인터페이스를 구현한 클래스로 생각할 수 있다.

이를 클라이언트와 서버 간의 관계에 대해 생각해본다면, 멤버들을 저장하는 MemberRepository라는 인터페이스 역할과, 이를 구현한 MemoryMemberRepository 또는 JdbcMemberRepository가 있는 것이다.

하지만 기존의 순수 자바 언어로는 아래와 같이 MemberService라는 클라이언트가 구현 클래스를 직접 선택하게 된다.

public class MemberService {
	...
    MemberRepository m = new MemoryMemberRepository();
    ...
}

이 말은 결국 MemberService라는 클라이언트는 인터페이스인 MemberRepository에도 의존하지만, 구현 클래스인 MemoryMemberRepository에도 의존하는 상황이 발생하고 이는 DIP 위반이 된다.

결국 다형성만으로는 OCP, DIP를 지킬 수 없다. 따라서 스프링은 DI(Dependency Injection) 컨테이너를 통해 의존관계 주입이라는 새로운 기술클라이언트 코드의 변경없이 기능 확장이 가능하다.

이번 시간에는 객체지향 5대 원칙과 스프링이 생길 수 밖에 없던 이유를 설명하였다. 다음에는 위에 설명한 DI 개념과 DI 컨테이너, 그리고 스프링에서 다루는 핵심 기술에 대해 정리하여 포스팅하도록 하겠다.

profile
새싹 백엔드 개발자

0개의 댓글