SOLID

hoon·2025년 1월 30일

JAVA

목록 보기
17/21

객체지향 설계 원칙: SOLID

객체지향 설계 원칙인 SOLID는 객체지향 프로그래밍(OOP)을 올바르게 사용하는 데 도움을 주는 다섯 가지 원칙을 뜻한다. 이 원칙들을 지키면 코드가 더 유지보수하기 쉽고, 확장 가능하며, 유연해진다. 각 원칙을 간단히 설명하고 예를 들어보겠다.


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

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

  • 의미: 클래스는 오직 하나의 일(책임)만 담당해야 하고, 그 일에 대해서만 변경되어야 한다.
  • 문제: 한 클래스에 여러 책임이 있으면, 하나의 기능을 수정할 때 다른 기능까지 영향을 미친다.
  • 예시:
    • 잘못된 예:
      class Report {
          void generateReport() { /* 보고서 생성 */ }
          void sendReportByEmail() { /* 이메일로 보고서 발송 */ }
      }
      → 이 클래스는 보고서를 생성하고 이메일을 보내는 두 가지 책임을 가짐.
    • 개선된 예:
      class ReportGenerator {
          void generateReport() { /* 보고서 생성 */ }
      }
      
      class EmailSender {
          void sendEmail() { /* 이메일 발송 */ }
      }

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

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

  • 의미: 기존 코드를 수정하지 않고 기능을 확장할 수 있어야 한다.
  • 문제: 새 기능 추가 시 기존 코드를 자주 변경하면 오류 발생 위험이 높아진다.
  • 예시:
    • 잘못된 예:
      class PaymentProcessor {
          void process(String paymentType) {
              if (paymentType.equals("creditCard")) { /* 신용카드 처리 */ }
              else if (paymentType.equals("paypal")) { /* PayPal 처리 */ }
          }
      }
    • 개선된 예:
      interface Payment {
          void process();
      }
      
      class CreditCardPayment implements Payment {
          public void process() { /* 신용카드 처리 */ }
      }
      
      class PayPalPayment implements Payment {
          public void process() { /* PayPal 처리 */ }
      }
      
      class PaymentProcessor {
          void process(Payment payment) {
              payment.process();
          }
      }
      → 새 결제 방식이 추가될 때 기존 코드를 변경하지 않고 확장 가능.

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

서브클래스는 부모클래스를 대체할 수 있어야 한다.

  • 의미: 부모 클래스 타입의 객체를 서브클래스 객체로 대체해도 프로그램이 문제없이 동작해야 한다.
  • 문제: 서브클래스가 부모클래스의 규칙을 어기면, 코드가 예상대로 동작하지 않는다.
  • 예시:
    • 잘못된 예:
      class Bird {
          void fly() { /* 날기 */ }
      }
      
      class Penguin extends Bird {
          void fly() { throw new UnsupportedOperationException("펭귄은 날 수 없음"); }
      }
    • 개선된 예:
      interface Bird {
          void move();
      }
      
      class FlyingBird implements Bird {
          public void move() { /* 날기 */ }
      }
      
      class Penguin implements Bird {
          public void move() { /* 헤엄치기 */ }
      }

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

클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다.

  • 의미: 인터페이스는 구체적이고 작게 나누어야 한다.
  • 문제: 인터페이스가 너무 크면, 사용하지 않는 메서드까지 구현해야 한다.
  • 예시:
    • 잘못된 예:
      interface Worker {
          void work();
          void eat();
      }
      
      class Robot implements Worker {
          public void work() { /* 작업 */ }
          public void eat() { throw new UnsupportedOperationException("로봇은 먹지 않음"); }
      }
    • 개선된 예:
      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() { /* 식사 */ }
      }

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

상위 모듈이 하위 모듈에 의존하지 말고, 추상화에 의존해야 한다.

  • 의미: 구체적인 클래스가 아니라 인터페이스나 추상 클래스에 의존해야 한다.
  • 문제: 특정 구현에 의존하면, 구현이 바뀔 때 상위 모듈까지 영향을 받는다.
  • 예시:
    • 잘못된 예:
      class Keyboard {
          void type() { /* 입력 */ }
      }
      
      class Computer {
          private Keyboard keyboard;
      
          public Computer() {
              this.keyboard = new Keyboard(); // 특정 구현에 의존
          }
      }
    • 개선된 예:
      interface InputDevice {
          void input();
      }
      
      class Keyboard implements InputDevice {
          public void input() { /* 입력 */ }
      }
      
      class Mouse implements InputDevice {
          public void input() { /* 입력 */ }
      }
      
      class Computer {
          private InputDevice inputDevice;
      
          public Computer(InputDevice inputDevice) {
              this.inputDevice = inputDevice; // 추상화에 의존
          }
      }

정리

원칙설명
SRP (단일 책임 원칙)하나의 클래스는 하나의 책임만 가져야 한다.
OCP (개방-폐쇄 원칙)기존 코드를 변경하지 않고 확장 가능해야 한다.
LSP (리스코프 치환 원칙)부모 클래스 대신 자식 클래스를 사용해도 정상 동작해야 한다.
ISP (인터페이스 분리 원칙)인터페이스는 작고 구체적으로 설계해야 한다.
DIP (의존 역전 원칙)구체적인 구현보다 추상화에 의존해야 한다.

이 원칙들을 완벽히 지키는 건 어렵지만, 지향하면서 코드를 작성하면 유지보수성, 재사용성, 확장성이 크게 향상된다! 🚀

0개의 댓글