SOLID 원칙은 객체 지향 프로그래밍(OOP)에서 유지보수성과 확장성을 높이기 위한 5가지 설계 원칙을 의미합니다. SOLID는 각 원칙의 앞 글자를 따서 만든 약어입니다.
1️⃣ 단일 책임 원칙 (SRP: Single Responsibility Principle)
클래스는 단 하나의 책임만 가져야 합니다.
❌ 잘못된 예제
#include <iostream>
#include <fstream>
using namespace std;
class Report {
public:
void generateReport() { cout << "리포트 생성\n"; }
void printReport() { cout << "리포트 출력\n"; }
void saveToFile() { ofstream file("report.txt"); file << "리포트 내용"; file.close(); }
};
- Report 클래스가 생성, 출력, 저장을 모두 담당 → 단일 책임 원칙 위반.
✅ 개선된 예제
#include <iostream>
#include <fstream>
using namespace std;
class Report {
public:
string generate() { return "리포트 내용"; }
};
class ReportPrinter {
public:
void print(const string& content) { cout << "출력: " << content << endl; }
};
class ReportSaver {
public:
void saveToFile(const string& content) {
ofstream file("report.txt");
file << content;
file.close();
cout << "파일 저장 완료" << endl;
}
};
int main() {
Report report;
ReportPrinter printer;
ReportSaver saver;
string content = report.generate();
printer.print(content);
saver.saveToFile(content);
return 0;
}
- 각각의 클래스가 하나의 역할만 수행하도록 분리.
2️⃣ 개방-폐쇄 원칙 (OCP: Open/Closed Principle)
코드는 확장에는 열려 있고, 수정에는 닫혀 있어야 합니다.
❌ 잘못된 예제
class PaymentProcessor {
public:
void processPayment(string type) {
if (type == "CreditCard") cout << "신용카드 결제\n";
else if (type == "PayPal") cout << "페이팔 결제\n";
}
};
- 새로운 결제 방식 추가 시 processPayment()를 수정해야 함 → OCP 위반.
✅ 개선된 예제
#include <iostream>
using namespace std;
class Payment {
public:
virtual void pay() = 0; // 추상 메서드
virtual ~Payment() = default;
};
class CreditCardPayment : public Payment {
public:
void pay() override { cout << "신용카드 결제\n"; }
};
class PayPalPayment : public Payment {
public:
void pay() override { cout << "페이팔 결제\n"; }
};
class PaymentProcessor {
public:
void processPayment(Payment& payment) {
payment.pay();
}
};
int main() {
CreditCardPayment cc;
PayPalPayment pp;
PaymentProcessor processor;
processor.processPayment(cc);
processor.processPayment(pp);
return 0;
}
- 새로운 결제 방식 추가 시 기존 코드 수정 없이 Payment 인터페이스만 구현하면 됨.
3️⃣ 리스코프 치환 원칙 (LSP: Liskov Substitution Principle)
자식 클래스는 부모 클래스를 완전히 대체할 수 있어야 합니다.
❌ 잘못된 예제
class Rectangle {
protected:
int width, height;
public:
void setWidth(int w) { width = w; }
void setHeight(int h) { height = h; }
int getArea() { return width * height; }
};
class Square : public Rectangle {
public:
void setWidth(int w) { width = height = w; } // 문제 발생
void setHeight(int h) { width = height = h; } // 문제 발생
};
- Rectangle을 기대하고 사용하는 코드에서 Square가 예상과 다르게 동작 → LSP 위반.
✅ 개선된 예제
class Shape {
public:
virtual int getArea() = 0;
virtual ~Shape() = default;
};
class Rectangle : public Shape {
protected:
int width, height;
public:
Rectangle(int w, int h) : width(w), height(h) {}
int getArea() override { return width * height; }
};
class Square : public Shape {
private:
int side;
public:
Square(int s) : side(s) {}
int getArea() override { return side * side; }
};
- Rectangle과 Square를 Shape 인터페이스를 통해 분리하여 LSP 준수.
4️⃣ 인터페이스 분리 원칙 (ISP: Interface Segregation Principle)
클라이언트가 사용하지 않는 인터페이스에 의존하지 않아야 합니다.
❌ 잘못된 예제
class Worker {
public:
virtual void work() = 0;
virtual void eat() = 0; // 문제 발생
};
class Robot : public Worker {
public:
void work() override { cout << "로봇 작업 중\n"; }
void eat() override { /* 로봇은 먹지 않음 */ } // 불필요한 메서드 구현
};
- Robot이 eat() 메서드를 구현해야 하는 문제 발생.
✅ 개선된 예제
class Workable {
public:
virtual void work() = 0;
virtual ~Workable() = default;
};
class Eatable {
public:
virtual void eat() = 0;
virtual ~Eatable() = default;
};
class Human : public Workable, public Eatable {
public:
void work() override { cout << "사람이 일함\n"; }
void eat() override { cout << "사람이 식사함\n"; }
};
class Robot : public Workable {
public:
void work() override { cout << "로봇이 일함\n"; }
};
- Workable과 Eatable을 분리하여 필요한 인터페이스만 상속하도록 개선.
5️⃣ 의존 역전 원칙 (DIP: Dependency Inversion Principle)
구체적인 구현이 아니라 추상화(인터페이스)에 의존해야 합니다.
❌ 잘못된 예제
class Keyboard {};
class Monitor {};
class Computer {
private:
Keyboard keyboard;
Monitor monitor;
};
- Computer가 Keyboard와 Monitor의 구체적인 구현에 직접 의존 → DIP 위반.
✅ 개선된 예제
class Keyboard {
public:
virtual void input() = 0;
virtual ~Keyboard() = default;
};
class Monitor {
public:
virtual void display() = 0;
virtual ~Monitor() = default;
};
class MechanicalKeyboard : public Keyboard {
public:
void input() override { cout << "기계식 키보드 입력\n"; }
};
class LEDMonitor : public Monitor {
public:
void display() override { cout << "LED 모니터 표시\n"; }
};
class Computer {
private:
Keyboard& keyboard;
Monitor& monitor;
public:
Computer(Keyboard& k, Monitor& m) : keyboard(k), monitor(m) {}
void use() {
keyboard.input();
monitor.display();
}
};
int main() {
MechanicalKeyboard keyboard;
LEDMonitor monitor;
Computer pc(keyboard, monitor);
pc.use();
return 0;
}
- Computer가 구체적인 클래스가 아닌 **인터페이스(추상 클래스)**에 의존하도록 변경.
🔥 정리
원칙 | 개념 | 개선 방법 |
SRP | 단일 책임 | 역할을 분리하여 여러 클래스로 나눈다. |
OCP | 개방-폐쇄 | 인터페이스(추상 클래스)를 사용하여 확장 가능하도록 한다. |
LSP | 리스코프 치환 | 부모 클래스를 완벽하게 대체할 수 있도록 설계한다. |
ISP | 인터페이스 분리 | 불필요한 메서드 구현을 피하기 위해 인터페이스를 분리한다. |
DIP | 의존 역전 | 구체적인 구현이 아닌 **추상화(인터페이스)**에 의존하도록 한다. |
이 원칙을 잘 지키면 유지보수성과 확장성이 뛰어난 C++ 코드를 작성할 수 있습니다! 🚀
반응형
'프로그래밍' 카테고리의 다른 글
시스템콜(System Call) 이란? (0) | 2025.04.04 |
---|