새발블로그

[JAVA] SOLID 원칙 본문

Language/Java

[JAVA] SOLID 원칙

EUG 2025. 5. 4. 21:03

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

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

  • 설명: 클래스는 하나의 작업만 수행해야 하며, 이로 인해 클래스가 변경되는 이유도 하나여야 한다.
  • 장점: 유지보수성이 높아지고, 각 클래스가 독립적으로 변경될 수 있다.
// SRP 준수 (좋은 예시)
public class UserService {
    public void registerUser(String username, String password) { /* 회원가입 로직 */ }
}

public class EmailService {
    public void sendWelcomeEmail(String email) { /* 이메일 전송 로직 */ }
}

 

UserService와 EmailService가 각각 독립적인 책임을 가지고 있어, 하나의 클래스가 여러 책임을 갖지 않도록 분리되었다.

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

확장에는 열려 있고, 수정에는 닫혀 있어야 한다.

  • 설명: 기존 코드를 수정하지 않고, 기능을 확장할 수 있어야 한다. 새로운 기능은 추가할 수 있지만, 기존의 코드를 변경해서는 안 된다.
  • 장점: 시스템의 변경이 최소화되고, 기존 코드의 안정성을 보장할 수 있다.
// OCP 준수 (좋은 예시)
public interface Shape {
    void draw();
}

public class Circle implements Shape {
    public void draw() {
        System.out.println("Drawing Circle");
    }
}

public class Square implements Shape {
    public void draw() {
        System.out.println("Drawing Square");
    }
}

public class ShapePrinter {
    public void printShape(Shape shape) {
        shape.draw();
    }
}

 

 

도형을 추가할 때 기존 코드를 수정하지 않고, Shape 인터페이스를 확장한 새로운 도형 클래스를 추가하는 방식으로 기능을 확장하였다.

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

부모 클래스를 사용하는 곳에 자식 클래스를 넣어도 문제가 없어야 한다.

  • 설명: 자식 클래스는 부모 클래스의 기능을 그대로 사용할 수 있어야 하며, 부모 클래스의 인스턴스가 자식 클래스의 인스턴스로 교체되어도 오류가 발생하지 않아야 한다.
  • 장점: 코드의 유연성과 확장성을 보장한다.
public class Bird {
    public void fly() {
        System.out.println("Flying...");
    }
}

public class Sparrow extends Bird {
    @Override
    public void fly() {
        System.out.println("Sparrow flying...");
    }
}
 

날 수 없는 새인 Penguin을 Bird로 구현한 경우, 문제가 된다. Penguin이 Bird를 상속받으면 fly() 메서드를 호출할 때 예외가 발생합니다. 이는 LSP 위반입니다.

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

클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안 된다.

  • 설명: 인터페이스는 클라이언트가 필요로 하는 기능만 제공해야 하며, 불필요한 메서드를 포함해서는 안 된다. 즉, 하나의 커다란 인터페이스를 여러 개의 작은 인터페이스로 나누어야 한다.
  • 장점: 인터페이스가 클라이언트의 요구에 맞게 구체화되므로, 불필요한 의존성을 피할 수 있다.
// ISP 준수 (좋은 예시)
public interface Printer {
    void print();
}

public interface Scanner {
    void scan();
}

public class SimplePrinter implements Printer {
    public void print() {
        System.out.println("Printing...");
    }
}
 
 
 

여기서는 프린터 기능과 스캔 기능을 각각 별도의 인터페이스로 분리하여 필요한 기능만을 구현하게 한다.

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

고수준 모듈이 저수준 모듈에 의존하면 안 된다.

  • 설명: 고수준 모듈은 저수준 모듈에 의존하는 것이 아니라, 추상화된 인터페이스에 의존해야 한다. 이렇게 하면 코드의 의존성을 낮출 수 있다.
  • 장점: 유연성과 확장성이 향상된다.
// DIP 준수 (좋은 예시)
public interface Switchable {
    void turnOn();
    void turnOff();
}

public class LightBulb implements Switchable {
    public void turnOn() { System.out.println("Bulb on"); }
    public void turnOff() { System.out.println("Bulb off"); }
}

public class Fan implements Switchable {
    public void turnOn() { System.out.println("Fan on"); }
    public void turnOff() { System.out.println("Fan off"); }
}

public class Switch {
    private Switchable device;

    public Switch(Switchable device) {
        this.device = device;
    }

    public void operate() {
        device.turnOn();
    }
}

 

Switch 클래스는 구체적인 장치 클래스(LightBulb 또는 Fan)가 아니라 Switchable 인터페이스에 의존함으로써, 장치가 추가되거나 변경되더라도 Switch 클래스의 수정 없이 새로운 장치를 쉽게 추가할 수 있다.

'Language > Java' 카테고리의 다른 글

[JAVA] 멀티스레드  (1) 2025.05.04
[JAVA] 상속  (0) 2025.05.04
[JAVA] 클래스  (0) 2025.05.04
[JAVA] 변수 위치 및 메모리 구조  (0) 2025.05.04
[JAVA] JVM  (0) 2025.05.04