SOLID

AI
qwen-3-235b-a22b-instruct-2507
작성자
익명
작성일
2025.09.01
조회수
9
버전
v1

SOLID

SOLID는 객체지향 소프트웨어 설계에서 코드의 유지보수성, 확장성, 재사용성을 높이기 위해 제안된 다섯 가지 핵심 원칙의 집합입니다. 이 원칙들은 소프트웨어 개발자 로버트 C. 마틴(Robert C. Martin)에 의해 정립되었으며, 각각의 이니셜을 따서 "SOLID"라는 이름이 붙여졌습니다. SOLID 원칙은 객체지향 프로그래밍(OOP)의 기본 개념인 캡슐화, 상속, 다형성을 효과적으로 활용하는 데 기여하며, 대규모 시스템 개발 시 결합도를 낮추고 응집도를 높이는 데 중요한 역할을 합니다.

이 문서에서는 SOLID의 다섯 원칙을 각각 상세히 설명하고, 각 원칙을 준수했을 때의 장점과 위반했을 때의 문제점을 살펴봅니다. 또한 실제 코드 예제를 통해 개념을 명확히 이해할 수 있도록 돕습니다.


SOLID의 다섯 원칙

SOLID는 다음의 다섯 가지 원칙으로 구성됩니다:

  1. S단일 책임 원칙 (Single Responsibility Principle)
  2. O개방-폐쇄 원칙 (Open/Closed Principle)
  3. L리스코프 치환 원칙 (Liskov Substitution Principle)
  4. I인터페이스 분리 원칙 (Interface Segregation Principle)
  5. D의존성 역전 원칙 (Dependency Inversion Principle)

각 원칙은 서로 독립적이면서도 상호보완적인 관계를 가지며, 함께 적용될 때 강력한 설계 기반을 형성합니다.


1. 단일 책임 원칙 (SRP)

"한 클래스는 하나의 이유만으로 변경되어야 한다."

단일 책임 원칙(Single Responsibility Principle)은 하나의 클래스가 하나의 기능 또는 책임만을 가져야 한다는 원칙입니다. 즉, 클래스는 오직 하나의 변경 이유를 가져야 하며, 여러 기능을 섞어두면 유지보수가 어려워지고 결합도가 높아집니다.

예시

// ❌ SRP 위반 예시
class Report {
    public void generate() { /* 보고서 생성 */ }
    public void saveToFile(String path) { /* 파일 저장 */ }
    public void sendEmail(String email) { /* 이메일 전송 */ }
}

이 클래스는 보고서 생성, 파일 저장, 이메일 전송이라는 세 가지 책임을 가지고 있어, 각 기능 중 하나가 변경되면 전체 클래스가 수정되어야 합니다.

개선된 예시

// ✅ SRP 준수
class ReportGenerator {
    public Report generate() { /* 생성만 담당 */ }
}

class ReportSaver {
    public void saveToFile(Report report, String path) { /* 저장만 담당 */ }
}

class ReportSender {
    public void sendEmail(Report report, String email) { /* 전송만 담당 */ }
}

각 클래스는 하나의 책임만 가지며, 독립적으로 변경 및 테스트 가능합니다.


2. 개방-폐쇄 원칙 (OCP)

"소프트웨어 요소는 확장에는 열려 있으나 수정에는 닫혀 있어야 한다."

개방-폐쇄 원칙(Open/Closed Principle)은 기존 코드를 수정하지 않고도 새로운 기능을 추가할 수 있도록 설계해야 한다는 원칙입니다. 이를 위해 추상화와 다형성을 활용합니다.

예시

// ❌ OCP 위반
class AreaCalculator {
    public double calculateArea(Object shape) {
        if (shape instanceof Rectangle) {
            Rectangle r = (Rectangle) shape;
            return r.width * r.height;
        } else if (shape instanceof Circle) {
            Circle c = (Circle) shape;
            return Math.PI * c.radius * c.radius;
        }
        // 새로운 도형 추가 시 이 메서드를 수정해야 함
    }
}

새로운 도형이 추가될 때마다 calculateArea 메서드를 수정해야 하므로 OCP를 위반합니다.

개선된 예시

// ✅ OCP 준수
interface Shape {
    double calculateArea();
}

class Rectangle implements Shape {
    public double calculateArea() { return width * height; }
}

class Circle implements Shape {
    public double calculateArea() { return Math.PI * radius * radius; }
}

class AreaCalculator {
    public double calculateArea(Shape shape) {
        return shape.calculateArea(); // 확장 가능
    }
}

새로운 도형 클래스를 추가하더라도 기존 코드를 수정하지 않고도 동작합니다.


3. 리스코프 치환 원칙 (LSP)

"자식 클래스는 부모 클래스를 대체할 수 있어야 한다."

리스코프 치환 원칙(Liskov Substitution Principle)은 하위 클래스가 상위 클래스의 인스턴스로 대체되었을 때, 프로그램의 정확성이 유지되어야 한다는 원칙입니다. 즉, 상속 관계에서 부모 클래스의 동작을 깨뜨리지 않아야 합니다.

예시

// ❌ LSP 위반
class Bird {
    public void fly() { System.out.println("Flying"); }
}

class Penguin extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("Penguins can't fly");
    }
}

팽귄은 새지만 날 수 없으므로 fly() 메서드를 오버라이드할 때 예외를 던지며, 이는 부모 클래스의 기대 동작을 위반합니다.

개선된 예시

// ✅ LSP 준수
interface Flyable {
    void fly();
}

class Bird { /* 일반적인 새 속성 */ }

class Sparrow extends Bird implements Flyable {
    public void fly() { System.out.println("Flying high"); }
}

class Penguin extends Bird { /* fly() 없음 */ }

날 수 있는 새만 Flyable 인터페이스를 구현함으로써 LSP를 지킵니다.


4. 인터페이스 분리 원칙 (ISP)

"클라이언트는 자신이 사용하지 않는 인터페이스에 의존해서는 안 된다."

인터페이스 분리 원칙(Interface Segregation Principle)은 큰 인터페이스를 여러 개의 작은 인터페이스로 나누어, 클라이언트가 불필요한 메서드에 의존하지 않도록 해야 한다는 원칙입니다.

예시

// ❌ ISP 위반
interface Machine {
    void print();
    void scan();
    void fax();
}

class OldPrinter implements Machine {
    public void print() { /* 가능 */ }
    public void scan() { throw new UnsupportedOperationException(); }
    public void fax() { throw new UnsupportedOperationException(); }
}

오래된 프린터는 스캔과 팩스를 지원하지 않지만, 인터페이스 구현을 위해 불필요한 메서드를 포함해야 합니다.

개선된 예시

// ✅ ISP 준수
interface Printable { void print(); }
interface Scannable { void scan(); }
interface Faxable { void fax(); }

class OldPrinter implements Printable {
    public void print() { /* 구현 */ }
}

class MultiFunctionPrinter implements Printable, Scannable, Faxable {
    public void print() { /* 구현 */ }
    public void scan() { /* 구현 */ }
    public void fax() { /* 구현 */ }
}

클라이언트는 필요한 인터페이스만 구현하면 됩니다.


5. 의존성 역전 원칙 (DIP)

"상위 모듈은 하위 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다."

의존성 역전 원칙(Dependency Inversion Principle)은 고수준 모듈(비즈니스 로직)이 저수준 모듈(구현 세부사항)에 직접 의존하지 않도록, 추상화된 인터페이스를 통해 의존성을 제어해야 한다는 원칙입니다.

예시

// ❌ DIP 위반
class EmailService {
    public void send(String message) { /* 이메일 전송 */ }
}

class Notification {
    private EmailService email = new EmailService();
    public void notify(String msg) { email.send(msg); }
}

Notification 클래스가 EmailService에 강하게 결합되어 있습니다.

개선된 예시

// ✅ DIP 준수
interface MessageService {
    void send(String message);
}

class EmailService implements MessageService {
    public void send(String message) { /* 이메일 전송 */ }
}

class Notification {
    private MessageService service;
    public Notification(MessageService service) {
        this.service = service; // 의존성 주입
    }
    public void notify(String msg) { service.send(msg); }
}

이제 Notification은 구체 클래스가 아닌 추상 인터페이스에 의존하므로, SMS, 푸시 알림 등으로 쉽게 확장 가능합니다.


결론

SOLID 원칙은 객체지향 설계의 질을 높이고, 소프트웨어의 유연성과 유지보수성을 극대화하는 데 핵심적인 역할을 합니다. 각 원칙은 독립적으로 적용할 수 있지만, 함께 사용될 때 시너지를 발휘하여 견고하고 확장 가능한 시스템을 구축할 수 있습니다. 현대의 프레임워크(예: Spring, .NET)는 이러한 원칙을 내재하고 있으며, DIP 기반의 의존성 주입(DI)과 같은 패턴은 SOLID를 실현하는 데 널리 활용됩니다.


참고 자료

  • Martin, R. C. (2003). Agile Software Development, Principles, Patterns, and Practices. Prentice Hall.
  • Uncle Bob’s Blog: https://blog.cleancoder.com
  • 객체지향 설계의 5가지 원칙 (SOLID), 위키백과
AI 생성 콘텐츠 안내

이 문서는 AI 모델(qwen-3-235b-a22b-instruct-2507)에 의해 생성된 콘텐츠입니다.

주의사항: AI가 생성한 내용은 부정확하거나 편향된 정보를 포함할 수 있습니다. 중요한 결정을 내리기 전에 반드시 신뢰할 수 있는 출처를 통해 정보를 확인하시기 바랍니다.

이 AI 생성 콘텐츠가 도움이 되었나요?