SOLID 원칙
=객체지향 설계 5원칙, 각 원칙의 앞 글자를 따서 만들어짐
객체지향설계 핵심은 의존성을 잘 관리하는 것임
✅Single Responsibility Principle(SRP): 단일 책임 원칙
클래스가 오직 하나의 목적이나 이유로만 변경되어야 함.
여러 책임이 한 클래스에 있으면 한 기능이 변경될 때 다른 기능도 영향 받음.
Responsibility(책임)은 특정 사용자나 기능 요구사항에 따라 소프트웨어의 변경 요청을 처리하는 역할을 의미함
📌SRP를 위반한 코드
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
saveToDatabase() {
// DB 저장 로직 (데이터 관리 책임)
console.log("User saved to database");
}
sendEmail() {
// 이메일 전송 로직 (이메일 관련 책임)
console.log("Email sent to user");
}
}
📌 SRP를 준수한 코드
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
}
class UserRepository {
save(user) {
console.log(`${user.name} saved to database`);
}
}
class EmailService {
sendEmail(user) {
console.log(`Email sent to ${user.email}`);
}
}
User 정보 저장 방식이 바뀌어도 EmailService에는 영향이 없음
✅ Open-Closed Principle(OCP): 개방 폐쇄 원칙
확장에는 열려있고, 변경에는 닫혀 있어야 함.
* 확장: 새로운 타입을 추가해 새로운 기능을 추가하는 것
* 폐쇄: 확장이 일어날 때 상위 레벨의 모듈이 영향을 받지 않아야 함
기존 코드 수정 없이 새로운 기능을 추가할 수 있어 버그 발생 가능성을 줄임. 다른 코드와 독립적임.
📌 OCP 위반한 코드
class PaymentProcessor {
process(paymentType) {
if (paymentType === "creditCard") {
console.log("Processing credit card payment");
} else if (paymentType === "paypal") {
console.log("Processing PayPal payment");
}
}
}
📌 OCP 준수한 코드
class PaymentProcessor {
process(paymentMethod) {
paymentMethod.pay();
}
}
class CreditCardPayment {
pay() {
console.log("Processing credit card payment");
}
}
class PayPalPayment {
pay() {
console.log("Processing PayPal payment");
}
}
// 확장할 때 기존 코드 수정 없이 새로운 클래스만 추가하면 됨!
class CryptoPayment {
pay() {
console.log("Processing cryptocurrency payment");
}
}
기존 PaymentProcessor 코드를 수정할 필요 없이 새로운 결제 방식을 추가할 수 있음
기능이 확장될 때 기존 코드에 영향 주지 않음
✅ Liskov Substitution Principle(LSP): 리스코브 치환 원칙
하위 클래스는 상위 클래스를 대체할 수 있어야 함
상위 타입을 사용하는 코드가 하위 타입으로 교체되더라도 정상적으로 작동해야 한다
하위 클래스가 상위 클래스 기능을 깨지 않도록 보장해 예상치 못한 버그 방지
📌 LSP를 위반한 코드
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
setWidth(width) {
this.width = width;
}
setHeight(height) {
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
class Square extends Rectangle {
setWidth(width) {
this.width = width;
this.height = width; // ❌ 정사각형이므로 width와 height가 항상 같아야 함
}
setHeight(height) {
this.height = height;
this.width = height; // ❌
}
}
const shape = new Square();
shape.setWidth(10);
console.log(shape.getArea()); // 기대값: 100, 실제값: 100 (OK)
const rect = new Rectangle(10, 5);
rect.setWidth(20);
console.log(rect.getArea()); // 기대값: 100, 실제값: 100 (OK)
Rectangle은 setWidth()와 setHeight()가 독립적으로 작동해야 하지만, Square는 항상 정사각형이 되도록 강제 됨
하위 클래스가 상위 클래스의 계약을 깸
📌 LSP를 준수한 코드
class Shape {
getArea() {
throw new Error("getArea must be implemented");
}
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
class Square extends Shape {
constructor(side) {
super();
this.side = side;
}
getArea() {
return this.side * this.side;
}
}
Rectangle과 Square는 서로 독립적으로 작동하며 상속 구조 유지
✅ Interface Sergregation Principle(ISP): 인터페이스 분리 원칙
클라이언트는 사용하지 않는 메서드에 의존하지 않아야 함
사용하지 않는 메서드가 많으면 클래스가 불필요한 의존성을 가지게 됨
변경 시 영향 범위를 최소화해 독립적인 개발 가능
📌 ISP 위반한 코드
class Worker {
work() {
console.log("Working...");
}
eat() {
console.log("Eating...");
}
}
class Robot extends Worker {
eat() {
throw new Error("Robot can't eat"); // ❌ 불필요한 메서드 포함됨
}
}
📌 ISP 준수한 코드
class Workable {
work() {
throw new Error("Method must be implemented");
}
}
class Eatable {
eat() {
throw new Error("Method must be implemented");
}
}
class HumanWorker extends Workable {
work() {
console.log("Working...");
}
eat() {
console.log("Eating...");
}
}
class RobotWorker extends Workable {
work() {
console.log("Working...");
}
}
불필요한 메서드 제거해 각 클래스가 자신의 역할만 수행하도록 함
✅ Dependency Inversion Principle: 의존성 역전 원칙
상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안되며, 둘 다 추상화에 의존해야 함
코드는 구체적 구현이 아닌, 인터페이스(추상화된 개념)에 의존해야 한다
하위 모듈이 변경되더라도, 상위 모듈에 영향을 주지 않고 유연하게 확장할 수 있음
📌 DIP를 위반한 코드
class Keyboard {
connect() {
return "Keyboard connected!";
}
}
class Computer {
constructor() {
this.keyboard = new Keyboard(); // ❌ 직접 의존 (구체적인 클래스 사용)
}
useKeyboard() {
console.log(this.keyboard.connect());
}
}
const myComputer = new Computer();
myComputer.useKeyboard(); // "Keyboard connected!"
Computer 클래스가 Keyboard 클래스에 직접 의존
Keyboard의 변경이 필요하면 Computer 코드도 수정해야 함
📌 DIP를 준수한 코드
// 1️⃣ 추상 인터페이스 생성 (Keyboard의 역할만 정의)
class InputDevice {
connect() {
throw new Error("connect() must be implemented");
}
}
// 2️⃣ Keyboard 클래스는 InputDevice를 상속받아 구현
class Keyboard extends InputDevice {
connect() {
return "Keyboard connected!";
}
}
// 3️⃣ Computer는 Keyboard에 직접 의존하지 않고, InputDevice(추상화)에 의존
class Computer {
constructor(inputDevice) {
this.inputDevice = inputDevice; // ✅ 인터페이스(추상화)에 의존
}
useInputDevice() {
console.log(this.inputDevice.connect());
}
}
// 4️⃣ 다른 입력 장치 추가 (Mouse)
class Mouse extends InputDevice {
connect() {
return "Mouse connected!";
}
}
// ✅ Keyboard 사용
const myComputer = new Computer(new Keyboard());
myComputer.useInputDevice(); // "Keyboard connected!"
// ✅ Mouse 사용
const gamingComputer = new Computer(new Mouse());
gamingComputer.useInputDevice(); // "Mouse connected!"
Computer가 Keyboard에 직접 의존하지 않고 InputDevice라는 인터페이스에 의존함
키보드를 바꾸거나 다른 입력 장치를 추가해도 코드 변경이 필요 없음
출처
[1] 매일메일. 250130. SOLID 원칙에 대해서 설명해주세요. 110번. https://maeil-mail.kr
[2] gpt에게 SOLID에 대해 묻다.
'개발자 강화 > 백엔드' 카테고리의 다른 글
[매일메일] 로드 밸런싱이란? (BE.250203) (0) | 2025.02.03 |
---|---|
[매일메일] 다중 서버에서 세션 기반 인증을 사용했을 때 문제점? (BE.250131) (0) | 2025.01.31 |
[매일메일] DB Replication이란? (BE.250129) (0) | 2025.01.30 |
[매일메일] Record를 VO가 아닌 DTO로 사용하는 이유? (BE.250128) (1) | 2025.01.29 |
[매일메일] HTTPS란? (BE. 250127) (0) | 2025.01.27 |