SOLID 실전 가이드
SOLID의 핵심 개념과 실무 활용
학습 항목
이미지 로딩 중...
SOLID 원칙 실무 활용 완벽 가이드
객체지향 설계의 5대 원칙인 SOLID를 실무에서 바로 적용할 수 있는 실용적인 예제로 배워봅니다. 초급 개발자도 쉽게 이해하고 활용할 수 있도록 구성했습니다.
들어가며
이 글에서는 SOLID 원칙 실무 활용 완벽 가이드에 대해 상세히 알아보겠습니다. 총 10가지 주요 개념을 다루며, 각각의 개념에 대한 설명과 실제 코드 예제를 함께 제공합니다.
목차
- 단일_책임_원칙_SRP
- 개방_폐쇄_원칙_OCP
- 리스코프_치환_원칙_LSP
- 인터페이스_분리_원칙_ISP
- 의존성_역전_원칙_DIP
- SRP_실무_예제_로깅
- OCP_실무_예제_알림
- DIP_실무_예제_캐시
- SOLID_종합_예제_1
- SOLID_종합_예제_2
1. 단일_책임_원칙_SRP
개요
하나의 클래스는 하나의 책임만 가져야 합니다. 사용자 관리 클래스를 책임별로 분리하는 예제입니다.
코드 예제
// Bad: 여러 책임을 가진 클래스
class UserService {
saveUser(user: User) { /* DB 저장 */ }
sendEmail(user: User) { /* 이메일 전송 */ }
}
// Good: 책임을 분리
class UserRepository {
saveUser(user: User) { /* DB 저장만 담당 */ }
}
class EmailService {
sendEmail(user: User) { /* 이메일만 담당 */ }
}
설명
각 클래스가 하나의 책임만 가지므로 변경 사유가 명확해지고 유지보수가 쉬워집니다.
2. 개방_폐쇄_원칙_OCP
개요
확장에는 열려있고 수정에는 닫혀있어야 합니다. 새로운 기능 추가시 기존 코드를 수정하지 않는 방법입니다.
코드 예제
interface PaymentMethod {
pay(amount: number): void;
}
class CreditCard implements PaymentMethod {
pay(amount: number) { console.log(`카드 결제: ${amount}`); }
}
class KakaoPay implements PaymentMethod {
pay(amount: number) { console.log(`카카오페이: ${amount}`); }
}
class Payment {
process(method: PaymentMethod, amount: number) {
method.pay(amount);
}
}
설명
새로운 결제 수단을 추가할 때 Payment 클래스를 수정하지 않고 인터페이스만 구현하면 됩니다.
3. 리스코프_치환_원칙_LSP
개요
자식 클래스는 부모 클래스를 완전히 대체할 수 있어야 합니다. 직사각형과 정사각형 예제로 알아봅니다.
코드 예제
abstract class Shape {
abstract getArea(): number;
}
class Rectangle extends Shape {
constructor(private width: number, private height: number) {
super();
}
getArea(): number { return this.width * this.height; }
}
class Square extends Shape {
constructor(private side: number) {
super();
}
getArea(): number { return this.side * this.side; }
}
설명
정사각형을 직사각형의 자식으로 만들지 않고 독립된 클래스로 설계하여 치환 원칙을 지킵니다.
4. 인터페이스_분리_원칙_ISP
개요
클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 합니다. 인터페이스를 작게 분리합니다.
코드 예제
// Bad: 큰 인터페이스
interface Worker {
work(): void;
eat(): void;
}
// Good: 분리된 인터페이스
interface Workable {
work(): void;
}
interface Eatable {
eat(): void;
}
class Human implements Workable, Eatable {
work() { console.log("작업중"); }
eat() { console.log("식사중"); }
}
설명
필요한 인터페이스만 구현하므로 불필요한 의존성이 없어지고 유연성이 높아집니다.
5. 의존성_역전_원칙_DIP
개요
구체적인 클래스가 아닌 추상화에 의존해야 합니다. 데이터베이스 연결 예제입니다.
코드 예제
interface Database {
connect(): void;
query(sql: string): any;
}
class MySQL implements Database {
connect() { console.log("MySQL 연결"); }
query(sql: string) { return "MySQL 결과"; }
}
class UserService {
constructor(private db: Database) {}
getUsers() {
this.db.connect();
return this.db.query("SELECT * FROM users");
}
}
설명
UserService는 구체적인 MySQL이 아닌 Database 인터페이스에 의존하므로 DB 변경이 쉽습니다.
6. SRP_실무_예제_로깅
개요
로깅 기능을 별도 클래스로 분리하여 단일 책임 원칙을 적용한 실무 예제입니다.
코드 예제
class Logger {
log(message: string) {
console.log(`[${new Date().toISOString()}] ${message}`);
}
}
class OrderService {
constructor(private logger: Logger) {}
createOrder(order: Order) {
// 주문 생성 로직
this.logger.log(`주문 생성: ${order.id}`);
return order;
}
}
설명
로깅 책임을 Logger 클래스로 분리하여 OrderService는 비즈니스 로직에만 집중합니다.
7. OCP_실무_예제_알림
개요
다양한 알림 채널을 확장 가능하게 설계한 개방-폐쇄 원칙 예제입니다.
코드 예제
interface Notifier {
send(message: string): void;
}
class EmailNotifier implements Notifier {
send(msg: string) { console.log(`Email: ${msg}`); }
}
class SlackNotifier implements Notifier {
send(msg: string) { console.log(`Slack: ${msg}`); }
}
class NotificationService {
notify(notifiers: Notifier[], msg: string) {
notifiers.forEach(n => n.send(msg));
}
}
설명
새로운 알림 채널 추가시 기존 코드를 수정하지 않고 Notifier 인터페이스만 구현합니다.
8. DIP_실무_예제_캐시
개요
캐시 구현체를 추상화하여 의존성을 역전시킨 실무 예제입니다.
코드 예제
interface Cache {
get(key: string): any;
set(key: string, value: any): void;
}
class RedisCache implements Cache {
get(key: string) { return "Redis 데이터"; }
set(key: string, value: any) { /* Redis 저장 */ }
}
class ProductService {
constructor(private cache: Cache) {}
getProduct(id: string) {
return this.cache.get(`product:${id}`);
}
}
설명
ProductService는 Redis가 아닌 Cache 인터페이스에 의존하여 캐시 구현체 변경이 자유롭습니다.
9. SOLID_종합_예제_1
개요
여러 SOLID 원칙을 함께 적용한 사용자 인증 시스템 예제입니다.
코드 예제
interface AuthStrategy {
authenticate(credentials: any): boolean;
}
class JWTAuth implements AuthStrategy {
authenticate(token: string) {
return token.length > 0;
}
}
class AuthService {
constructor(private strategy: AuthStrategy) {}
login(credentials: any): boolean {
return this.strategy.authenticate(credentials);
}
}
설명
OCP와 DIP를 활용하여 다양한 인증 방식을 쉽게 교체하고 확장할 수 있습니다.
10. SOLID_종합_예제_2
개요
파일 처리 시스템에 SOLID 원칙을 적용한 종합 예제입니다.
코드 예제
interface FileReader {
read(path: string): string;
}
interface FileWriter {
write(path: string, content: string): void;
}
class TextFileReader implements FileReader {
read(path: string) { return "파일 내용"; }
}
class FileProcessor {
constructor(
private reader: FileReader,
private writer: FileWriter
) {}
}
설명
ISP로 읽기/쓰기를 분리하고 DIP로 구체적 구현에 의존하지 않아 테스트와 확장이 쉽습니다.
마치며
이번 글에서는 SOLID 원칙 실무 활용 완벽 가이드에 대해 알아보았습니다. 총 10가지 개념을 다루었으며, 각각의 사용법과 예제를 살펴보았습니다.
관련 태그
#TypeScript #SOLID #OOP #DesignPatterns #CleanCode