객체지향 설계 원칙이란?
객체지향 프로그래밍(OOP, Object-Oriented Programming)은 유지보수성과 확장성이 뛰어난 소프트웨어 개발을 위해 널리 사용되는 패러다임입니다. 하지만 객체지향 방식으로 코드를 작성한다고 해서 무조건 좋은 코드가 되는 것은 아닙니다. 이를 보완하기 위해 SOLID 원칙이라는 설계 원칙이 등장했습니다.
SOLID 원칙은 소프트웨어 개발에서 코드의 결합도를 낮추고, 가독성과 유지보수성을 높이는 데 중점을 둡니다. 그렇다면 SOLID 원칙이란 무엇이며, 각각 어떤 의미를 가질까요? 자세히 살펴보겠습니다.
[동영상 강의]
SOLID 원칙이란?
SOLID 원칙은 로버트 C. 마틴(Robert C. Martin)이 정립한 객체지향 설계 원칙으로, 다섯 가지 원칙의 앞글자를 따서 만들어졌습니다.
1. SRP (Single Responsibility Principle) - 단일 책임 원칙
- 하나의 클래스는 하나의 책임(기능)만 가져야 한다.
- 클래스가 변경되는 이유는 단 하나여야 한다.
- 📌 예제
class UserManager {
public void saveUser(User user) {
// 사용자 정보 저장
}
}
class EmailService {
public void sendEmail(User user) {
// 이메일 전송 로직
}
}
✅ UserManager 클래스는 사용자 관리만 담당하고, 이메일 전송 기능은 EmailService에서 처리하도록 분리했습니다.
2. OCP (Open/Closed Principle) - 개방-폐쇄 원칙
- 기존 코드를 변경하지 않고 확장할 수 있도록 설계해야 한다.
- 새로운 기능 추가 시 기존 코드를 수정하지 않고도 구현이 가능해야 한다.
- 📌 예제:
interface Shape {
double calculateArea();
}
class Circle implements Shape {
private double radius;
public Circle(double radius) { this.radius = radius; }
public double calculateArea() { return Math.PI * radius * radius; }
}
class Rectangle implements Shape {
private double width, height;
public Rectangle(double width, double height) {
this.width = width; this.height = height;
}
public double calculateArea() { return width * height; }
}
✅ Shape 인터페이스를 활용하여 새로운 도형이 추가되어도 기존 코드 수정 없이 확장이 가능하도록 설계했습니다.
3. LSP (Liskov Substitution Principle) - 리스코프 치환 원칙
- 자식 클래스는 부모 클래스를 대체할 수 있어야 한다.
- 하위 클래스가 상위 클래스의 기능을 변경하면 안 된다.
- 📌 잘못된 예제:
class Bird {
public void fly() {
System.out.println("새가 날아갑니다.");
}
}
class Penguin extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("펭귄은 날 수 없습니다.");
}
}
❌ Penguin 클래스가 Bird 클래스를 상속했지만 fly()를 사용할 수 없기 때문에 잘못된 설계입니다.
4. ISP (Interface Segregation Principle) - 인터페이스 분리 원칙
- 클라이언트가 사용하지 않는 인터페이스에 의존하지 않도록 해야 한다.
- 하나의 큰 인터페이스보다는 여러 개의 작은 인터페이스로 분리하는 것이 좋다.
- 📌 예제:
interface Worker {
void work();
}
interface Eater {
void eat();
}
class Human implements Worker, Eater {
public void work() { System.out.println("사람이 일합니다."); }
public void eat() { System.out.println("사람이 밥을 먹습니다."); }
}
class Robot implements Worker {
public void work() { System.out.println("로봇이 작업합니다."); }
}
- ✅ Worker와 Eater 인터페이스를 분리함으로써, Robot 클래스가 불필요한 eat() 메서드를 구현하지 않아도 됩니다.
- interface Worker { void work(); } interface Eater { void eat(); } class Human implements Worker, Eater { public void work() { System.out.println("사람이 일합니다."); } public void eat() { System.out.println("사람이 밥을 먹습니다."); } } class Robot implements Worker { public void work() { System.out.println("로봇이 작업합니다."); } }
5. DIP (Dependency Inversion Principle) - 의존성 역전 원칙
- 상위 모듈(추상화)이 하위 모듈(구현)에 의존하는 것이 아니라, 반대로 하위 모듈이 상위 모듈에 의존해야 한다.
- 의존성을 줄이기 위해 인터페이스를 활용하는 것이 일반적이다.
- 📌 예제:
interface Database {
void save(String data);
}
class MySQLDatabase implements Database {
public void save(String data) {
System.out.println("MySQL에 데이터 저장: " + data);
}
}
class DatabaseService {
private Database database;
public DatabaseService(Database database) {
this.database = database;
}
public void saveData(String data) {
database.save(data);
}
}
✅ DatabaseService 클래스는 Database 인터페이스를 통해 MySQLDatabase 구현체를 사용하므로, 다른 데이터베이스로 쉽게 변경할 수 있습니다.
SOLID 원칙을 지켜야 하는 이유
SOLID 원칙을 따르면 다음과 같은 장점이 있습니다.
- ✅ 유지보수 용이: 코드 수정 시 영향 범위를 최소화할 수 있습니다.
- ✅ 확장성 향상: 새로운 기능을 추가하기 쉬워집니다.
- ✅ 결합도 감소: 모듈 간 의존성이 줄어들어 독립적인 개발이 가능합니다.
- ✅ 가독성 증가: 코드가 명확하고 논리적으로 정리됩니다.
마무리
객체지향 프로그래밍을 제대로 활용하기 위해서는 SOLID 원칙을 숙지하고 코드에 적용하는 것이 중요합니다. 이 원칙들을 지키면 유지보수하기 쉬운 구조를 만들 수 있으며, 협업과 확장에도 유리한 코드를 작성할 수 있습니다.
'정보처리기술사 > 03. SW공학' 카테고리의 다른 글
마이크로서비스 아키텍처(MSA)란? 쉽고 자세한 가이드 (1) | 2025.03.07 |
---|---|
객체지향 설계에서 캡슐화란? 쉽게 이해하는 개념 정리 (0) | 2025.03.03 |
회전 복잡도(Cyclomatic Complexity)와 할스테드 지표(Halstead Metrics) 설명 및 활용 분야 (2) | 2025.02.22 |