SOLID 원칙

정윤서·2024년 1월 26일
0
post-custom-banner

SOLID 원칙이란?

SOLID 원칙은 객체 지향 프로그래밍 및 소프트웨어 설계에서의 다섯 가지 기본 원칙을 나타낸다. 이 원칙들은 소프트웨어를 더 이해하기 쉽고, 유연하며, 유지 보수가 용이하게 설계하는데 도움을 준다.

프로그래밍을 할 때 좋은 코드를 짜기 위한 기본적인 규칙!

SOLID는 다음과 같은 원칙들의 첫 글자를 따서 만든 약어이다.

1. S: 단일 책임 원칙 (Single Responsibility Principle)

  • 하나의 클래스는 하나의 책임만을 가져야 한다.
  • 클래스가 변경되는 이유는 단 하나여야한다. 즉, 클래스는 하나의 기능만을 수행하는데 집중해야 한다.

    하나의 클래스는 하나의 일만 해야한다!
    먄약 '책' 클래스가 있다면 책에 관련된 일만 해야한다. 인쇄나 판매 같은 다른 일은 할 수 없다.

// 좋은 예: 클래스가 하나의 책임만 가짐
public class UserSettings {
    public void changeEmail(User user) {
        // 이메일 변경 로직
    }
}

// 나쁜 예: 클래스가 여러 책임을 가짐
public class User {
    public void changeEmail(User user) {
        // 이메일 변경 로직
    }

    public void saveUser(User user) {
        // 유저 저장 로직
    }
}

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

  • 소프트웨어의 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있어야 하지만, 변경에 대해서는 닫혀 있어야 한다.
  • 기존 코드를 변경하지 않으면서 기능을 추가할 수 있어야 한다. -> 인터페이스를 사용하여 확장성을 높일 수 있음.

    코드를 새로운 기능으로 확장할 수 있어야 하지만 기존 코드를 바꾸지는 않아야 한다!
    '도형' 클래스가 있고 '원'이나 '사각형'을 추가하고 싶을 때, '도형' 클래스를 바꾸지 않고도 새로운 도형을 추가할 수 있어야 한다.

public abstract class Shape {
    public abstract double area();
}

public class Circle extends Shape {
    private double radius;

    public double area() {
        return Math.PI * radius * radius;
    }
}

public class AreaCalculator {
    public double calculateArea(Shape[] shapes) {
        double area = 0;
        for (Shape shape : shapes) {
            area += shape.area();
        }
        return area;
    }
}

3. L: 리스코프 치환 원칙 (Liskov Substitution Principle)

  • 서브 클래스는 언제나 그것의 베이스 클래스로 교체될 수 있어야 한다.
  • 서브 클래스는 베이스 클래스의 역할을 완전히 대체할 수 있어야 한다. 즉, 베이스 클래스의 인스턴스 대신 서브 클래스의 인스턴스를 사용해도 프로그램의 기능에 문제가 없어야한다.

    부모 클래스 대신 자식 클래스를 사용해도 문제가 없어야 한다!
    '새'클래스가 있고 '참새'클래스가 '새'의 자식 클래스라면, '새'를 사용한느 모든 곳에서 '참새'를 사용해도 잘 작동해야 한다.

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 {
    public void setWidth(int width) {
        this.width = this.height = width;
    }

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

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

  • 클라이언트는 자신이 사용하지 않는 메소드에 의존하면 안된다.
  • 한 개의 범용 인터페이스보다는 특정 클라이언트에 맞춘 여러 개의 구체적인 인터페이스가 더 좋다는 것을 의미.

    너무 많은 기능을 하나의 인터페이스에 넣지 말아야 한다!
    프린터와 스캐너 기능을 모두 가진 '복합기' 인터페이스 대신, '프린터' 인터페이스와 '스캐너' 인터페이스를 따로 만들어야 한다.

// 좋은 예: 특정 클라이언트에 맞춘 인터페이스
public interface Printer {
    void print();
}

public interface Scanner {
    void scan();
}

// 나쁜 예: 여러 기능을 하나의 인터페이스에
public interface Machine {
    void print();
    void fax();
    void scan();
}

5. D: 의존관계 역전 원칙 (Dependency Inversion Principle)

  • 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다.
  • 추상화는 세부사항에 의존해서는 안되고, 세부사항은 추상화에 의존해야한다.
  • 구체적인 클래스보다는 인터페이스나 추상 클래스에 의존해야 한다는 것을 의미 -> 모듈 간의 결합도를 낮추고, 유연성 및 재사용성을 높일 수 있음.

    고수준의 모듈은 저수준의 모듈에 직접 의존하지 않아야 한다!
    대신에 둘 다 추상화(인터페이스나 추상 클래스)에 의존해야한다. 예를 들어, '데이터베이스' 클래스 대신 '데이터 저장 인터페이스'를 사용해야 한다.

// 좋은 예: 추상화에 의존
public interface Database {
    void save(Object data);
}

public class MySQLDatabase implements Database {
    public void save(Object data) {
        // MySQL 저장 로직
    }
}

public class UserService {
    private Database database;

    public UserService(Database database) {
        this.database = database;
    }

    public void saveUser(User user) {
        database.save(user);
    }
}

이 원칙들은 코드를 더 깔끔하고, 유지보수하기 쉽고, 확장하기 쉬운 방향으로 가이드해준다. 프로그래밍을 할 때 이 원칙들을 염두에 두면 좋은 코드를 작성하는 데 도움이 된다.

post-custom-banner

0개의 댓글