[Design Pattern] SOLID 원칙

2025. 8. 5. 23:46·Java/디자인패턴

SOLID 원칙이란 객체지향 설계에서 지켜야 할 5가지 원칙으로, 다음과 같다.

  • 단일 책임 원칙(Single Responsibility Principle)
  • 개방-폐쇄 원칙(Open-Closed Principle)
  • 리스코프 치환 원칙(Liskov Substitution Principle)
  • 인터페이스 분리 원칙(Interface Segregation Principle)
  • 의존성 역전 원칙(Dependency Inversion Principle)

 


 

SOLID 원칙은 왜 필요할까?

프로그램을 설계할 때는 결합도는 낮게, 응집도는 높게 유지하는 것이 중요하다.
즉, 변화하는 요구 사항에 유연하게 대응하기 위해 내부적으로는 응집된 모듈을 최대한 수정하지 않고,

파생되는 기능들은 느슨하게 결합하여 변경에 쉽게 적응할 수 있어야 한다.

 

위 원칙을 지키기 위해서는 기본 뼈대 역할을 하는 SOLID 원칙이 필요하다!

이제부터 SOLID 원칙을 하나씩 살펴보도록 하겠다.

 


 

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

하나의 객체는 반드시 하나의 동작만의 책임을 가져야 한다는 원칙이다.

 

public class Order {
    public void processOrder() {
        // 주문 처리 로직
    }

    public void processPayment() {
        // 결제 처리 로직
    }
}

 

위 코드의 경우 주문과 결제 로직이 모두 Order 클래스 내에 있다.

고로 Order 클래스를 변경한다면 그 이유는 '주문'과 '결제' 둘 중 하나일 것이다.

따라서 Order 클래스는 단일 책임 원칙에 위배된다.

 

단일 책임 원칙에 맞게 클래스를 분리해보자!

public class Order {
    public void processOrder() {
        // 주문 처리 로직
    }
}

public class Payment {
    public void processPayment() {
        // 결제 처리 로직
    }
}

 

Order 클래스를 변경한다면 그 이유는 오직 '주문' 하나이고, Payment 클래스를 변경한다면 그 이유는 오직 '결제' 하나 때문이다.

따라서 변경이 필요할 때 한 클래스만 수정하면 되므로 유지보수가 쉬워진다.

 


 

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

변경에는 닫혀있어야 하고, 확장에는 열려있어야 한다는 원칙이다.

즉, 기존 코드는 수정하지 않고 새로운 기능을 확장할 수 있도록 설계해야 한다는 의미이다.

 

기능 확장을 하는데 있어서 기존 코드를 수정하지 않을 수 있을까?

먼저 코드를 살펴보자.

public class WriterService {
    public void write(String type, String content) {
        if ("file".equals(type)) {
            // 파일에 쓰기
        } else if ("database".equals(type)) {
            // 데이터베이스에 쓰기
        }
    }
}

 

본 코드는 쓰기 서비스를 지원하는 클래스이다.

만약 콘솔에 콘텐츠를 쓰고 싶으면 else if 문에 "console" 을 추가하면 된다.

하지만 이렇게 되면 기존 코드는 수정해야 하므로 개방-폐쇄 원칙을 위배하게 된다.

 

개방-폐쇄 원칙을 따르기 위한 코드를 살펴보자.

public interface Writer {
    void write(String content);
}

public class FileWriter implements Writer {
    public void write(String content) {
        // 파일에 내용 쓰기
    }
}

public class DatabaseWriter implements Writer {
    public void write(String content) {
        // DB에 내용 쓰기
    }
}

public class WriterService {
    private Writer writer;

    public WriterService(Writer writer) {
        this.writer = writer;
    }

    public void write(String content) {
        writer.write(content);
    }
}

 

Writer 인터페이스는 writer() 함수를 정의하고, 쓰기 서비스를 지원하는 WriterService는 인터페이스를 참조하고 있다.

콘솔에 콘텐츠를 쓰고 싶다면 기존 WriterService를 바꿀 필요없이 ConsoleWriter를 추가해 기능을 확장하면 된다!

public class ConsoleWriter implements Writer {
    public void write(String content) {
        // 콘솔에 내용 쓰기
    }
}

 

 


 

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

하위 타입 객체는 상위 타입 객체에서 가능한 행위를 수행할 수 있어야 한다는 원칙이다.

 

B 타입이 A의 하위 타입일 때,
A 타입으로 작성된 프로그램은 B 타입으로 치환해도 정상적으로 동작해야 한다!

 

대표적인 직사각형 - 정사각형 클래스의 관계를 통해 리스코프 치환 원칙을 위배하는 예시를 살펴보자.

class Rectangle {
    protected int width;
    protected int height;

    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getArea() {
        return width * height;
    }
}
class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width; // 정사각형은 너비와 높이가 같아야 함
    }

    @Override
    public void setHeight(int height) {
        this.width = height; // 정사각형은 너비와 높이가 같아야 함
        this.height = height;
    }
}
public class Main {
    public static void main(String[] args) {
        Rectangle rect = new Rectangle();
        rect.setWidth(4);
        rect.setHeight(5);
        System.out.println("직사각형 넓이: " + rect.getArea()); // 20

        Rectangle square = new Square();
        square.setWidth(4);
        square.setHeight(5);
        System.out.println("정사각형 넓이 (기대값 20): " + square.getArea()); // 25
    }
}

 

상위 타입은 Rectangle 클래스 / 하위 타입은 Square 클래스 이므로, 

Rectangle이 Square 타입으로 치환해도 동일하게 동작해야 한다.

하지만 Square 클래스의 setWidth()와 setHeight() 함수로 인해 본 기댓값 20과 달리 25가 출력되었다.

 

따라서 리스코프 치환 원칙을 준수하기 위해서는 Rectangle과 Square 클래스의 상위 클래스로 Shape 클래스를 두는 것을 권장한다.

 


 

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

클라이언트는 자신이 사용하는 메소드에만 의존해야 한다는 원칙이다.

만약, 인터페이스에서 사용하지 않은 메소드가 있다면 다른 인터페이스로 분리하여야 한다.

 

interface Phone {
    void call();
    void sms();
    void showApps();  // 스마트폰에만 해당하는 기능
}

 

Phone 인터페이스는 구세대 폰이 동작하지 않는 showApps() 함수가 있다.

만약 구세대 폰에 대한 클래스를 만드려면 showApps()는 비워두거나 예외를 발생시켜야 한다!

따라서 이 코드는 인터페이스 분리 원칙을 위배한다고 할 수 있다.

 

인터페이스 분리 원칙에 맞게 인터페이스를 분리시켜보자!

interface BasicPhone {
    void call();
    void sms();
}

interface SmartFeatures {
    void showApps();
}

 

구세대 폰 클래스는 BasicPhone 인터페이스를 implements 하고, 

스마트 폰 클래스는 BasicPhone과 SmartFeatures 두 인터페이스를 implements 하면

→ 두 클래스 모두 사용하는 메소드만 의존할 수 있게 된다.

 


 

의존성 역전 원칙(DIP, Dependency Inversion Principle)

의존 관계를 맺을 때, 변하기 쉬운 것(구체적인 것)보다는 변하기 어려운 것(추상적인 것)에 의존해야 한다는 원칙이다.

즉, 저수준 모듈에 의존하면 확장이 어렵다는 의미이다.

 

class Adder {
    public int add(int a, int b) { return a + b; }
}

class Calculator {
    private Adder adder = new Adder();

    public int calculate(int a, int b) {
        return adder.add(a, b);
    }
}

 

Calculator 클래스는 Adder 클래스(저수준 모듈)에 의존하고 있다.

여기서 Multiplier 클래스를 추가한다면, Calculator를 직접 바꿔야하므로 확장이 어려워진다.

이는 의존성 역전 원칙을 위배한다고 볼 수 있기 때문에, 다음과 같이 고수준 모듈에 의존하는 방식으로 바꾸어야 한다.

 

interface Operation {
    int operate(int a, int b);
}

class Adder implements Operation {
    public int operate(int a, int b) { return a + b; }
}

class Calculator {
    private Operation operation;

    public Calculator(Operation operation) {
        this.operation = operation;
    }

    public int calculate(int a, int b) {
        return operation.operate(a, b);
    }
}

 

Calculator은 저수준 모듈인 Adder 클래스에 의존하지 않고, Operation 인터페이스에 의존하기 때문에,

Multiplier 클래스를 추가할 때 Calculator 코드를 직접 바꿀 필요가 없다!

다시 말해 추상적인 것에 의존하므로 의존성 역전 원칙을 지킨 코드라고 할 수 있다.

 


 

마무리

 

지금까지 SOLID 5가지 원칙에 대해 각각 알아보았다.

각각의 원칙이 결국 말하고자 하는 것은 클래스가 각자의 책임에 맞게 분배되어야 한다는 것이다.

처음부터 모든 원칙을 완벽히 적용하여 설계하는 것이 이상적이지만,

변화하는 요구 사항을 모두 반영하는 설계를 만드는 것은 결코 쉬운 일이 아니다...

그래도 위 원칙들을 염두에 두고 설계한다면, 유지보수가 훨씬 용이해질 것이다!

 

:)

 

출처(참고 자료)

https://tech.kakaobank.com/posts/2411-solid-truth-or-myths-for-developers/

https://sihyung92.oopy.io/oop/solid

 

 

'Java > 디자인패턴' 카테고리의 다른 글

[Design Pattern] 디자인 패턴  (4) 2025.08.09
'Java/디자인패턴' 카테고리의 다른 글
  • [Design Pattern] 디자인 패턴
jjangsudiary
jjangsudiary
jjangsudiary 님의 블로그 입니다.
  • jjangsudiary
    jjangsudiary 님의 블로그
    jjangsudiary
  • 전체
    오늘
    어제
    • 분류 전체보기 (81) N
      • 이모저모 (0)
        • 회고 (0)
      • Development (17) N
        • 개발 공부 (14) N
        • 프로젝트 (2)
      • Android (10)
        • Compose (1)
      • AI (15)
      • Computer Science (25)
        • 네트워크 (8)
        • 데이터베이스 (10)
        • 운영체제 (6)
        • 자료구조 (0)
        • 컴퓨터구조 (1)
      • Java (9)
        • 디자인패턴 (2)
      • Spring (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • GitHub
  • 공지사항

  • 인기 글

  • 태그

    database
    baekjoon
    TensorFlow
    프로그래머스
    android
    코딩 테스트
    os
    인공지능
    자바
    안드로이드
    머신러닝
    딥러닝
    백준
    Python
    java
    CS
    Ai
    파이썬
    운영체제
    db
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
jjangsudiary
[Design Pattern] SOLID 원칙
상단으로

티스토리툴바