📚
SOLID 원칙이란?
객체지향 설계 5대 원칙으로, 유지보수성과 확장성을 높이고 코드의 복잡도를 줄이기 위한 핵심 설계 지침입니다.
SOLID 원칙은 특정 언어나 프레임워크에 종속되지 않는, "변화에 유연하고 유지보수하기 쉬운 코드"를 만들기 위한 기본 철학입니다.
1. SRP: 단일 책임 원칙 (Single Responsibility Principle)

"한 클래스는 하나의 책임(기능)만 가져야 한다."
하나의 클래스에 여러 책임이 섞여 있으면, 한 기능의 변경이 다른 기능에 연쇄적인 영향을 줄 수 있습니다. 책임을 명확히 분리하면 변경의 파급효과를 줄이고 유지보수성이 높아집니다.
💡 Tip:
청소기는 청소만 잘하면 됩니다. 화분에 물 주고 드라이 기능까지 넣으면 하나만 고장 나도 전체를 못 쓰게 되죠. 하나의 클래스는 한 가지 책임만 지게 하세요!
청소기는 청소만 잘하면 됩니다. 화분에 물 주고 드라이 기능까지 넣으면 하나만 고장 나도 전체를 못 쓰게 되죠. 하나의 클래스는 한 가지 책임만 지게 하세요!
✅ 간단한 예시
// 보고서 생성 책임
public class ReportGenerator {
public String generate() {
return "Report 내용";
}
}
// 보고서 출력 책임 (분리됨)
public class ReportPrinter {
public void print(String report) {
System.out.println(report);
}
}
2. OCP: 개방-폐쇄 원칙 (Open/Closed Principle)

"클래스는 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다."
기능이 변경되거나 추가될 때, 기존 코드를 뜯어고치지 않고 새로운 기능을 추가할 수 있도록 설계해야 합니다. 주로 추상화(인터페이스/상속)를 통해 이를 달성합니다.
💡 Tip:
역할(인터페이스)과 구현(클래스)을 분리하세요. 기존 코드는 건드리지 말고, 새로운 클래스를 추가(상속/구현)하는 방식으로 기능을 확장하세요.
역할(인터페이스)과 구현(클래스)을 분리하세요. 기존 코드는 건드리지 말고, 새로운 클래스를 추가(상속/구현)하는 방식으로 기능을 확장하세요.
✅ 간단한 예시
interface Payment {
void pay();
}
// 새로운 결제 수단이 추가되어도 Payment 인터페이스만 구현하면 됨
class CardPayment implements Payment {
public void pay() {
System.out.println("카드 결제 진행");
}
}
class OrderService {
private Payment payment;
// 생성자 주입을 통해 유연하게 변경 가능
public OrderService(Payment payment) {
this.payment = payment;
}
public void checkout() {
payment.pay(); // 기존 코드는 수정할 필요 없음
}
}
3. LSP: 리스코프 치환 원칙 (Liskov Substitution Principle)

"자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다."
다형성을 활용할 때, 부모 클래스 타입으로 선언된 변수에 자식 객체를 대입해도 프로그램이 원래 의도대로 동작해야 한다는 원칙입니다. 즉, 자식 클래스는 부모의 규약을 철저히 지켜야 합니다.
💡 Tip:
자바의 Collection 프레임워크가 대표적인 예입니다.
자바의 Collection 프레임워크가 대표적인 예입니다.
LinkedList에서 HashSet으로 구현체를 바꿔도 .add() 동작은 논리적으로 문제없이 작동해야 합니다.✅ 간단한 예시
// 부모 타입 Collection
Collection<String> myData = new LinkedList<>();
myData.add("apple");
// 자식 클래스를 HashSet으로 교체해도 add() 동작은 보장됨 -> LSP 만족
myData = new HashSet<>();
myData.add("banana");
4. ISP: 인터페이스 분리 원칙 (Interface Segregation Principle)

"인터페이스는 작게 나눠서, 구현체가 필요한 기능만 구현하게 하자."
범용적인 큰 인터페이스 하나보다는, 구체적인 여러 개의 인터페이스가 낫습니다. 구현하는 클래스가 자신이 사용하지 않는 기능까지 억지로 구현하지 않도록 해야 합니다.
💡 Tip:
인터페이스는 다중 상속(구현)이 가능합니다. 기능별로 잘게 쪼개서 필요한 것만 골라 구현하도록 설계하세요.
인터페이스는 다중 상속(구현)이 가능합니다. 기능별로 잘게 쪼개서 필요한 것만 골라 구현하도록 설계하세요.
✅ 간단한 예시
// 기능별로 인터페이스 분리
interface Printer {
void print();
}
interface Fax {
void fax();
}
// 팩스 기능이 없는 프린터는 Printer만 구현하면 됨 (불필요한 fax() 구현 강제 X)
class BasicPrinter implements Printer {
public void print() {
System.out.println("프린트 출력 중");
}
}
5. DIP: 의존 역전 원칙 (Dependency Inversion Principle)

"구현 클래스에 의존하지 말고, 추상화(인터페이스)에 의존해야 한다."
변하기 쉬운 것(구현체)보다는 변하지 않는 것(인터페이스, 추상 클래스)에 의존해야 유지보수가 쉬워집니다. 이를 위해 보통 의존성 주입(DI)을 활용합니다.
💡 Tip:
"저장한다"는 역할(인터페이스)에 의존해야지, "MySQL에 저장한다"는 구체적인 사실에 의존하면 나중에 오라클로 DB를 바꿀 때 코드를 다 뜯어고쳐야 합니다.
"저장한다"는 역할(인터페이스)에 의존해야지, "MySQL에 저장한다"는 구체적인 사실에 의존하면 나중에 오라클로 DB를 바꿀 때 코드를 다 뜯어고쳐야 합니다.
✅ 간단한 예시
// 추상화에 의존
interface Database {
void save(String data);
}
class MySQLDatabase implements Database {
public void save(String data) {
System.out.println("MySQL에 저장: " + data);
}
}
class DataService {
private Database db; // 구체적인 MySQLDatabase가 아닌 인터페이스에 의존
public DataService(Database db) {
this.db = db;
}
public void store(String input) {
db.save(input);
}
}
마무리 요약
| 원칙 | 설명 |
|---|---|
| SRP | 하나의 클래스는 하나의 책임만 가져야 한다. |
| OCP | 확장에는 열려 있고, 수정에는 닫혀 있어야 한다. |
| LSP | 자식 클래스는 부모 클래스를 대체해도 문제없어야 한다. |
| ISP | 필요한 기능만 가진 인터페이스로 분리해야 한다. |
| DIP | 구현체가 아닌, 추상화(인터페이스)에 의존해야 한다. |
'Knowledge > 디자인 패턴' 카테고리의 다른 글
| [OOP] GoF(Gang of Four) 디자인 패턴 장점 과 종류 !! (2) | 2026.01.26 |
|---|
