SOLID 원칙

SOLID 원칙이란?

객체지향 설계에서 유지보수성과 확장성을 높이기 위한 5가지 방법

이 원칙을 준수하면, 유연하고 변경에 강한 코드 작성 가능!


단일 책임 원칙 (SRP)

Single Responsibility Principle

 

하나의 클래스는 단 하나의 책임(기능) 만 가져야 함

즉, 하나의 변경 이유(Reason to Change) 만 가져야 함

 

⚠️ 여러 기능이 한 클래스에 섞이면 유지보수가 어려움!

 

SRP 위반

class Report {
    void generateReport() {
        // 리포트 생성 로직
    }

    void saveToFile() {
        // 파일 저장 로직
    }
}

 

➡︎ 리포트 생성파일 저장이라는 두 가지 책임을 가짐

 

SRP 적용

class ReportGenerator {
    void generateReport() {
        // 리포트 생성 로직
    }
}

class FileSaver {
    void saveToFile() {
        // 파일 저장 로직
    }
}

개방-폐쇄 원칙 (OCP)

Open/Closed Principle

 

코드는 확장에는 열려(Open) 있고, 변경에는 닫혀(Closed) 있어야 함

즉, 새로운 기능을 추가할 때 기존 코드를 수정하지 않아야 함

 

OCP 위반

class PaymentService {
    void pay(String paymentType) {
        if (paymentType.equals("CreditCard")) {
            // 신용카드 결제 로직
        } else if (paymentType.equals("PayPal")) {
            // PayPal 결제 로직
        }
    }
}

 

➡︎ 새로운 결제 방식이 추가될 때마다 기존 코드를 수정해야 함

 

OCP 적용

interface Payment {
    void pay();
}

class CreditCardPayment implements Payment {
    public void pay() {
        // 신용카드 결제 로직
    }
}

class PayPalPayment implements Payment {
    public void pay() {
        // PayPal 결제 로직
    }
}

class PaymentService {
    void processPayment(Payment payment) {
        payment.pay();
    }
}

 

➡︎ 다형성을 활용해서 기존 코드를 수정하지 않고, 새로운 결제 방식 추가 가능!


리스코프 치환 원칙(LSP)

Liskov Substitution Principle

 

자식 클래스는 부모 클래스를 대체할 수 있어야 함

즉, 부모 클래스를 상속받은 모든 클래스는  부모의 역할을 온전히 수행할 수 있어야 함

 

LSP 위반

class Rectangle {
    int width, height;

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

class Square extends Rectangle {
    void setWidth(int width) { 
        this.width = width;
        this.height = width; // 가로와 세로를 동일하게 설정 (LSP 위반)
    }
}

 

➡︎ Square 클래스는 Rectangle 을 대체할 수 없음

 

LSP 적용

interface Shape {
    int getArea();
}

class Rectangle implements Shape {
    int width, height;
    public int getArea() { return width * height; }
}

class Square implements Shape {
    int side;
    public int getArea() { return side * side; }
}

 

➡︎ 공통 인터페이스를 활용

 


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

Interface Segregation Principle

 

하나의 큰 인터페이스보다, 여러 개의 작은 인터페이스로 분리하는 게 좋음

즉, 사용하지 않는 메서드에 의존하면 안됨

 

ISP 위반

interface Worker {
    void work();
    void eat();
}

class Robot implements Worker {
    public void work() { System.out.println("일을 합니다."); }
    public void eat() { throw new UnsupportedOperationException(); } // 로봇은 먹을 수 없음 (ISP 위반)
}

 

➡︎ Roboteat() 메서드를 가질 필요가 없음

 

ISP 적용

interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

class Robot implements Workable {
    public void work() { System.out.println("일을 합니다."); }
}

 

➡︎ 인터페이스를 분리하여 불필요한 의존성 제거


의존성 역전 원칙(DIP)

Dependency Inversion Principle

 

상위 모듈이 하위 모듈에 의존하면 안 됨

즉, 세부 구현이 아니라 추상화(Interface)에 의존해야 함

 

DIP 위반

class MySQLDatabase {
    void connect() { System.out.println("MySQL 연결"); }
}

class DataManager {
    MySQLDatabase database = new MySQLDatabase(); // 특정 DB에 강하게 결합 (DIP 위반)
}

 

➡︎ DB 를 변경하려면 DataManager 코드를 수정해야 함

 

DIP 적용

interface Database {  // 추상화 적용
    void connect();
}

class MySQLDatabase implements Database {
    public void connect() { System.out.println("MySQL 연결"); }
}

class PostgreSQLDatabase implements Database {
    public void connect() { System.out.println("PostgreSQL 연결"); }
}

class DataManager {
    Database database;

    DataManager(Database database) { // 의존성 주입 (DIP 적용)
        this.database = database;
    }
}

 

➡︎ 추상화 적용 ➔ 인터페이스를 활용하여 유연한 설계 가능


추상화

추상화(Abstraction) 란?

객체의 핵심점인 특징만 노출하고, 불필요한 세부 사항은 숨기는 개념

인터페이스추상 클래스로 구현 가능

코드의 복잡도/결합도를 낮추고, 확장성을 높임

 

비교 항목 인터페이스 추상 클래스
목적 행동 정의 기본 기능 제공
메서드 구현 전부 구현 X (default 메서드 제외) 일부 구현 가능
다중 상속 가능 - implements 불가능 - extends
사용 예시 다양한 객체가 동일한 동작을 할 때 공통 기능을 제공하면서 일부만 구현이 필요할 때

 

* 자세한 내용은 아래 글의 인터페이스 vs 추상 클래스 내용 참고

 

상속(Inheritance)과 다형성(Polymorphism)

상속상속(Inheritance) 이란?기존 클래스를 확장하여 새로운 클래스를 만드는 개념코드 재사용성 증가 및 객체 간 계층 구조 형성 상속의 기본 구조// 부모 클래스 (Super Class)class Animal { String name; voi

jelliclesu.tistory.com


주요 포인트

  • SOLID 원칙은 단순 암기가 아니라 어떻게 적용되는 지 알아야 함
    • SRP: 하나의 클래스는 하나의 책임만 가져야 함
    • OCP: 코드는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 함
    • LSP: 자식 클래스는 부모 클래스를 대체할 수 있어야 함
    • ISP: 하나의 큰 인터페이스보다, 여러 개의 작은 인터페이스로 분리하는 게 좋음
    • DIP: 상위 모듈이 하위 모듈에 의존하면 안 됨
  • 추상화가 왜 필요한지, 인터페이스와 추상 클래스의 차이는 무엇인지 명확하게 알아야 함
    • 추상화: 객체의 핵심적인 특징만 노출하고, 불필요한 세부 사항은 숨김
    • 장점: 코드의 결합도는 낮아지고, 유지보수성 향상
  • SOLID 원칙과 추상화를 연계하여 유연한 설계 방법을 설명할 줄 알아야 함

+ Recent posts