SOLID

객체지향 설계에서 유지보수성과 확장성이 높은 코드를 작성하기 위한 5가지 설계 원칙입니다.

SOLID 한눈에 보기

원칙이름핵심 한 줄
S단일 책임 원칙 (SRP)클래스는 하나의 책임만 가져야 한다
O개방-폐쇄 원칙 (OCP)확장에는 열려있고, 수정에는 닫혀있어야 한다
L리스코프 치환 원칙 (LSP)자식 클래스는 부모 클래스를 대체할 수 있어야 한다
I인터페이스 분리 원칙 (ISP)사용하지 않는 인터페이스에 의존하면 안 된다
D의존성 역전 원칙 (DIP)추상화에 의존해야 한다, 구체화에 의존하면 안 된다

S - 단일 책임 원칙 (SRP)

// X 하나의 클래스가 여러 책임
class User {
    public void login() { ... }       // 인증 책임
    public void saveToDb() { ... }    // DB 책임
    public void sendEmail() { ... }   // 이메일 책임
}

// O 책임별로 클래스 분리
class AuthService   { public void login() { ... } }
class UserRepository { public void save() { ... } }
class EmailService  { public void send() { ... } }

O - 개방-폐쇄 원칙 (OCP)

// X 새 결제 수단 추가 시 기존 코드 수정 필요
class PaymentService {
    public void pay(String type) {
        if (type.equals("card")) { ... }
        else if (type.equals("kakao")) { ... }  // 추가할 때마다 수정
    }
}

// O 인터페이스로 확장에만 열기
interface PaymentStrategy {
    void pay();
}
class CardPayment  implements PaymentStrategy { public void pay() { ... } }
class KakaoPayment implements PaymentStrategy { public void pay() { ... } }

class PaymentService {
    public void pay(PaymentStrategy strategy) {
        strategy.pay();  // 기존 코드 수정 없이 새 결제 수단 추가 가능
    }
}

L - 리스코프 치환 원칙 (LSP)

// X 자식이 부모를 대체하지 못하는 경우
class Rectangle {
    public void setWidth(int w)  { this.width = w; }
    public void setHeight(int h) { this.height = h; }
}
class Square extends Rectangle {
    @Override
    public void setWidth(int w) {
        this.width = w;
        this.height = w;  // 정사각형이라 높이도 같이 변경 → 부모 동작 위반 X
    }
}

// O 공통 인터페이스로 분리
interface Shape { int area(); }
class Rectangle implements Shape { ... }
class Square    implements Shape { ... }

I - 인터페이스 분리 원칙 (ISP)

// X 너무 많은 기능을 가진 인터페이스
interface Worker {
    void work();
    void eat();   // 로봇은 eat()이 필요 없음
}
class Robot implements Worker {
    public void work() { ... }
    public void eat()  { }  // 억지로 구현 X
}

// O 인터페이스 분리
interface Workable { void work(); }
interface Eatable  { void eat(); }

class Human implements Workable, Eatable { ... }
class Robot implements Workable { ... }  // 필요한 것만 구현

D - 의존성 역전 원칙 (DIP)

// X 구체 클래스에 직접 의존
class OrderService {
    private MySQLRepository repo = new MySQLRepository();  // 구체 클래스에 의존
}

// O 추상화(인터페이스)에 의존
class OrderService {
    private final OrderRepository repo;  // 인터페이스에 의존

    public OrderService(OrderRepository repo) {  // 외부에서 주입 (DI)
        this.repo = repo;
    }
}

// MySQL → MongoDB로 바꿔도 OrderService 코드 변경 없음
class MySQLRepository  implements OrderRepository { ... }
class MongoRepository  implements OrderRepository { ... }

왜 중요한가?

SOLID 미적용          SOLID 적용
─────────────         ─────────────
기능 추가 → 기존 코드 수정   기능 추가 → 새 코드만 추가
테스트 어려움               단위 테스트 용이
변경 시 사이드이펙트 큼      변경 영향 범위 최소화

SOLID는 규칙이 아닌 "지향점" 입니다. 무조건 적용하면 오히려 과설계가 될 수 있으므로, 변경 가능성이 높은 부분에 선택적으로 적용하는 것이 핵심입니다.

CSLOG에서 보기