
SOLID는 객체지향 설계(Object-Oriented Design)에서 코드의 품질을 높이고 유지보수성을 강화하기 위한 5가지 원칙을 말한다. 이는 Robert C. Martin(일명 "Uncle Bob")이 주창한 개념으로, 각 원칙의 첫 글자를 따서 SRP, OCP, LSP, ISP, DIP로 구성된다.
SRP (Single Responsibility Principle) - 단일 책임 원칙
한 클래스는 단 하나의 책임만 가져야 하며, 그 책임을 변경해야 하는 이유도 단 하나여야 한다는 원칙.
즉, 클래스가 너무 많은 역할을 맡으면 유지보수가 어려워짐.
// SRP 위반 사례
class User {
void saveUserToDatabase() { /* 사용자 DB 저장 */ }
void sendEmail() { /* 이메일 전송 */ }
}
// SRP 준수 사례
class User {
void saveUserToDatabase() { /* 사용자 DB 저장 */ }
}
class EmailService {
void sendEmail() { /* 이메일 전송 */ }
}
위반 사례에서는 User 클래스가 데이터 저장과 이메일 전송이라는 두 가지 책임을 가진다. SRP를 적용하면 책임을 분리해 각 클래스에 단일 책임을 부여한다.
OCP (Open/Closed Principle) - 개방-폐쇄 원칙
소프트웨어 요소는 확장에는 열려 있어야 하지만, 수정에는 닫혀 있어야 한다는 원칙. 즉, 새로운 기능을 추가할 때 기존 코드를 수정하지 않고도 확장이 가능해야 한다.
// OCP 위반 사례
class Shape {
void draw(String type) {
if (type.equals("circle"))
else if (type.equals("square"))
}
}
// OCP 준수 사례
interface Shape {
void draw();
}
class Circle implements Shape {
public void draw() { /* 원 그리기 */ }
}
class Square implements Shape {
public void draw() { /* 사각형 그리기 */ }
}
위반 사례에서는 새로운 도형이 추가될 때마다 Shape 클래스를 수정해야 한다. OCP를 준수하면 인터페이스를 통해 확장 가능하고 수정은 최소화된다.
LSP (Liskov Substitution Principle) - 리스코프 치환 원칙
자식 클래스는 부모 클래스의 역할을 완전히 대체할 수 있어야 한다는 원칙. 즉, 상속받은 클래스가 부모 클래스의 동작을 깨뜨리지 않아야 한다.
// LSP 위반 사례
class Bird {
void fly()
}
class Penguin extends Bird {
void fly() { throw new UnsupportedOperationException("펭귄은 날 수 없음"); }
}
// LSP 준수 사례
interface Flyable {
void fly();
}
class Sparrow implements Flyable {
public void fly() { /* 날기 */ }
}
class Penguin {
// fly 메서드 없음
}
위반 사례에서 Penguin은 Bird를 상속받았지만 날 수 없어 예외를 던진다. LSP를 준수하면 날 수 있는 객체와 날 수 없는 객체를 분리한다.
ISP (Interface Segregation Principle) - 인터페이스 분리 원칙
클라이언트가 자신이 사용하지 않는 메서드에 의존하지 않도록 인터페이스를 작고 구체적으로 분리해야 한다는 원칙.
// ISP 위반 사례
interface Worker {
void work();
void eat();
}
class Robot implements Worker {
public void work()
public void eat() { /* 로봇은 밥을 안 먹음 */ }
}
// ISP 준수 사례
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() { /* 밥 먹기 */ }
}
위반 사례에서 Robot은 불필요한 eat 메서드를 구현해야 했다. ISP를 적용하면 필요한 인터페이스만 구현하도록 분리된다.
DIP (Dependency Inversion Principle) - 의존성 역전 원칙
고수준 모듈은 저수준 모듈에 의존하지 않아야 하며, 둘 다 추상화에 의존해야 한다는 원칙. 즉, 구체적인 구현이 아닌 인터페이스나 추상 클래스에 의존해야 한다.
// DIP 위반 사례
class Keyboard {
void type() { /* 타이핑 */ }
}
class Computer {
private Keyboard keyboard = new Keyboard(); // 구체 클래스에 직접 의존
void start() { keyboard.type(); }
}
// DIP 준수 사례
interface InputDevice {
void input();
}
class Keyboard implements InputDevice {
public void input() { /* 타이핑 */ }
}
class Computer {
private InputDevice device; // 추상화에 의존
public Computer(InputDevice device) {
this.device = device;
}
void start() { device.input(); }
}
위반 사례에서 Computer는 Keyboard라는 구체 클래스에 직접 의존한다. DIP를 적용하면 인터페이스에 의존해 유연성이 높아진다.