SOLID 원칙

객체지향 프로그래밍(OOP)을 하면서 "유지보수하기 쉽고, 유연하게 확장 가능한 소프트웨어"를 만들기 위해 지켜야 할 5가지 핵심 원칙이다.
SOLID원칙을 묻는 이유
이론을 잘 파악하고 있는지를 묻는 것이 아닌, "협업 가능한 코드를 짤 줄 아는가?"를 확인하고 싶어서 면접관 분들이 질문하지 않을까?라고 생각합니다.
- 유지보수성: 네가 짠 코드가 다른 사람이 수정할 때 욕하지 않는가?(스파게티 코드 방지)
- 확장성: 기능 추가할 때마다 기존 코드를 다 뜯어 고쳐야 하는가?(OCP: 개방-폐쇄 원칙)
- 결합도/응집도: 클래스끼리 너무 엉켜있지 않는가?(결합도는 낮게, 응집도는 높게)
SRP( Single Responsibility Principle ) - 단일 책임 원칙
클래스가 여러 가지 일을 동시에 처리하면, 그중 하나만 수정해도 다른 기능에 영향을 줄 수 있다.
즉, "클래스를 변경해야 하는 이유는 오직 하나여야 한다"는 것이 핵심이다.
상황
User 클래스가 로그인도 하고, 이메일도 보내고, DB에 저장도 한다면. 이메일 형식이 바뀌어도 User를 수정해야 하고, DB가 바뀌어도 User를 수정해야 한다.(책임과다)
class User {
void login() { ... }
void sendEmail() { ... } // 책임이 섞임
void saveToDB() { ... } // 책임이 섞임
}
적용
기능을 하나씩 나누어 관리하는 방식으로 변경해야 한다.
class User { ... } // 데이터만 보유
class UserAuth { void login() { ... } } // 인증 담당
class EmailService { void sendEmail() { ... } } // 메일 담당
class UserRepository { void save() { ... } } // DB 담당
SRP를 잘 지키면 클래스 간의 응집도(Cohesion)는 높아지고 결합도(Coupling)는 낮아진다.
OCP( Open/Closed Principle ) - 개방-폐쇄 원칙
"확장은 열려있고, 변경에는 닫혀 있어야 한다."(다형성의 핵심이 되는 원칙이다.)
기존의 코드를 뜯어고치지 않고도 새로운 기능을 추가할 수 있어야 한다.
다형성(Polymorphism)과 인터페이스가 이 원칙을 지키는 가장 중요한 도구입니다.
상황
결재 수단을 추가하는데 기존 코드를 수정해야 한다면, OCP 위반이다.
class PaymentProcessor {
void pay(String type) {
if (type.equals("CARD")) { ... }
else if (type.equals("SAMSUNG")) { ... }
}
}
처음에는 카드만 결제가 되었는데, 추가로 삼성 페이가 가능해지면서, else if 라는 문장을 추가했다.
이처럼 인터페이스 없이 하나의 클래스로 작성을 하고, 기존의 코드를 수정해 기능을 추가한다면, OCP 원칙의 위반이다.
적용
인터페이스를 사용해 다형성을 활용하면, 기존에 있는 코드를 수정하지 않고도 기능을 수정하거나 추가할 수 있게 된다.
interface Payment { void pay(); }
class CardPay implements Payment { ... }
class SamsungPay implements Payment { ... }
class PaymentProcessor {
void process(Payment payment) {
payment.pay(); // 다형성 활용
}
}
자바의 다형성(Polymorphism)을 활용하여 유연한 설계를 가능하게 하는 원칙입니다.
LSP ( Liskov Substitution Principle ) - 리스코프 치환 원칙
"부모 객체의 자리에 자식 객체를 넣어도 시스템이 문제없이 돌아가야 한다."(상속을 올바르게 썼는가?)
부모 클래스가 할 수 있는 행동은 자식 클래스도 무조건 할 수 있어야 한다는 뜻이다.
단순히 컴파일이 되는 것을 넘어, 부모 클래스의 의도와 규약을 지켜야 한다는 것이다.
상황
타조는 새를 상속받았는데, fly라는 메서드를 호출했더니 "타조는 못날아요"라는 에러를 낸다면. 이는 부모의 약속을 깬 것이다.
class Bird { void fly() { ... } }
class Ostrich extends Bird {
@Override
void fly() { throw new Exception("못 날아!"); } // LSP 위반
}
적용
억지로 상속하지 말고 별도의 클래스로 분리하거나, 공통 상위 인터페이스를 둔다.
class Bird { ... }
interface Flyable { void fly(); }
class Sparrow extends Bird implements Flyable { ... } // 참새는 낢
class Ostrich extends Bird { ... } // 타조는 안 낢 (fly 구현 강제 X)
상속 관계가 깨지지 않도록 'is-a' 관계가 확실할 때만 상속을 사용해야 한다는 것을 의미한다.
ISP ( Interface Segregation Principle ) - 인터페이스 분리 원칙
"범용 인터페이스 하나보다는 구체적인 여러 개의 인터페이스가 낫다." (내가 쓰지 않는 기능에 의존하면 안 된다.)
상황
martPhone 인터페이스에 call(), sms(), wirelessCharge()가 다 있는데, 옛날 2G 폰을 구현하려니 wirelessCharge()(무선충전)를 빈 껍데기로라도 구현해야 한다.
interface SmartPhone {
void call();
void sms();
void wirelessCharge();
}
적용
하나의 인터페이스로 기능을 모두 담지 않고 기능별로 인터페이스 분리해야 한다.
interface Phone { void call(); void sms(); }
interface WirelessChargable { void wirelessCharge(); }
class GalaxyS24 implements Phone, WirelessChargable { ... } // 둘 다 구현
class OldPhone implements Phone { ... } // 전화 기능만 구현
Phone 인터페이스에 공통적인 기능을 구현하고, 무선 충전 기능을 가진 인터페이스를 따로 구현한다.
DIP ( Dependency Inversion Principle ) - 의존관계 역전 원칙
"구체적인 클래스에 의존하지 말고, 추상화(인터페이스)에 의존하라." (자바의 Spring Framework가 이 원칙을 기반)
변하기 쉬운 것(구체 클래스)에 의존하지 말고, 변하지 않는 것(인터페이스)에 의존하라는 뜻입니다.
객체끼리 직접 연결하지 말고, 중간에 인터페이스를 껴서 느슨하게 연결하라는 것이다.
상황
편의점 아르바이트생(Client)이 '삼성카드'(구체 클래스)만 받을 줄 알면, 손님이 '현대카드'를 내밀었을 때 결제를 못 한다.
class Store {
SamsungPay paySystem = new SamsungPay(); // 직접 생성 (강한 결합)
}
구체적인 것에 의존(SamsungPay가 바뀌면, 자신도 영향을 받음)
적용
추상적인 것에 의존해야 한다.(무엇이 들어올지 모름 = 유연함 증가)
class Store {
Payment paySystem;
// 생성자 주입 (Dependency Injection)
public Store(Payment paySystem) {
this.paySystem = paySystem;
}
}
스프링 프레임워크의 핵심인 DI(의존성 주입)가 바로 이 원칙을 따르는 것이다.
면접 답변식 요약
SOLID는 객체지향 설계를 더욱 견고하고 유연하게 만드는 5가지 원칙입니다.
단일 책임 원칙(SRP)으로 클래스의 역할을 명확히 하고, 개방-폐쇄 원칙(OCP)을 통해 기존 코드를 건드리지 않고 기능을 확장하며, 리스코프 치환 원칙(LSP)으로 상속의 신뢰성을 보장합니다. 또한, 인터페이스 분리 원칙(ISP)으로 불필요한 의존성을 줄이고, 의존 역전 원칙(DIP)을 통해 구체적인 구현체가 아닌 추상화에 의존하게 함으로써,
결과적으로 결합도는 낮추고 응집도는 높여 유지보수하기 좋은 시스템을 만드는 것이 핵심입니다.
'Daily Dev Q&A 정리 템플릿' 카테고리의 다른 글
| 25.12.09 자바의 접근 제어자(Access Modifier)에 대하여 (0) | 2025.12.09 |
|---|---|
| 25.12.08 자바의 Collections Framework에 대해 설명해보기 (0) | 2025.12.08 |
| 25.12.04 오버로딩과 오버라이딩에 대해서 구체적으로 설명하라는 질문에 대한 대답은? (0) | 2025.12.05 |
| 25.12.03 다형성을 좀 더 구체적으로 설명해주세요! 라는 질문에 대비하기 (0) | 2025.12.03 |
| 25.12.02 상속과 컴포지션(Composition)의 차이와, 언제 컴포지션을 사용할까? (0) | 2025.12.02 |