SOLID 원칙

허성재's avatar
Aug 19, 2024
SOLID 원칙
SOLID 원칙은 객체 지향 설계의 다섯 가지 기본 원칙을 나타내며, 소프트웨어 설계에서 유지보수성과 확장성을 개선하는 데 중점을 둡니다. 각 원칙은 객체 지향 프로그래밍에서 코드의 품질을 높이기 위해 고안되었습니다. 아래는 SOLID 원칙을 Java 코드 예제와 함께 설명한 내용입니다.

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

원칙: 하나의 클래스는 하나의 책임만 가져야 하며, 그 책임은 변경의 이유가 되어야 합니다.
예시:
// 잘못된 예시: Book 클래스가 데이터를 저장하고 출력까지 책임지고 있음. class Book { private String title; private String author; // 저장 책임 public void save() { // 데이터베이스에 책 정보를 저장하는 코드 } // 출력 책임 public void print() { System.out.println(title + " by " + author); } } // 개선된 예시 class Book { private String title; private String author; public String getTitle() { return title; } public String getAuthor() { return author; } } // 별도의 클래스가 출력 책임을 담당 class BookPrinter { public void print(Book book) { System.out.println(book.getTitle() + " by " + book.getAuthor()); } }

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

원칙: 소프트웨어 엔터티(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다.
예시:
// 잘못된 예시: 기존 클래스의 코드를 수정해야만 새로운 기능을 추가할 수 있음. class Rectangle { public double length; public double width; } class AreaCalculator { public double calculateRectangleArea(Rectangle rectangle) { return rectangle.length * rectangle.width; } } // 개선된 예시: 새로운 도형이 추가되어도 기존 코드를 수정할 필요가 없음. interface Shape { double area(); } class Rectangle implements Shape { private double length; private double width; public Rectangle(double length, double width) { this.length = length; this.width = width; } @Override public double area() { return length * width; } } class Circle implements Shape { private double radius; public Circle(double radius) { this.radius = radius; } @Override public double area() { return Math.PI * radius * radius; } } class AreaCalculator { public double calculateArea(Shape shape) { return shape.area(); } }

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

원칙: 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서, 자식 클래스 객체로 치환할 수 있어야 한다.
예시:
// 잘못된 예시: Square가 Rectangle을 상속받았지만, Rectangle의 행동 규약을 깨뜨림. 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) { this.width = width; this.height = width; // 높이와 너비가 같아야 함 } @Override public void setHeight(int height) { this.width = height; this.height = height; } } // Square는 Rectangle을 대체할 수 없으므로 LSP를 위반함
해결책: 상속 구조를 재고하거나 상속 대신 별도의 클래스로 구현합니다.

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

원칙: 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다. 인터페이스는 작고 구체적인 단위로 분리되어야 한다.
예시:
// 잘못된 예시: Interface가 너무 많아 모든 메서드를 구현할 필요가 없음. interface Worker { void work(); void eat(); } class Developer implements Worker { @Override public void work() { // 코딩 작업 } @Override public void eat() { // 개발자도 먹기는 하지만, 이 메서드는 무의미할 수 있음 } } // 개선된 예시: 인터페이스를 분리함으로써 필요한 부분만 구현하도록 함. interface Workable { void work(); } interface Eatable { void eat(); } class Developer implements Workable { @Override public void work() { // 코딩 작업 } } class OfficeWorker implements Workable, Eatable { @Override public void work() { // 사무 작업 } @Override public void eat() { // 식사 } }

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

원칙: 고수준 모듈은 저수준 모듈에 의존해서는 안 된다. 둘 다 추상화에 의존해야 한다. 또한, 추상화는 구체적인 것에 의존해서는 안 된다. 구체적인 것이 추상화에 의존해야 한다.
예시:
// 잘못된 예시: High-level 모듈이 Low-level 모듈에 의존하고 있음. class LightBulb { public void turnOn() { System.out.println("LightBulb: Bulb turned on..."); } public void turnOff() { System.out.println("LightBulb: Bulb turned off..."); } } class Switch { private LightBulb lightBulb; public Switch(LightBulb lightBulb) { this.lightBulb = lightBulb; } public void operate() { lightBulb.turnOn(); } } // 개선된 예시: 인터페이스를 사용하여 추상화에 의존하게 함. 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 Switch { private Switchable device; public Switch(Switchable device) { this.device = device; } public void operate() { device.turnOn(); } }

요약

  • SRP: 클래스는 하나의 책임만 가져야 한다.
  • OCP: 기존 코드를 수정하지 않고 기능을 확장할 수 있어야 한다.
  • LSP: 자식 클래스는 부모 클래스를 대체할 수 있어야 한다.
  • ISP: 클라이언트는 사용하지 않는 메서드에 의존하지 않아야 한다.
  • DIP: 고수준 모듈은 저수준 모듈에 의존해서는 안 된다.
이 원칙들을 준수하면 코드를 더 유연하고 유지보수하기 쉽게 만들 수 있습니다.
Share article

heo-gom