다섯가지 원칙 SOLID는 아래와 같다.
단일 책임 원칙 (Single Responsibility principle)
개방 폐쇄 원칙 (Open Close Principle)
리스코프 치환 원칙 (Liscov Substitution Principle)
인터페이스 분리 원칙 (Interface Sergregation Principle)
의존성 역전 원칙 (Dependency Inversion Principle)
1. 변경에 유연한 구조
'변경에 유연하다'는 말은 곧 '결합도는 낮고, 응집도는 높은 구조'를 의미합니다.
예를 들어 A라는 회사의 시스템이 작은 기능 추가에도 수십 개의 테스트가 깨질 만큼 복잡하다면,
기능을 자주 추가하는 것이 어려울 것입니다.
이때 매달 새로운 기능을 출시할 수 있는 경쟁사 B가 있다면, A회사는 경쟁에서 뒤처질 수밖에 없습니다.
따라서 시스템은 항상 결합도가 낮고 응집도가 높은 상태를 유지하는 것이 중요합니다.
2. 이해하기 쉬운 구조
'이해하기 쉽다'는 것은 '가독성이 좋다'로 표현할 수 있습니다.
아무리 좋은 코드라도 본인만 이해할 수 있다면 좋은 설계라 할 수 없습니다.
깔끔한 코드는 디버깅과 유지보수를 쉽게 만들어 소프트웨어 품질 향상에 기여합니다.
이처럼 결합도가 낮고 응집도가 높으며 가독성이 좋은 코드를 작성하는 것이 모든 소프트웨어 설계가
추구하는 궁극적인 지향점입니다.
이를 통해 유연하고 유지보수하기 쉬운 소프트웨어를 개발할 수 있습니다.
Single Responsibility Principle
단일 책임 원칙이란 하나의 객체는 반드시 하나의 기능만을 수행하는 책임을 갖는다는 원칙이다. 이는 클래스를 변경하는 이유는 오직 하나뿐이어야 한다는 것과 같은 의미이다.
SRP를 적용한 구조에서는 각 모듈이나 클래스가 하나의 책임만 가지게 되어, 변경의 이유가 분리된다. 이는 코드의 응집도를 높이고, 유지보수성과 재사용성을 증가시키며, 불필요한 의존성을 제거하는 효과를 가져온다.
SRP를 따르는 Spring Boot REST API에서는 각 기능을 담당하는 클래스를 분리하여, 각각의 클래스가 하나의 책임만 가지도록 합니다.
“확장에 열려 있어야 하고, 변경에는 닫혀 있어야 한다.”
만약 새로운 요구사항을 구현하는 데 기존에 있던 코드를 전반적으로 수정해야 한다면, 좋은 설계라고 할 수 없을 것이다.
이는 기존의 코드를 변경하지 않으면서도 시스템의 기능을 확장할 수 있어야 함을 의미한다. 이 원칙을 적용하면, 새로운 기능을 추가하거나 변경사항이 발생했을 때 기존의 코드에 영향을 주지 않고도 요구사항을 충족시킬 수 있다.
예를 들어, 결제 시스템에서 다양한 결제 방식을 지원해야 한다고 가정해보자.
이때 각 결제 방식을 처리하는 클래스를 별도로 구현하고, 이들을 결제 처리 인터페이스를 통해 추상화한다면, 새로운 결제 방식이 추가되어도 기존 코드를 변경하지 않고도 쉽게 확장할 수 있다.
서브타입은 언제나 그것의 베이스 타입으로 교체될 수 있어야 한다는 원칙이다.
즉, 서브클래스는 슈퍼클래스의 행위를 정확하게 모방해야 하며, 슈퍼클래스의 인스턴스 대신 서브클래스의 인스턴스를 사용해도 시스템의 정확성이 유지되어야 한다. 이 원칙을 준수하면, 다형성을 활용한 설계가 가능해진다.
자바에선 대표적으로 Collection 인터페이스를 LSP의 예로 들수있다.
Collection 타입의 객체에서 자료형을 LinkedList에서 전혀 다른 자료형 HashSet으로 바꿔도 add() 메서드를 실행하는데 있어 원래 의도대로 작동되기 때문이다.한마디로 다형성 이용을 위해 부모 타입으로 메서드를 실행해도 의도대로 실행되도록 구성을 해줘야 하는 원칙이라 이해하면 된다.
public void myData() {
// Collection 인터페이스 타입으로 변수 선언
Collection data = new LinkedList();
data = new HashSet(); // 중간에 전혀 다른 자료형 클래스를 할당해도 호환됨
modify(data); // 메소드 실행
}
public void modify(Collection data){
list.add(1); // 인터페이스 구현 구조가 잘 잡혀있기 때문에 add 메소드 동작이 각기 자료형에 맞게 보장됨
// ...
}
ISP 원칙은 인터페이스를 각각 사용에 맞게 끔 잘게 분리해야한다는 설계 원칙이다.
SRP 원칙이 클래스의 단일 책임을 강조한다면, ISP는 인터페이스의 단일 책임을 강조하는 것으로 보면 된다.
즉, SRP 원칙의 목표는 클래스 분리를 통하여 이루어진다면, ISP 원칙은 인터페이스 분리를 통해 설계하는 원칙.
ISP 원칙은 인터페이스를 사용하는 클라이언트를 기준으로 분리함으로써, 클라이언트의 목적과 용도에 적합한 인터페이스 만을 제공하는 것이 목표이다.
다만 ISP 원칙의 주의해야 할점은 한번 인터페이스를 분리하여 구성해놓고 나중에 무언가 수정사항이 생겨서 또 인터페이스들을 분리하는 행위를 가하지 말아야 한다.
DIP 원칙은 어떤 Class를 참조해서 사용해야하는 상황이 생긴다면, 그 Class를 직접 참조하는 것이 아니라 그 대상의 상위 요소(추상 클래스 or 인터페이스)로 참조하라는 원칙
쉽게 이야기해서 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻
의존 관계를 맺을 때 변화하기 쉬운 것 또는 자주 변화하는 것보다는, 변화하기 어려운 것 거의 변화가 없는 것에 의존하라는 것
클린 아키텍처는 양파 껍질처럼, 가장 안쪽에 변하지 않는 고수준 정책을 배치하고, 바깥쪽으로 갈수록 변동성이 큰 저수준 모듈들을 배치합니다. 의존성은 바깥쪽에서 안쪽으로 향하고, 이렇게 하면 바깥쪽의 변화가 안쪽에 영향을 주지 않도록 할 수 있다. 따라서 변동성이 큰 저수준 모듈들은 바깥쪽에 자리 잡고, 이들의 변화가 안쪽으로 전파되지 않는 것이 클린 아키텍처의 핵심이다.
출처: https://inpa.tistory.com/entry/OOP-💠-객체-지향-설계의-5가지-원칙-SOLID [Inpa Dev 👨💻:티스토리]
https://tech.kakaobank.com/posts/2411-solid-truth-or-myths-for-developers/
https://f-lab.kr/insight/solid-principles-in-oop-20241214