CS

SOLID 원칙

baek-dev 2025. 4. 4. 19:11

📌 SOLID 원칙이란?

 

SOLID 원칙객체 지향 프로그래밍(OOP)의 5가지 설계 원칙으로,

소프트웨어를 유지보수하기 쉽게 설계하는 방법을 제시함.

 

SOLID 원칙을 따를 경우:

코드의 가독성재사용성이 높아짐.

변경 사항이 최소한의 영향만 주도록 설계할 수 있음.

유지보수가 쉬운 소프트웨어를 만들 수 있음.

 

SOLID는 다음 5가지 원칙의 약어:

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

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

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

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

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

 


1️⃣ SRP (단일 책임 원칙)

 

“하나의 클래스는 하나의 책임(기능)만 가져야 한다.”

“하나의 클래스가 변경되는 이유는 단 하나뿐이어야 한다.”

 

잘못된 예제 (SRP 위반)

public class UserManager {
    public void createUser(String name) {
        // 사용자 생성 로직
    }

    public void sendEmail(String email, String message) {
        // 이메일 전송 로직 (SRP 위반)
    }
}

문제점:

UserManager사용자 관리뿐만 아니라 이메일 전송 기능까지 담당함.

sendEmail()이 변경되면 UserManager도 영향을 받음.

 

SRP를 적용한 코드

public class UserManager {
    public void createUser(String name) {
        // 사용자 생성 로직
    }
}

public class EmailService {
    public void sendEmail(String email, String message) {
        // 이메일 전송 로직
    }
}

👉 해결:

UserManager사용자 생성 역할만 수행.

EmailService이메일 전송을 담당.

서로 다른 책임을 분리하여 변경이 한 부분에만 영향을 미침.

 


2️⃣ OCP (개방-폐쇄 원칙)

 

“클래스는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.”

“새 기능을 추가할 때 기존 코드를 변경하지 않아야 한다.”

 

잘못된 예제 (OCP 위반)

public class PaymentProcessor {
    public void processPayment(String type) {
        if (type.equals("CreditCard")) {
            System.out.println("Processing credit card payment");
        } else if (type.equals("PayPal")) {
            System.out.println("Processing PayPal payment");
        }
    }
}

문제점:

새로운 결제 방식(예: Apple Pay)을 추가할 때 processPayment()를 수정해야 함 → OCP 위반.

 

OCP를 적용한 코드 (인터페이스 활용)

public interface PaymentMethod {
    void pay();
}

public class CreditCardPayment implements PaymentMethod {
    public void pay() {
        System.out.println("Processing credit card payment");
    }
}

public class PayPalPayment implements PaymentMethod {
    public void pay() {
        System.out.println("Processing PayPal payment");
    }
}

public class PaymentProcessor {
    public void processPayment(PaymentMethod method) {
        method.pay();
    }
}

새로운 결제 방식 추가 시(ApplePayPayment), 기존 PaymentProcessor 코드를 수정할 필요 없음.

 


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

 

“자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다.”

“상속받은 클래스가 부모 클래스의 기능을 깨뜨려서는 안 된다.”

 

잘못된 예제 (LSP 위반)

public class Rectangle {
    protected int width, height;

    public void setWidth(int width) { this.width = width; }
    public void setHeight(int height) { this.height = height; }
    public int getArea() { return width * height; }
}

public class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width;  // LSP 위반
    }
}

문제점:

Square 클래스는 Rectangle을 상속받았지만, setWidth()를 호출하면 height도 변경됨.

부모 클래스(Rectangle)의 동작을 깨뜨림LSP 위반.

 

LSP를 적용한 코드 (상속 대신 인터페이스 사용)

public interface Shape {
    int getArea();
}

public class Rectangle implements Shape {
    private int width, height;

    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public int getArea() {
        return width * height;
    }
}

public class Square implements Shape {
    private int side;

    public Square(int side) {
        this.side = side;
    }

    public int getArea() {
        return side * side;
    }
}

사각형과 정사각형을 개별 클래스로 분리하여 문제 해결.

부모 클래스의 동작을 변경하지 않도록 설계.

 


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

 

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

“인터페이스를 여러 개로 나누어야 한다.”

 

잘못된 예제 (ISP 위반)

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

public class Robot implements Worker {
    public void work() { System.out.println("로봇이 일함"); }
    public void eat() { throw new UnsupportedOperationException("로봇은 밥을 먹지 않음"); }
}

문제점:

Robot 클래스는 eat() 메서드를 구현할 필요 없음.

필요하지 않은 메서드를 강제로 구현해야 함 → ISP 위반.

 

ISP를 적용한 코드 (인터페이스 분리)

public interface Workable {
    void work();
}

public interface Eatable {
    void eat();
}

public class Robot implements Workable {
    public void work() { System.out.println("로봇이 일함"); }
}

public class Human implements Workable, Eatable {
    public void work() { System.out.println("사람이 일함"); }
    public void eat() { System.out.println("사람이 밥을 먹음"); }
}

인터페이스를 Workable, Eatable로 분리하여 문제 해결.

각 클래스는 필요한 기능만 구현하도록 개선됨.

 


5️⃣ DIP (의존 역전 원칙)

 

“상위(추상) 클래스에 의존하고, 하위(구현) 클래스에 의존하지 않아야 한다.”

“의존성을 낮추고 확장성을 높이기 위해 인터페이스를 활용해야 한다.”

 

잘못된 예제 (DIP 위반)

public class FileLogger {
    public void log(String message) {
        System.out.println("File log: " + message);
    }
}

public class UserService {
    private FileLogger logger = new FileLogger();

    public void registerUser(String username) {
        logger.log("User registered: " + username);
    }
}

문제점:

UserServiceFileLogger에 강하게 의존.

새로운 로깅 방식(예: DatabaseLogger)을 추가할 때 UserService 코드를 수정해야 함.

 

DIP를 적용한 코드 (인터페이스 활용)

public interface Logger {
    void log(String message);
}

public class FileLogger implements Logger {
    public void log(String message) { System.out.println("File log: " + message); }
}

public class UserService {
    private Logger logger;

    public UserService(Logger logger) {
        this.logger = logger;
    }

    public void registerUser(String username) {
        logger.log("User registered: " + username);
    }
}

UserService는 Logger 인터페이스에 의존유연한 구조로 변경됨.

 


📌 정리

 

SOLID 원칙을 따르면 코드 유지보수성이 높아지고, 확장하기 쉬운 구조가 됨.

원칙 설명
SRP 하나의 클래스는 하나의 책임만 가져야 함
OCP 기존 코드를 변경하지 않고 확장 가능해야 함
LSP 하위 클래스는 상위 클래스를 대체할 수 있어야 함
ISP 인터페이스를 여러 개로 분리해야 함
DIP 구체적인 클래스가 아닌 인터페이스에 의존해야 함

 

'CS' 카테고리의 다른 글

퍼사드 패턴  (0) 2025.04.14
DSL  (0) 2025.03.25
JIT(Just-In-Time) 컴파일러  (0) 2025.03.05
트러블 슈팅  (0) 2025.02.28
SSE (Server-Sent Events)  (0) 2025.02.19