Topic

SOLID 원칙

JackerLab 2025. 3. 19. 01:08
728x90
반응형

개요

SOLID 원칙은 객체지향 프로그래밍(OOP)의 설계 원칙으로, 소프트웨어의 유지보수성과 확장성을 극대화하기 위한 5가지 핵심 원칙을 의미합니다. 이 원칙을 적용하면 코드가 더욱 모듈화되고, 결합도를 낮추며, 변경 사항에 대한 유연성이 증가합니다. 본 글에서는 SOLID 원칙의 개념과 각각의 원칙을 구체적으로 설명하고, 적용 방법 및 실제 사례를 살펴봅니다.


1. SOLID 원칙이란?

SOLID 원칙은 **로버트 C. 마틴(Robert C. Martin)**이 제안한 객체지향 설계의 5가지 핵심 원칙을 의미합니다. 이 원칙들은 개별적으로도 강력한 설계 원칙이지만, 함께 적용하면 더욱 효과적인 소프트웨어 구조를 구축할 수 있습니다.

1.1 SOLID 원칙의 구성

원칙설명

S - 단일 책임 원칙(Single Responsibility Principle, SRP) 하나의 클래스는 단 하나의 책임만 가져야 한다.
O - 개방-폐쇄 원칙(Open-Closed Principle, OCP) 기존 코드를 변경하지 않고 기능을 확장할 수 있어야 한다.
L - 리스코프 치환 원칙(Liskov Substitution Principle, LSP) 자식 클래스는 부모 클래스를 대체할 수 있어야 한다.
I - 인터페이스 분리 원칙(Interface Segregation Principle, ISP) 클라이언트가 필요하지 않는 인터페이스에 의존하지 않아야 한다.
D - 의존 역전 원칙(Dependency Inversion Principle, DIP) 고수준 모듈이 저수준 모듈에 의존하지 않고, 추상화에 의존해야 한다.

SOLID 원칙을 적용하면 코드의 가독성과 유지보수성이 높아지고, 변화에 유연한 설계를 할 수 있습니다.


2. SOLID 원칙의 상세 설명 및 예제

2.1 단일 책임 원칙 (SRP - Single Responsibility Principle)

"클래스는 하나의 책임만 가져야 한다."

설명:

  • 하나의 클래스가 하나의 책임만 가지도록 설계해야 유지보수가 용이하고 코드 변경 시 다른 기능에 영향을 미치지 않습니다.
  • 클래스가 여러 책임을 가지면, 변경 사항이 많아지고 결합도가 증가합니다.

예제 (잘못된 설계)

class Report {
    public void generateReport() { /* 리포트 생성 로직 */ }
    public void printReport() { /* 리포트 출력 로직 */ }
    public void saveToFile() { /* 파일 저장 로직 */ }
}

개선된 설계 (각 책임을 분리)

class ReportGenerator { public void generateReport() { /* 리포트 생성 */ } }
class ReportPrinter { public void printReport() { /* 리포트 출력 */ } }
class ReportSaver { public void saveToFile() { /* 파일 저장 */ } }

2.2 개방-폐쇄 원칙 (OCP - Open-Closed Principle)

"기존 코드를 수정하지 않고 기능을 확장할 수 있어야 한다."

설명:

  • 확장을 위해 기존 코드를 변경하는 것이 아니라, 새로운 기능을 추가하는 방식으로 설계해야 한다.
  • 유지보수성을 높이고, 기존 기능에 영향을 최소화할 수 있다.

예제 (잘못된 설계 - 조건문으로 확장 관리)

class PaymentProcessor {
    public void processPayment(String type) {
        if (type.equals("CreditCard")) { /* 신용카드 결제 로직 */ }
        else if (type.equals("PayPal")) { /* PayPal 결제 로직 */ }
    }
}

개선된 설계 (추상화 활용)

interface Payment {
    void process();
}
class CreditCardPayment implements Payment {
    public void process() { /* 신용카드 결제 로직 */ }
}
class PayPalPayment implements Payment {
    public void process() { /* PayPal 결제 로직 */ }
}
class PaymentProcessor {
    public void processPayment(Payment payment) { payment.process(); }
}

2.3 리스코프 치환 원칙 (LSP - Liskov Substitution Principle)

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

설명:

  • 자식 클래스가 부모 클래스의 기능을 변경하지 않고 확장해야 한다.
  • 상속 구조에서 일관성을 유지하고, 다형성을 보장할 수 있다.

예제 (잘못된 설계 - 부모 클래스를 대체하지 못하는 경우)

class Rectangle {
    protected int width, height;
    public void setWidth(int w) { width = w; }
    public void setHeight(int h) { height = h; }
}
class Square extends Rectangle {
    public void setWidth(int w) { width = height = w; }
    public void setHeight(int h) { width = height = h; }
}

개선된 설계 (직사각형과 정사각형을 별도로 구현)

interface Shape {
    int getArea();
}
class Rectangle implements Shape {
    protected int width, height;
    public int getArea() { return width * height; }
}
class Square implements Shape {
    private int side;
    public int getArea() { return side * side; }
}

2.4 인터페이스 분리 원칙 (ISP - Interface Segregation Principle)

"클라이언트가 필요하지 않는 인터페이스에 의존하지 않아야 한다."

설명:

  • 하나의 인터페이스가 너무 많은 기능을 포함하면, 이를 구현하는 클래스가 불필요한 기능까지 구현해야 하는 문제가 발생합니다.
  • 클라이언트별로 인터페이스를 분리하여 필요한 기능만 제공하도록 설계해야 합니다.

예제 (잘못된 설계 - 단일 인터페이스에 너무 많은 기능 포함)

interface Worker {
    void work();
    void eat();
}
class OfficeWorker implements Worker {
    public void work() { /* 사무 작업 수행 */ }
    public void eat() { /* 점심시간 */ }
}
class Robot implements Worker {
    public void work() { /* 공장 작업 수행 */ }
    public void eat() { throw new UnsupportedOperationException("로봇은 먹지 않습니다."); }
}

개선된 설계 (인터페이스 분리 적용)

interface Workable {
    void work();
}
interface Eatable {
    void eat();
}
class OfficeWorker implements Workable, Eatable {
    public void work() { /* 사무 작업 수행 */ }
    public void eat() { /* 점심시간 */ }
}
class Robot implements Workable {
    public void work() { /* 공장 작업 수행 */ }
}

2.5 의존 역전 원칙 (DIP - Dependency Inversion Principle)

"고수준 모듈이 저수준 모듈에 의존하지 않고, 추상화에 의존해야 한다."

설명:

  • 고수준 모듈(비즈니스 로직)은 저수준 모듈(데이터베이스, API 등)에 직접 의존하면 변경이 어렵습니다.
  • 인터페이스(추상화)를 사용하여 모듈 간의 결합도를 낮추고 유연성을 증가시킵니다.

예제 (잘못된 설계 - 구체적인 클래스에 직접 의존)

class MySQLDatabase {
    public void connect() { /* MySQL 연결 */ }
}
class DataService {
    private MySQLDatabase database;
    public DataService() { this.database = new MySQLDatabase(); }
    public void fetchData() { database.connect(); }
}

개선된 설계 (추상화 활용)

interface Database {
    void connect();
}
class MySQLDatabase implements Database {
    public void connect() { /* MySQL 연결 */ }
}
class PostgreSQLDatabase implements Database {
    public void connect() { /* PostgreSQL 연결 */ }
}
class DataService {
    private Database database;
    public DataService(Database database) { this.database = database; }
    public void fetchData() { database.connect(); }
}

3. SOLID 원칙의 적용 방법

SOLID 원칙을 적용하면 소프트웨어의 유지보수성과 확장성을 높일 수 있습니다. 적용 방법은 다음과 같습니다.

  1. 코드의 단일 책임 원칙 적용 - 클래스가 하나의 책임만 가지도록 설계
  2. 추상화와 인터페이스를 활용 - 개방-폐쇄 원칙 준수
  3. 상속 시 부모 클래스의 동작을 변경하지 않도록 설계 - 리스코프 치환 원칙 준수
  4. 불필요한 인터페이스 의존성 제거 - 인터페이스 분리 원칙 적용
  5. 고수준 모듈과 저수준 모듈의 의존성을 분리 - 의존 역전 원칙 적용

SOLID 원칙을 적용하면 코드 품질이 향상되고 유지보수가 쉬운 시스템을 구축할 수 있습니다.


4. 결론

SOLID 원칙은 객체지향 설계에서 가장 중요한 원칙으로, 유지보수성과 확장성을 높이고 코드의 안정성을 향상하는 데 중요한 역할을 합니다. SOLID 원칙을 적용하면 소프트웨어의 결합도를 낮추고 유연성을 증가시켜, 변화에 강한 시스템을 구축할 수 있습니다.

SOLID 원칙을 적용하여 더욱 견고하고 확장 가능한 객체지향 설계를 실현해보세요!

728x90
반응형