SOLID 원칙

date
slug
solid
status
Published
tags
OOP
summary
SOLID 원칙 알아보기
type
Post
매번 공부하지만 뒤돌면 까먹는 SOLID 원칙에 대해 알아보자.

SRP : Single Responsibility Principle

"책임은 변경하려는 이유다." - 로버트 C.마틴
하나의 클래스는 하나의 책임만을 가져야 한다. 즉, 하나의 클래스는 하나의 이유만으로 변경되어야한다.
너무나도 당연시 여겨지는 내용이지만, 의도치 않게 위반하는 경우가 생긴다. 예시를 들어보자.
  1. Controller - Service - Repository 구조에서, Service에서 예외가 발생했을 때 Http 상태 코드를 담은 예외를 던지는 경우 → 이 경우, Controller가 Http 요청을 받지 않도록 수정되면 Service도 함께 수정되어야하므로 SRP가 위반된다.
  1. Service가 여러개의 Repository와 이를 각각 사용하는 메서드를 한 클래스에 가지고 있는 경우 → 응집도가 떨어지고, 파편화된 메서드와 필드들이 섞여 있으므로 SRP가 위반된다.

OCP : Open/Close Principle

확장에는 열려있어야 하나 변경에는 닫혀있어야 한다. OCP를 지키는 코드는 클라이언트 코드가 추상화에 의존하고 있기 때문에 확장될 때와 변경될 때 모두 다른 코드에 영향을 주지 않는다.
  • 두 종류의 Repository 클래스가 존재하고, Service에서 if-else 문을 통해 상황에 따라 다른 레포지토리를 참조하는 경우
    • 위의 경우, Repository 종류가 추가/변경될 때, UserService의 코드를 수정해야하므로 OCP를 위반하는 경우이다.
따라서 UserRepository 인터페이스를 사용하여 분기를 없애고, 사용하는 Repository의 종류는 외부에서 결정하도록 하는 것이 좋다.
참고로 OCP를 설명할 때 인터페이스를 이용한 예시가 가장 일반적이지만, 꼭 인터페이스가 필요한 것은 아니며 Enum, 디자인패턴, 이벤트 기반 프로그래밍 등을 통해 OCP를 지킬 수 있다.

LSP : Liskov Substitution Principle

파생 클래스는 기반 클래스를 대체할 수 있어야한다. 쉽게말하면, 부모 클래스가 할 수 있는 행동은 자식 클래스도 할 수 있어야 한다는 원칙이다.
리스코프 치환 원칙이 성립하지 않는 경우
  • 부모클래스가 할 수 있는 동작을 자식 클래스가 할 수 없는 경우
    • 매번 부모 클래스인지, 자식 클래스인지 분기를 나누어 코드를 작성해야하므로 다형성을 전혀 활용할 수 없다.
    • 자식클래스에서 필드나 메소드의 접근제어자를 더 엄격하게 변경하는 경우 (public -> private)
    • 자식 클래스의 접근제어자가 더 엄격해지면, 부모클래스 레퍼런스 변수에 자식 클래스의 인스턴스를 할당했을 때 문제가 발생하므로 이는 불가능하다.
따라서 리스코프 치환원칙을 지키지 않는 상속은 하면 안된다. 참고: 계약에 의한 설계

ISP : Interface Segregation Principle

특정 클라이언트를 위한 인터페이스 여러개가 범용 인터페이스 하나보다 낫다. 하나의 인터페이스에 여러 개의 구현 클래스를 위한 메소드를 각각 정의해놓으면, 어느 한 쪽에서는 불필요한 메서드가 노출되는 상황이 발생한다. 이 경우에는 인터페이스를 구현 클래스별로 세분화하면, 불필요한 메소드가 노출되는 상황을 막을 수 있고 유지보수를 편리하게 할 수 있다.

DIP : Dependency Inversion Principle

고수준 컴포넌트는 저수준 컴포넌트에 의존하지 않아야 한다.
고수준 컴포넌트란 비즈니스 로직에 가까운 부분이고, 저수준 컴포넌트란 세부 구현, 구체적 기술, 하드웨어에 가까운 부분이다.
일반적으로, 구현 클래스에 직접 의존하지 않고 인터페이스를 중간에 끼워넣어 의존 관계를 역전시킬 수 있다.
OCP와 DIP 모두 인터페이스를 사용하는 것이 대표적인 방법이지만, 그 목적에 차이가 있다.
  • OCP: 확장, 변경 시 다른 코드들이 영향을 받지 않도록 하기 위함
  • DIP: 인터페이스를 사용하여 고수준 컴포넌트가 저수준 컴포넌트에 의존하지 않도록 바꾸기 위함
간접적으로 의존 역전 원칙이 깨지는 상황
  • 예외를 처리할 때, 고수준 컴포넌트가 처리하는 예외가 저수준 컴포넌트의 정보를 간접적으로 담고 있는 경우
    • 저수준 컴포넌트가 변경되면 고수준 컴포넌트의 예외 처리를 변경해야하므로 OCP / DIP를 모두 위반하게 된다.
    • 따라서, 저수준 컴포넌트(구현체)들이 추상적인 예외를 던지도록 캡슐화하고 고수준 컴포넌트는 하나의 예외만을 처리하도록 변경해야 한다.

정리

  • 변경은 피할 수 없으니, 확장을 염두에 두고 구현하자
  • 객체 또는 모듈 간의 의존성을 항상 의식적으로 파악하자
  • SOLID 원칙은 곧 테스트 코드까지 영향을 준다 → 곧 TDD로 이어짐

질문

  • 어디까지 추상화를 해야하는가?
    • 변경 가능성
    • 재사용 가능성
    • 테스트 코드 작성 시 용이한지
    • DIP 정책 위반 여부 (고수준 컴포넌트가 저수준 컴포넌트에 의존하고 있지는 않은지)
등을 적절히 고려해 추상화 여부를 결정하자.
YAGNI(You Aren’t gonna need it) 원칙이라는 것도 있다.

© Daehwi Kim 2025