Jin's IT Story
[JAVA] SOLID 원칙 이해하기(LSP, ISP, DIP) 본문
목차
소프트웨어 개발에서 객체지향 프로그래밍은 유지보수성과 확장성을 높이는 핵심적인 방법론으로 자리 잡고 있습니다. 그중에서도 SOLID 원칙은 자바(Java) 개발자를 포함한 대부분의 프로그래머들이 반드시 이해하고 실천해야 하는 중요한 지침이라 할 수 있습니다.
본 글에서는 SOLID 원칙 가운데 후반부에 해당하는 리스코프 치환 원칙(Liskov Substitution Principle, LSP), 인터페이스 분리 원칙(Interface Segregation Principle, ISP), 그리고 의존성 역전 원칙(Dependency Inversion Principle, DIP)에 대해 자세히 살펴보겠습니다.
리스코프 치환 원칙(LSP)
리스코프 치환 원칙은 1987년 바바라 리스코프(Barbara Liskov)가 제안한 개념으로, 객체지향 프로그래밍에서 상속을 어떻게 올바르게 사용해야 하는지를 설명하는 핵심 원칙입니다.
정의를 간단히 요약하면 "상위 타입의 객체를 하위 타입 객체로 치환해도 프로그램의 동작이 올바르게 유지되어야 한다"는 것입니다. 이는 곧 상속받은 클래스가 부모 클래스의 행위를 무너뜨리지 않고 일관성 있게 확장해야 한다는 의미를 담고 있습니다.
예를 들어, 자바에서 Rectangle
클래스를 상속한 Square
클래스를 생각해 보겠습니다. 사각형은 가로와 세로가 다를 수 있지만, 정사각형은 반드시 가로와 세로가 같아야 합니다.
만약 Square
클래스가 Rectangle
의 세터 메서드를 그대로 사용한다면, 가로와 세로를 다르게 설정하는 순간 정사각형의 본질을 위배하게 됩니다. 이런 경우는 리스코프 치환 원칙을 어긴 전형적인 사례입니다. 따라서 올바른 설계를 위해서는 상속보다는 별도의 추상화나 컴포지션을 활용하는 것이 더 적절할 수 있습니다.
리스코프 치환 원칙을 지키면 코드 재사용성이 높아지고, 유지보수 과정에서 예기치 못한 오류가 줄어듭니다. 자바의 다형성을 활용할 때 LSP는 특히 중요한 기준이 되며, 팀 프로젝트에서 공통 모듈을 설계할 때 반드시 고려해야 하는 요소입니다.
인터페이스 분리 원칙(ISP)
인터페이스 분리 원칙은 "클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다"는 개념을 중심으로 합니다. 다시 말해, 하나의 큰 인터페이스에 여러 기능을 모두 몰아넣기보다는, 역할과 책임에 따라 인터페이스를 분리하여 설계해야 한다는 원칙입니다.
자바 예시로 살펴보면, Machine
이라는 인터페이스에 print()
, scan()
, fax()
메서드를 모두 정의해 둔다고 가정해봅시다. 그런데 어떤 구현체는 프린트 기능만 필요하고, 어떤 구현체는 스캔 기능만 필요할 수 있습니다. 이때 모든 클래스가 사용하지 않는 메서드까지 억지로 구현해야 한다면 불필요한 의존성과 코드 복잡성이 증가하게 됩니다. 이는 ISP를 위반하는 상황입니다.
이를 해결하기 위해서는 Printer
, Scanner
, Fax
와 같이 인터페이스를 분리하고, 각 구현체가 자신에게 필요한 인터페이스만 구현하도록 설계하는 것이 좋습니다. 이렇게 하면 코드의 가독성이 높아지고, 단일 책임 원칙(SRP)과도 자연스럽게 연결되어 견고한 객체지향 구조를 구축할 수 있습니다.
또한 유지보수 단계에서 기능을 추가하거나 변경할 때, 다른 클래스에 불필요한 영향을 주지 않기 때문에 생산성이 크게 향상됩니다.
의존성 역전 원칙(DIP)
의존성 역전 원칙은 "상위 모듈이 하위 모듈에 의존하지 않고, 둘 다 추상화에 의존해야 한다"는 원칙을 담고 있습니다.
전통적인 구조에서는 고수준 모듈(비즈니스 로직)이 저수준 모듈(구현 클래스)에 직접 의존하는 경우가 많습니다. 그러나 DIP를 적용하면 인터페이스나 추상 클래스와 같은 추상화 계층을 도입하여, 고수준 모듈과 저수준 모듈이 서로 독립성을 유지할 수 있도록 만듭니다.
자바에서 흔히 사용하는 의존성 주입(Dependency Injection) 패턴은 바로 DIP를 실현하는 대표적인 방식입니다.
예를 들어, 결제 시스템을 구현할 때 PaymentService
가 특정 CreditCardPayment
클래스에 직접 의존한다면, 새로운 결제 수단을 추가할 때마다 PaymentService
를 수정해야 합니다. 그러나 PaymentMethod
라는 인터페이스를 두고 CreditCardPayment
, KakaoPayPayment
, PayPalPayment
등을 구현하면, PaymentService
는 인터페이스에만 의존하게 되어 확장성과 유연성이 극대화됩니다.
이러한 구조는 테스트 환경 구성에서도 큰 장점을 줍니다. 의존성 역전을 통해 인터페이스를 기준으로 목(Mock) 객체를 주입하면 단위 테스트 작성이 훨씬 쉬워지고, 실제 서비스 운영 중에도 특정 구현체 교체가 유연해집니다.
리스코프 치환 원칙(LSP), 인터페이스 분리 원칙(ISP), 의존성 역전 원칙(DIP)은 자바 객체지향 설계에서 매우 중요한 후반부 SOLID 원칙들입니다.
LSP는 올바른 상속 관계를 보장하고, ISP는 인터페이스 설계의 유연성을 높이며, DIP는 추상화를 통한 모듈 간 독립성을 강화합니다. 이 세 가지 원칙을 준수하면 소프트웨어의 유지보수성과 확장성이 향상될 뿐 아니라, 팀 협업 과정에서도 예측 가능한 구조를 제공하여 생산성을 높일 수 있습니다.
궁극적으로 SOLID 원칙은 단순히 이론적인 지침이 아니라, 실제 자바 개발 현장에서 반복되는 문제를 해결하기 위해 만들어진 실천 가이드라인입니다. 따라서 프로젝트를 설계하거나 리팩토링 할 때 이번 글에서 다룬 원칙들을 적극적으로 적용해 보시길 권장드립니다.
이는 결과적으로 안정적인 코드 베이스를 구축하고, 장기적인 운영 비용을 절감하는 데 큰 도움이 될 것입니다.
2025.09.05 - [DevBasics: 개발 개념 기초 다지기] - [JAVA] SOLID 원칙 이해하기(SRP과 OCP)
'DevBasics: 개발 개념 기초 다지기' 카테고리의 다른 글
쉽게 배우는 IT 용어 재귀 구조 정의와 활용 (0) | 2025.09.16 |
---|---|
인터럽트란? 개념과 작동 원리 (0) | 2025.09.15 |
컴파일 언어 종류와 차이점 비교 (0) | 2025.09.09 |
WCAG 웹 접근성 가이드(WCAG 핵심 변화, KWCAG) (0) | 2025.09.08 |
UX/UI ISO 표준 총정리: 최신 동향과 적용 사례 (0) | 2025.09.07 |