주의

  • 본 게시글은 Claude로 작성되었습니다. 잘못된 정보가 있을 수 있습니다.

개요

SOLID는 객체 지향 프로그래밍 및 설계의 다섯 가지 기본 원칙을 마이클 페더스가 소개한 약어입니다. 이 원칙들은 시스템을 이해하기 쉽고, 유연하며, 유지보수가 쉽도록 만드는 것을 목표로 합니다. SOLID 원칙을 따르면 더 나은 품질의 소프트웨어를 개발할 수 있습니다.

상세

SOLID는 다음 다섯 가지 원칙의 약어입니다:

  1. S - 단일 책임 원칙 (Single Responsibility Principle)
  2. O - 개방-폐쇄 원칙 (Open-Closed Principle)
  3. L - 리스코프 치환 원칙 (Liskov Substitution Principle)
  4. I - 인터페이스 분리 원칙 (Interface Segregation Principle)
  5. D - 의존관계 역전 원칙 (Dependency Inversion Principle)

각 원칙에 대해 자세히 살펴보겠습니다:

1. 단일 책임 원칙 (SRP)

클래스는 단 하나의 책임만 가져야 합니다. 즉, 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 합니다.

예제:

// SRP를 위반하는 예
class Employee {
    public void calculatePay() { /* ... */ }
    public void saveEmployee() { /* ... */ }
    public void generateReport() { /* ... */ }
}
 
// SRP를 준수하는 예
class Employee {
    private String name;
    private int id;
    // getter, setter 등
}
 
class PayCalculator {
    public void calculatePay(Employee employee) { /* ... */ }
}
 
class EmployeeRepository {
    public void save(Employee employee) { /* ... */ }
}
 
class ReportGenerator {
    public void generateReport(Employee employee) { /* ... */ }
}

2. 개방-폐쇄 원칙 (OCP)

소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있어야 하지만, 수정에 대해서는 닫혀 있어야 합니다.

예제:

// OCP를 위반하는 예
class Rectangle {
    public double width;
    public double height;
}
 
class AreaCalculator {
    public double calculateArea(Rectangle rectangle) {
        return rectangle.width * rectangle.height;
    }
}
 
// OCP를 준수하는 예
interface Shape {
    double calculateArea();
}
 
class Rectangle implements Shape {
    private double width;
    private double height;
    
    @Override
    public double calculateArea() {
        return width * height;
    }
}
 
class Circle implements Shape {
    private double radius;
    
    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}
 
class AreaCalculator {
    public double calculateArea(Shape shape) {
        return shape.calculateArea();
    }
}

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

서브타입은 언제나 기반 타입으로 교체할 수 있어야 합니다. 즉, 자식 클래스는 부모 클래스의 기능을 수행할 수 있어야 합니다.

예제:

// LSP를 위반하는 예
class Rectangle {
    protected int width;
    protected int height;
 
    public void setWidth(int width) {
        this.width = width;
    }
 
    public void setHeight(int height) {
        this.height = height;
    }
 
    public int getArea() {
        return width * height;
    }
}
 
class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        super.setWidth(width);
        super.setHeight(width);
    }
 
    @Override
    public void setHeight(int height) {
        super.setHeight(height);
        super.setWidth(height);
    }
}
 
// LSP를 준수하는 예
interface Shape {
    int getArea();
}
 
class Rectangle implements Shape {
    protected int width;
    protected int height;
 
    public void setWidth(int width) {
        this.width = width;
    }
 
    public void setHeight(int height) {
        this.height = height;
    }
 
    @Override
    public int getArea() {
        return width * height;
    }
}
 
class Square implements Shape {
    private int side;
 
    public void setSide(int side) {
        this.side = side;
    }
 
    @Override
    public int getArea() {
        return side * side;
    }
}

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

클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 합니다. 큰 인터페이스를 더 작고 구체적인 여러 개의 인터페이스로 분리해야 합니다.

예제:

// ISP를 위반하는 예
interface Worker {
    void work();
    void eat();
    void sleep();
}
 
class Human implements Worker {
    public void work() { /* ... */ }
    public void eat() { /* ... */ }
    public void sleep() { /* ... */ }
}
 
class Robot implements Worker {
    public void work() { /* ... */ }
    public void eat() { /* 로봇은 먹지 않음 */ }
    public void sleep() { /* 로봇은 자지 않음 */ }
}
 
// ISP를 준수하는 예
interface Workable {
    void work();
}
 
interface Eatable {
    void eat();
}
 
interface Sleepable {
    void sleep();
}
 
class Human implements Workable, Eatable, Sleepable {
    public void work() { /* ... */ }
    public void eat() { /* ... */ }
    public void sleep() { /* ... */ }
}
 
class Robot implements Workable {
    public void work() { /* ... */ }
}

5. 의존관계 역전 원칙 (DIP)

고수준 모듈은 저수준 모듈에 의존해서는 안 됩니다. 둘 다 추상화에 의존해야 합니다. 또한, 추상화는 세부 사항에 의존해서는 안 됩니다. 세부사항이 추상화에 의존해야 합니다.

예제:

// DIP를 위반하는 예
class LightBulb {
    public void turnOn() {
        System.out.println("LightBulb: Bulb turned on...");
    }
    
    public void turnOff() {
        System.out.println("LightBulb: Bulb turned off...");
    }
}
 
class ElectricPowerSwitch {
    private LightBulb bulb;
    private boolean on;
    
    public ElectricPowerSwitch(LightBulb bulb) {
        this.bulb = bulb;
        this.on = false;
    }
    
    public boolean isOn() {
        return this.on;
    }
    
    public void press() {
        if (this.on) {
            bulb.turnOff();
            this.on = false;
        } else {
            bulb.turnOn();
            this.on = true;
        }
    }
}
 
// DIP를 준수하는 예
interface Switchable {
    void turnOn();
    void turnOff();
}
 
class LightBulb implements Switchable {
    @Override
    public void turnOn() {
        System.out.println("LightBulb: Bulb turned on...");
    }
    
    @Override
    public void turnOff() {
        System.out.println("LightBulb: Bulb turned off...");
    }
}
 
class ElectricPowerSwitch {
    private Switchable device;
    private boolean on;
    
    public ElectricPowerSwitch(Switchable device) {
        this.device = device;
        this.on = false;
    }
    
    public boolean isOn() {
        return this.on;
    }
    
    public void press() {
        if (this.on) {
            device.turnOff();
            this.on = false;
        } else {
            device.turnOn();
            this.on = true;
        }
    }
}

예상 면접 질문/답변

SOLID 원칙이 무엇이며 왜 중요한가요?

SOLID는 객체 지향 프로그래밍의 5가지 기본 원칙을 나타내는 약어입니다. 단일 책임, 개방-폐쇄, 리스코프 치환, 인터페이스 분리, 의존관계 역전 원칙을 의미합니다. 이 원칙들은 코드를 더 유지보수하기 쉽고, 유연하며, 확장 가능하게 만들어 소프트웨어의 품질을 향상시키는 데 중요합니다.

단일 책임 원칙(SRP)을 설명하고 예를 들어주세요.

단일 책임 원칙은 클래스가 하나의 책임만 가져야 한다는 원칙입니다. 예를 들어, 직원 클래스가 급여 계산, 데이터베이스 저장, 보고서 생성 등 여러 책임을 가지는 대신, 이를 각각의 클래스로 분리하여 단일 책임을 갖도록 하는 것입니다.

개방-폐쇄 원칙(OCP)을 어떻게 적용할 수 있나요?

개방-폐쇄 원칙은 확장에는 열려있고 수정에는 닫혀있어야 한다는 원칙입니다. 이를 적용하려면 인터페이스나 추상 클래스를 사용하여 기능을 추상화하고, 새로운 기능을 추가할 때 기존 코드를 수정하지 않고 새로운 클래스를 만들어 확장할 수 있도록 설계해야 합니다.

리스코프 치환 원칙(LSP)을 위반하는 흔한 예는 무엇인가요?

리스코프 치환 원칙을 위반하는 흔한 예는 정사각형-직사각형 문제입니다. 직사각형 클래스를 상속받아 정사각형 클래스를 만들 경우, 정사각형의 너비와 높이는 항상 같아야 하므로 직사각형의 동작을 제대로 수행할 수 없게 됩니다. 이는 LSP를 위반하는 예입니다.

의존관계 역전 원칙(DIP)을 적용하면 어떤 이점이 있나요?

의존관계 역전 원칙을 적용하면 고수준 모듈이 저수준 모듈에 직접 의존하지 않고 추상화에 의존하게 됩니다. 이로 인해 코드의 결합도가 낮아지고 유연성이 증가합니다. 또한 테스트가 용이해지고, 시스템의 일부를 쉽게 교체하거나 확장할 수 있게 됩니다.

스스로 찾아보면 좋은 연관 주제

  1. 디자인 패턴과 SOLID 원칙의 관계
  2. SOLID 원칙을 적용한 리팩토링 기법
  3. SOLID 원칙과 애자일 개발 방법론의 연관성
  4. SOLID 원칙을 준수하는 코드와 그렇지 않은 코드의 성능 비교
  5. 다양한 프로그래밍 언어에서의 SOLID 원칙 적용 방법