📌 SOLID 원칙이란?
SOLID 원칙은 객체 지향 프로그래밍(OOP)의 5가지 설계 원칙으로,
소프트웨어를 유지보수하기 쉽게 설계하는 방법을 제시함.
✅ SOLID 원칙을 따를 경우:
• 코드의 가독성과 재사용성이 높아짐.
• 변경 사항이 최소한의 영향만 주도록 설계할 수 있음.
• 유지보수가 쉬운 소프트웨어를 만들 수 있음.
✅ SOLID는 다음 5가지 원칙의 약어:
1. S → SRP (단일 책임 원칙, Single Responsibility Principle)
2. O → OCP (개방-폐쇄 원칙, Open-Closed Principle)
3. L → LSP (리스코프 치환 원칙, Liskov Substitution Principle)
4. I → ISP (인터페이스 분리 원칙, Interface Segregation Principle)
5. D → DIP (의존 역전 원칙, Dependency Inversion Principle)
1️⃣ SRP (단일 책임 원칙)
“하나의 클래스는 하나의 책임(기능)만 가져야 한다.”
“하나의 클래스가 변경되는 이유는 단 하나뿐이어야 한다.”
✅ 잘못된 예제 (SRP 위반)
public class UserManager {
public void createUser(String name) {
// 사용자 생성 로직
}
public void sendEmail(String email, String message) {
// 이메일 전송 로직 (SRP 위반)
}
}
• 문제점:
• UserManager는 사용자 관리뿐만 아니라 이메일 전송 기능까지 담당함.
• sendEmail()이 변경되면 UserManager도 영향을 받음.
✅ SRP를 적용한 코드
public class UserManager {
public void createUser(String name) {
// 사용자 생성 로직
}
}
public class EmailService {
public void sendEmail(String email, String message) {
// 이메일 전송 로직
}
}
👉 해결:
• UserManager는 사용자 생성 역할만 수행.
• EmailService는 이메일 전송을 담당.
• 서로 다른 책임을 분리하여 변경이 한 부분에만 영향을 미침.
2️⃣ OCP (개방-폐쇄 원칙)
“클래스는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.”
“새 기능을 추가할 때 기존 코드를 변경하지 않아야 한다.”
✅ 잘못된 예제 (OCP 위반)
public class PaymentProcessor {
public void processPayment(String type) {
if (type.equals("CreditCard")) {
System.out.println("Processing credit card payment");
} else if (type.equals("PayPal")) {
System.out.println("Processing PayPal payment");
}
}
}
• 문제점:
• 새로운 결제 방식(예: Apple Pay)을 추가할 때 processPayment()를 수정해야 함 → OCP 위반.
✅ OCP를 적용한 코드 (인터페이스 활용)
public interface PaymentMethod {
void pay();
}
public class CreditCardPayment implements PaymentMethod {
public void pay() {
System.out.println("Processing credit card payment");
}
}
public class PayPalPayment implements PaymentMethod {
public void pay() {
System.out.println("Processing PayPal payment");
}
}
public class PaymentProcessor {
public void processPayment(PaymentMethod method) {
method.pay();
}
}
• 새로운 결제 방식 추가 시(ApplePayPayment), 기존 PaymentProcessor 코드를 수정할 필요 없음.
3️⃣ LSP (리스코프 치환 원칙)
“자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다.”
“상속받은 클래스가 부모 클래스의 기능을 깨뜨려서는 안 된다.”
✅ 잘못된 예제 (LSP 위반)
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 {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width; // LSP 위반
}
}
• 문제점:
• Square 클래스는 Rectangle을 상속받았지만, setWidth()를 호출하면 height도 변경됨.
• 부모 클래스(Rectangle)의 동작을 깨뜨림 → LSP 위반.
✅ LSP를 적용한 코드 (상속 대신 인터페이스 사용)
public interface Shape {
int getArea();
}
public class Rectangle implements Shape {
private int width, height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public int getArea() {
return width * height;
}
}
public class Square implements Shape {
private int side;
public Square(int side) {
this.side = side;
}
public int getArea() {
return side * side;
}
}
• 사각형과 정사각형을 개별 클래스로 분리하여 문제 해결.
• 부모 클래스의 동작을 변경하지 않도록 설계.
4️⃣ ISP (인터페이스 분리 원칙)
“클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다.”
“인터페이스를 여러 개로 나누어야 한다.”
✅ 잘못된 예제 (ISP 위반)
public interface Worker {
void work();
void eat();
}
public class Robot implements Worker {
public void work() { System.out.println("로봇이 일함"); }
public void eat() { throw new UnsupportedOperationException("로봇은 밥을 먹지 않음"); }
}
• 문제점:
• Robot 클래스는 eat() 메서드를 구현할 필요 없음.
• 필요하지 않은 메서드를 강제로 구현해야 함 → ISP 위반.
✅ ISP를 적용한 코드 (인터페이스 분리)
public interface Workable {
void work();
}
public interface Eatable {
void eat();
}
public class Robot implements Workable {
public void work() { System.out.println("로봇이 일함"); }
}
public class Human implements Workable, Eatable {
public void work() { System.out.println("사람이 일함"); }
public void eat() { System.out.println("사람이 밥을 먹음"); }
}
• 인터페이스를 Workable, Eatable로 분리하여 문제 해결.
• 각 클래스는 필요한 기능만 구현하도록 개선됨.
5️⃣ DIP (의존 역전 원칙)
“상위(추상) 클래스에 의존하고, 하위(구현) 클래스에 의존하지 않아야 한다.”
“의존성을 낮추고 확장성을 높이기 위해 인터페이스를 활용해야 한다.”
✅ 잘못된 예제 (DIP 위반)
public class FileLogger {
public void log(String message) {
System.out.println("File log: " + message);
}
}
public class UserService {
private FileLogger logger = new FileLogger();
public void registerUser(String username) {
logger.log("User registered: " + username);
}
}
• 문제점:
• UserService는 FileLogger에 강하게 의존.
• 새로운 로깅 방식(예: DatabaseLogger)을 추가할 때 UserService 코드를 수정해야 함.
✅ DIP를 적용한 코드 (인터페이스 활용)
public interface Logger {
void log(String message);
}
public class FileLogger implements Logger {
public void log(String message) { System.out.println("File log: " + message); }
}
public class UserService {
private Logger logger;
public UserService(Logger logger) {
this.logger = logger;
}
public void registerUser(String username) {
logger.log("User registered: " + username);
}
}
• UserService는 Logger 인터페이스에 의존 → 유연한 구조로 변경됨.
📌 정리
✅ SOLID 원칙을 따르면 코드 유지보수성이 높아지고, 확장하기 쉬운 구조가 됨.
원칙 | 설명 |
SRP | 하나의 클래스는 하나의 책임만 가져야 함 |
OCP | 기존 코드를 변경하지 않고 확장 가능해야 함 |
LSP | 하위 클래스는 상위 클래스를 대체할 수 있어야 함 |
ISP | 인터페이스를 여러 개로 분리해야 함 |
DIP | 구체적인 클래스가 아닌 인터페이스에 의존해야 함 |
'CS' 카테고리의 다른 글
퍼사드 패턴 (0) | 2025.04.14 |
---|---|
DSL (0) | 2025.03.25 |
JIT(Just-In-Time) 컴파일러 (0) | 2025.03.05 |
트러블 슈팅 (0) | 2025.02.28 |
SSE (Server-Sent Events) (0) | 2025.02.19 |