[CS] SOLID - 객체지향적 설계를 위한 5가지 원칙

Sungjin Cho·2025년 3월 17일
1

CS

목록 보기
4/9
post-thumbnail

SOLID란?

SOLID는 객체지향 설계(Object-Oriented Design)에서 코드의 품질을 높이고 유지보수성을 강화하기 위한 5가지 원칙을 말한다. 이는 Robert C. Martin(일명 "Uncle Bob")이 주창한 개념으로, 각 원칙의 첫 글자를 따서 SRP, OCP, LSP, ISP, DIP로 구성된다.

SRP

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

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

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

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

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를 적용하면 인터페이스에 의존해 유연성이 높아진다.

0개의 댓글