SOLID 원칙
기술노트
SOLID 원칙 (SOLID Principles)
SOLID는 객체 지향 프로그래밍 및 설계의 다섯 가지 기본 원칙을 나타내는 약어입니다. 이 원칙들을 따르는 소프트웨어는 시간이 지나도 더 이해하기 쉽고, 유연하며, 유지보수하기 쉬운 특징을 가집니다.
로버트 C. 마틴(Robert C. Martin)에 의해 대중화된 이 원칙들은 코드의 결합도(coupling)를 낮추고 응집도(cohesion)를 높여, 변화에 유연하게 대처할 수 있는 안정적인 시스템을 만드는 것을 목표로 합니다.
SOLID의 5가지 원칙
- 1. S
- 단일 책임 원칙 (Single Responsibility Principle, SRP)
- "클래스를 변경해야 하는 이유는 단 하나여야 한다."
- **핵심 개념:** 하나의 클래스는 하나의 기능(책임)만 가져야 합니다. 여러 책임을 갖게 되면, 한 책임의 변경이 다른 책임과 관련된 코드에 예기치 않은 영향을 줄 수 있습니다.
- **예시:** `보고서` 클래스가 보고서 내용 생성과 출력(이메일 전송, 프린트) 책임을 모두 갖는 것은 SRP 위반입니다. 보고서 내용 생성 책임만 갖도록 하고, 출력 책임은 별도의 `이메일전송기`, `프린터` 클래스로 분리해야 합니다.
- 2. O
- 개방-폐쇄 원칙 (Open/Closed Principle, OCP)
- "소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있어야 하지만, 수정에 대해서는 닫혀 있어야 한다."
- **핵심 개념:** 새로운 기능을 추가할 때 기존 코드를 수정하는 것이 아니라, 새로운 코드를 추가하여(확장) 기능을 구현해야 합니다. 이는 주로 추상화(인터페이스, 추상 클래스)를 통해 달성됩니다.
- **예시:** `도형`의 넓이를 계산하는 로직을 만들 때, 새로운 도형(예: 삼각형)이 추가될 때마다 기존 계산 로직 코드를 수정하는 것은 OCP 위반입니다. `도형`이라는 추상적인 인터페이스를 만들고 각 도형(`사각형`, `원`, `삼각형`)이 이를 구현하도록 하면, 새로운 도형이 추가되어도 기존 코드는 수정할 필요가 없습니다.
- 3. L
- 리스코프 치환 원칙 (Liskov Substitution Principle, LSP)
- "서브타입은 언제나 자신의 기반 타입(부모 클래스)으로 교체될 수 있어야 한다."
- **핵심 개념:** 자식 클래스는 부모 클래스의 역할을 온전히 수행할 수 있어야 합니다. 즉, 부모 클래스의 객체를 사용하는 곳에 자식 클래스의 객체를 대신 넣어도 프로그램이 문제없이 동작해야 합니다. 이는 상속 관계가 올바르게 설계되었는지를 판단하는 중요한 기준입니다.
- **예시:** `직사각형` 클래스를 상속받는 `정사각형` 클래스가 있다고 가정해 봅시다. `직사각형`은 가로와 세로를 독립적으로 변경할 수 있지만, `정사각형`은 가로가 바뀌면 세로도 함께 바뀌어야 합니다. 이 경우, `정사각형` 객체는 `직사각형` 객체를 완벽히 대체할 수 없으므로 LSP 위반입니다. (상속 대신 다른 관계를 고려해야 함)
- 4. I
- 인터페이스 분리 원칙 (Interface Segregation Principle, ISP)
- "클라이언트는 자신이 사용하지 않는 메서드에 의존해서는 안 된다."
- **핵심 개념:** 하나의 거대한 인터페이스보다는, 여러 개의 구체적인 인터페이스로 분리하는 것이 좋습니다. 이를 통해 클래스가 자신이 사용하지도 않는 불필요한 메서드를 구현하도록 강제하는 상황을 막을 수 있습니다.
- **예시:** `일하는 기계` 인터페이스에 `일하기()`, `밥먹기()`, `잠자기()` 메서드가 모두 있다면, `로봇` 클래스는 자신이 필요 없는 `밥먹기()`, `잠자기()`를 억지로 구현해야 합니다. 이는 ISP 위반입니다. `일할수있는`, `먹을수있는`, `잠잘수있는` 인터페이스로 분리하여 필요한 것만 구현하도록 해야 합니다.
- 5. D
- 의존관계 역전 원칙 (Dependency Inversion Principle, DIP)
- "고수준 모듈은 저수준 모듈에 의존해서는 안 된다. 둘 다 추상화에 의존해야 한다."
- **핵심 개념:** 구체적인 구현 클래스에 직접 의존하는 것이 아니라, 추상적인 인터페이스나 추상 클래스에 의존해야 합니다. 이를 통해 모듈 간의 결합도를 낮추고, 유연성과 재사용성을 높일 수 있습니다. 의존성 주입(Dependency Injection)은 이 원칙을 구현하는 대표적인 방법입니다.
- **예시:** `자동차` 클래스가 구체적인 `한국타이어` 클래스에 직접 의존하는 것은 DIP 위반입니다. `타이어`라는 인터페이스에 의존하게 하고, 외부에서 `한국타이어`나 `미국타이어` 객체를 주입받도록 설계해야 합니다. 이렇게 하면 타이어를 교체해도 자동차 코드는 수정할 필요가 없습니다.