SOLID 원칙은 객체지향 프로그래밍에서 코드의 유지보수성과 확장성을 높이기 위한 다섯 가지 설계 원칙을 의미합니다.
이유: 하나의 클래스가 여러 책임을 가지면, 하나의 책임이 변경될 때 다른 기능도 영향을 받을 수 있음.
class Report {
void generateReport(); // 리포트 생성
void saveToFile(); // 리포트 저장 (잘못된 책임 분리)
};
위 코드는 Report 클래스가 "리포트 생성"과 "파일 저장"이라는 두 가지 책임을 가짐.
→ 해결 방법: 파일 저장 기능을 분리
class ReportSaver {
void saveToFile(Report report);
};
2. 개방-폐쇄 원칙 (Open-Closed Principle, OCP)
설명: 기존 코드를 수정하지 않고 기능을 확장할 수 있도록 설계해야 한다.
이유: 코드 수정 없이 확장할 수 있어야 유지보수가 용이함.
예시: 몬스터의 공격 방식 추가 시 기존 클래스를 수정하면 OCP 위반.
class Monster {
void attack() {
if (type == "fire") { / 불 공격 / }
else if (type == "ice") { / 얼음 공격 / } // 새로운 공격 추가 시 기존 코드 수정 필요
}
};
→ 해결 방법: 다형성을 활용한 확장 설계
class AttackStrategy {
virtual void attack() = 0;
};
class FireAttack : public AttackStrategy {
void attack() override { / 불 공격 구현 / }
};
class IceAttack : public AttackStrategy {
void attack() override { / 얼음 공격 구현 / }
};
class Monster {
AttackStrategy attackStrategy;
public:
void setAttackStrategy(AttackStrategy strategy) { attackStrategy = strategy; }
void attack() { attackStrategy->attack(); }
};
3. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP)
설명: 자식 클래스는 부모 클래스를 대체할 수 있어야 한다.
이유: 상속 관계에서 부모 클래스의 기능을 그대로 유지하면서 확장해야 함.
위반 예시
class Bird {
virtual void fly() {};
};
class Penguin : public Bird {
void fly() override { throw std::logic_error("펭귄은 날 수 없음"); } // 문제 발생
};
→ 해결 방법: Bird 클래스를 날 수 있는 새와 그렇지 않은 새로 분리
class Bird {};
class FlyingBird : public Bird {
virtual void fly() = 0;
};
class Sparrow : public FlyingBird {
void fly() override { / 참새가 날아감 / }
};
class Penguin : public Bird {
// fly() 함수 없음 → LSP 준수
};
4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP)
설명: 클라이언트가 자신이 사용하지 않는 인터페이스에 의존하지 않아야 한다.
이유: 하나의 인터페이스가 너무 많은 기능을 포함하면, 불필요한 기능이 강제될 수 있음.
위반 예시
class Monster {
virtual void attack() = 0;
virtual void fly() = 0; // 모든 몬스터가 날아야 하나?
};
class Goblin : public Monster {
void attack() override { / 공격 / }
void fly() override { / 고블린은 날 수 없음 → 강제 구현 / }
};
→ 해결 방법: 인터페이스를 분리
class IAttack {
virtual void attack() = 0;
};
class IFly {
virtual void fly() = 0;
};
class Goblin : public IAttack {
void attack() override { / 공격 / }
};
class Dragon : public IAttack, public IFly {
void attack() override { / 공격 / }
void fly() override { / 비행 / }
};
5. 의존 역전 원칙 (Dependency Inversion Principle, DIP)
설명: 고수준 모듈(상위 클래스)은 저수준 모듈(하위 클래스)에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다.
이유: 구현이 아닌 인터페이스(추상 클래스)에 의존하면 결합도를 낮출 수 있음.
위반 예시
class Keyboard {
void input() { / 키보드 입력 / }
};
class Computer {
Keyboard keyboard; // 직접 의존 → DIP 위반
};
→ 해결 방법: 인터페이스를 통해 의존성 역전
class InputDevice {
virtual void input() = 0;
};
class Keyboard : public InputDevice {
void input() override { / 키보드 입력 / }
};
class Mouse : public InputDevice {
void input() override { / 마우스 입력 / }
};
class Computer {
InputDevice inputDevice; // 추상화에 의존
public:
void setInputDevice(InputDevice device) { inputDevice = device; }
};
✅ 정리
원칙 핵심 개념 위반 시 문제점 해결 방법
SRP (단일 책임) 하나의 클래스는 하나의 책임만 가져야 함 여러 책임이 엮여 유지보수 어려움 클래스를 기능별로 분리
OCP (개방-폐쇄) 확장은 가능하지만 기존 코드는 변경하지 않음 기존 코드 수정 시 사이드 이펙트 발생 상속, 인터페이스, 전략 패턴 사용
LSP (리스코프 치환) 자식 클래스가 부모 클래스를 대체 가능해야 함 상속 구조에서 예외가 발생 올바른 상속 관계 설계
ISP (인터페이스 분리) 클라이언트가 불필요한 인터페이스에 의존하지 않음 불필요한 기능을 강제 구현해야 함 작은 단위의 인터페이스로 분리
DIP (의존 역전) 고수준 모듈이 저수준 모듈에 직접 의존하지 않음 하위 클래스 변경이 상위 클래스에 영향을 줌 인터페이스(추상 클래스)를 활용
SOLID 원칙을 잘 적용하면 유지보수성이 높고 확장성이 좋은 코드를 작성할 수 있어. 언리얼 엔진 개발에서도 OCP와 DIP는 특히 중요하니까 면접 준비할 때 예제 코드까지 익혀두면 좋아! 😊