객체지향 설계 원칙인 SOLID는 객체지향 프로그래밍(OOP)을 올바르게 사용하는 데 도움을 주는 다섯 가지 원칙을 뜻한다. 이 원칙들을 지키면 코드가 더 유지보수하기 쉽고, 확장 가능하며, 유연해진다. 각 원칙을 간단히 설명하고 예를 들어보겠다.
클래스는 하나의 책임만 가져야 한다.
class Report {
void generateReport() { /* 보고서 생성 */ }
void sendReportByEmail() { /* 이메일로 보고서 발송 */ }
} → 이 클래스는 보고서를 생성하고 이메일을 보내는 두 가지 책임을 가짐.class ReportGenerator {
void generateReport() { /* 보고서 생성 */ }
}
class EmailSender {
void sendEmail() { /* 이메일 발송 */ }
}확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.
class PaymentProcessor {
void process(String paymentType) {
if (paymentType.equals("creditCard")) { /* 신용카드 처리 */ }
else if (paymentType.equals("paypal")) { /* PayPal 처리 */ }
}
}interface Payment {
void process();
}
class CreditCardPayment implements Payment {
public void process() { /* 신용카드 처리 */ }
}
class PayPalPayment implements Payment {
public void process() { /* PayPal 처리 */ }
}
class PaymentProcessor {
void process(Payment payment) {
payment.process();
}
} → 새 결제 방식이 추가될 때 기존 코드를 변경하지 않고 확장 가능.서브클래스는 부모클래스를 대체할 수 있어야 한다.
class Bird {
void fly() { /* 날기 */ }
}
class Penguin extends Bird {
void fly() { throw new UnsupportedOperationException("펭귄은 날 수 없음"); }
}interface Bird {
void move();
}
class FlyingBird implements Bird {
public void move() { /* 날기 */ }
}
class Penguin implements Bird {
public void move() { /* 헤엄치기 */ }
}클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다.
interface Worker {
void work();
void eat();
}
class Robot implements Worker {
public void work() { /* 작업 */ }
public void eat() { throw new UnsupportedOperationException("로봇은 먹지 않음"); }
}interface Workable {
void work();
}
interface Eatable {
void eat();
}
class Robot implements Workable {
public void work() { /* 작업 */ }
}
class Human implements Workable, Eatable {
public void work() { /* 작업 */ }
public void eat() { /* 식사 */ }
}상위 모듈이 하위 모듈에 의존하지 말고, 추상화에 의존해야 한다.
class Keyboard {
void type() { /* 입력 */ }
}
class Computer {
private Keyboard keyboard;
public Computer() {
this.keyboard = new Keyboard(); // 특정 구현에 의존
}
}interface InputDevice {
void input();
}
class Keyboard implements InputDevice {
public void input() { /* 입력 */ }
}
class Mouse implements InputDevice {
public void input() { /* 입력 */ }
}
class Computer {
private InputDevice inputDevice;
public Computer(InputDevice inputDevice) {
this.inputDevice = inputDevice; // 추상화에 의존
}
}| 원칙 | 설명 |
|---|---|
| SRP (단일 책임 원칙) | 하나의 클래스는 하나의 책임만 가져야 한다. |
| OCP (개방-폐쇄 원칙) | 기존 코드를 변경하지 않고 확장 가능해야 한다. |
| LSP (리스코프 치환 원칙) | 부모 클래스 대신 자식 클래스를 사용해도 정상 동작해야 한다. |
| ISP (인터페이스 분리 원칙) | 인터페이스는 작고 구체적으로 설계해야 한다. |
| DIP (의존 역전 원칙) | 구체적인 구현보다 추상화에 의존해야 한다. |
이 원칙들을 완벽히 지키는 건 어렵지만, 지향하면서 코드를 작성하면 유지보수성, 재사용성, 확장성이 크게 향상된다! 🚀