CS 지식

SOLID 원칙

이제하네 2024. 9. 1. 22:01

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

1.1 SRP 개념

단일 책임 원칙은 클래스는 단 하나의 책임만 가져야 한다는 원칙입니다. 즉, 클래스는 하나의 기능만 담당해야 하며, 그 기능을 변경해야 할 이유도 하나뿐이어야 합니다. 이 원칙을 따르면 코드의 가독성이 향상되고, 유지보수가 쉬워집니다.

1.2 잘못된 예제

public class User {
    private String name;
    private String email;

    // 사용자 정보 저장
    public void saveUser() {
        // 사용자 정보를 데이터베이스에 저장하는 코드
    }

    // 이메일 전송
    public void sendEmail(String message) {
        // 이메일을 전송하는 코드
    }
}

 

위 예제에서 User 클래스는 사용자 정보 관리뿐만 아니라 이메일 전송 기능도 포함하고 있습니다. 이는 단일 책임 원칙을 위반한 것입니다.

1.3 SRP를 준수한 예제

public class User {
    private String name;
    private String email;

    // 사용자 정보만 관리
}

public class EmailService {
    // 이메일 전송 기능만 담당
    public void sendEmail(User user, String message) {
        // 이메일을 전송하는 코드
    }
}
 
이제 User 클래스는 사용자 정보만 관리하고, EmailService 클래스는 이메일 전송 기능만 담당합니다. 이렇게 하면 각 클래스가 단일 책임만 가지게 되어 코드의 변경이 용이해집니다.

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

2.1 OCP 개념

개방-폐쇄 원칙은 소프트웨어 요소(클래스, 모듈 등)가 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다는 원칙입니다. 즉, 기존 코드를 수정하지 않고도 기능을 확장할 수 있도록 설계해야 합니다.

2.2 잘못된 예제

public class Rectangle {
    public double width;
    public double height;
}

public class AreaCalculator {
    public double calculateRectangleArea(Rectangle rectangle) {
        return rectangle.width * rectangle.height;
    }
}
 
이 코드에서 AreaCalculator는 직사각형의 면적만 계산할 수 있습니다. 만약 다른 도형의 면적을 계산하려면 AreaCalculator 클래스를 수정해야 합니다.

2.3 OCP를 준수한 예제 

public interface Shape {
    double calculateArea();
}

public class Rectangle implements Shape {
    public double width;
    public double height;

    @Override
    public double calculateArea() {
        return width * height;
    }
}

public class Circle implements Shape {
    public double radius;

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

public class AreaCalculator {
    public double calculateArea(Shape shape) {
        return shape.calculateArea();
    }
}

 

이제 Shape 인터페이스를 구현한 새로운 도형 클래스만 추가하면 AreaCalculator를 수정하지 않고도 다양한 도형의 면적을 계산할 수 있습니다.

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

3.1 LSP 개념

리스코프 치환 원칙은 서브타입은 언제나 자신의 기반 타입(base type)으로 교체할 수 있어야 한다는 원칙입니다. 이 원칙을 따르면 서브클래스는 부모 클래스의 계약을 준수해야 하며, 서브클래스를 기반 클래스와 교체하더라도 프로그램이 정상적으로 작동해야 합니다.

3.2 잘못된 예제

public class Bird {
    public void fly() {
        System.out.println("날 수 있습니다.");
    }
}

public class Ostrich extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("타조는 날 수 없습니다.");
    }
}

Ostrich 클래스는 Bird 클래스를 상속받았지만, 타조는 날 수 없기 때문에 fly 메서드를 사용할 수 없습니다. 이 경우 LSP를 위반하게 됩니다.

3.3 LSP를 준수한 예제

public class Bird {
    public void move() {
        System.out.println("움직일 수 있습니다.");
    }
}

public class FlyingBird extends Bird {
    public void fly() {
        System.out.println("날 수 있습니다.");
    }
}

public class Ostrich extends Bird {
    @Override
    public void move() {
        System.out.println("타조는 달릴 수 있습니다.");
    }
}

이제 Bird 클래스는 모든 새들이 공통적으로 가지는 move 메서드를 포함하고, FlyingBird는 날 수 있는 새들을 위한 클래스로 분리되었습니다. 이로써 LSP를 준수하게 됩니다.

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

4.1 ISP 개념

인터페이스 분리 원칙은 클라이언트가 자신이 사용하지 않는 메서드에 의존하지 않도록, 인터페이스를 가능한 한 작은 단위로 분리해야 한다는 원칙입니다.

4.2 잘못된 예제

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

public class Developer implements Worker {
    @Override
    public void work() {
        System.out.println("개발 작업을 합니다.");
    }

    @Override
    public void eat() {
        System.out.println("점심을 먹습니다.");
    }
}

public class Robot implements Worker {
    @Override
    public void work() {
        System.out.println("로봇 작업을 합니다.");
    }

    @Override
    public void eat() {
        throw new UnsupportedOperationException("로봇은 먹을 수 없습니다.");
    }
}

 

여기서 Robot 클래스는 eat 메서드를 사용할 수 없기 때문에, 이 인터페이스는 로봇의 경우 불필요한 메서드를 포함하고 있습니다.

4.3 ISP를 준수한 예제

public interface Workable {
    void work();
}

public interface Eatable {
    void eat();
}

public class Developer implements Workable, Eatable {
    @Override
    public void work() {
        System.out.println("개발 작업을 합니다.");
    }

    @Override
    public void eat() {
        System.out.println("점심을 먹습니다.");
    }
}

public class Robot implements Workable {
    @Override
    public void work() {
        System.out.println("로봇 작업을 합니다.");
    }
}

 

이제 인터페이스를 Workable과 Eatable로 분리하여, Robot은 필요하지 않은 eat 메서드를 구현할 필요가 없어졌습니다. 이로써 ISP를 준수하게 됩니다.

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

5.1 DIP 개념

의존성 역전 원칙은 고수준 모듈(High-level modules)은 저수준 모듈(Low-level modules)에 의존해서는 안 되며, 둘 다 추상화된 인터페이스에 의존해야 한다는 원칙입니다. 즉, 구체적인 구현이 아닌 추상화에 의존하게 하여 유연한 설계를 가능하게 합니다.

5.2 잘못된 예제

public class Keyboard {
    // 키보드 관련 코드
}

public class Computer {
    private Keyboard keyboard;

    public Computer() {
        this.keyboard = new Keyboard();
    }
}

 

여기서 Computer 클래스는 Keyboard 클래스에 직접 의존합니다. 만약 다른 종류의 입력 장치를 사용해야 한다면 Computer 클래스를 수정해야 합니다.

5.3 DIP를 준수한 예제

public interface InputDevice {
    void input();
}

public class Keyboard implements InputDevice {
    @Override
    public void input() {
        System.out.println("키보드를 사용합니다.");
    }
}

public class Computer {
    private InputDevice inputDevice;

    public Computer(InputDevice inputDevice) {
        this.inputDevice = inputDevice;
    }

    public void useDevice() {
        inputDevice.input();
    }
}

 

이제 Computer 클래스는 InputDevice 인터페이스에 의존하며, Keyboard나 다른 종류의 입력 장치를 사용할 수 있게 되었습니다. 이로써 DIP를 준수하게 됩니다.

'CS 지식' 카테고리의 다른 글

예외처리  (2) 2024.09.08
동기 ,비동기 처리  (0) 2024.09.02
추상 클래스와 인터페이스  (0) 2024.08.25
오버로딩과 오버라이딩  (0) 2024.08.15
MVC 패턴  (0) 2024.08.11