SOLID 원칙
심화
객체지향 설계의 5가지 핵심 원칙을 깊이 있게 이해하고, AI 시대에 맞게 재해석합니다.
SOLID 원칙 개요
Robert C. Martin이 정립한 객체지향 설계의 5가지 핵심 원칙
SOLID 원칙은 변경에 유연하고, 이해하기 쉬우며, 재사용 가능한 소프트웨어를 만들기 위한 가이드라인입니다. 각 원칙을 클릭하면 해당 섹션으로 이동합니다.
Single Responsibility Principle
단일 책임 원칙
단일 책임 원칙
클래스는 변경되어야 할 이유가 오직 하나뿐이어야 한다"하나의 클래스는 하나의 책임만 가져야 한다"
더 정확히 말하면, 클래스를 변경해야 하는 이유(reason to change)가 오직 하나여야 합니다. 책임 = 변경의 이유입니다.
class UserService { createUser(data) { // 비즈니스 로직 } saveToDatabase(user) { // DB 접근 } sendEmail(user) { // 이메일 발송 } generateReport() { // 리포트 생성 } logActivity(action) { // 로깅 } } // 5가지 변경 이유가 존재!
class UserService { createUser(data) { // 비즈니스 로직만 } } class UserRepository { save(user) { // DB 접근만 } } class EmailService { send(to, content) { // 이메일만 } } // 각 클래스는 하나의 변경 이유만!
AI가 코드를 생성할 때, 명확하게 분리된 단일 책임 클래스일수록 더 정확한 코드를 생성합니다. "UserService에 이메일 기능 추가해줘"보다 "EmailService 만들어줘"가 AI에게 더 명확한 지시입니다. 또한, 테스트 코드 생성 시에도 책임이 분리된 클래스는 더 정확한 단위 테스트를 생성할 수 있습니다.
Open/Closed Principle
개방-폐쇄 원칙
개방-폐쇄 원칙
확장에는 열려있고, 수정에는 닫혀있어야 한다"기존 코드를 변경하지 않고 기능을 확장할 수 있어야 한다"
새로운 요구사항이 생겼을 때, 기존 코드를 수정하는 것이 아니라 새로운 코드를 추가하여 해결해야 합니다. 이는 추상화와 다형성을 통해 달성됩니다.
class PaymentProcessor { process(payment) { if (payment.type === 'credit') { // 신용카드 처리 } else if (payment.type === 'paypal') { // PayPal 처리 } else if (payment.type === 'crypto') { // 새 결제 수단마다 수정 필요! } } }
interface PaymentMethod { process(amount): Result; } class CreditCardPayment implements PaymentMethod { process(amount) { /* ... */ } } class CryptoPayment implements PaymentMethod { process(amount) { /* 새로 추가만! */ } } // 기존 코드 수정 없이 확장 가능
OCP를 따르는 코드는 AI에게 "새로운 결제 수단 CryptoPayment 클래스 만들어줘"라고 요청할 수 있습니다. AI는 기존 인터페이스를 참조하여 일관된 구현체를 생성할 수 있고, 기존 코드를 건드리지 않아 버그 발생 가능성이 최소화됩니다. 플러그인 아키텍처의 기반이 됩니다.
Liskov Substitution Principle
리스코프 치환 원칙
리스코프 치환 원칙
서브타입은 기반 타입을 대체할 수 있어야 한다"자식 클래스는 부모 클래스를 대체해도 프로그램이 정상 동작해야 한다"
상속 관계에서 자식 클래스는 부모 클래스의 행동 규약(계약)을 반드시 지켜야 합니다. IS-A 관계가 행동적으로도 성립해야 합니다.
class Rectangle { setWidth(w) { this.width = w; } setHeight(h) { this.height = h; } } class Square extends Rectangle { setWidth(w) { this.width = w; this.height = w; // 부모와 다른 동작! } } // rect.setWidth(5); rect.setHeight(10); // 직사각형: 면적 50, 정사각형: 면적 100 (예상과 다름!)
interface Shape { getArea(): number; } class Rectangle implements Shape { constructor(w, h) { ... } getArea() { return this.w * this.h; } } class Square implements Shape { constructor(side) { ... } getArea() { return this.side ** 2; } } // 상속 대신 공통 인터페이스 구현
LSP 위반은 AI가 탐지하기 어려운 미묘한 버그를 유발합니다. AI에게 "Rectangle을 상속받는 Square 만들어줘"라고 하면 형식적으로 맞는 코드를 생성하지만, 런타임에 예상치 못한 동작이 발생합니다. 상속보다 컴포지션을 우선하고, 명확한 인터페이스를 정의하는 것이 AI와 협업할 때 더 안전합니다.
Interface Segregation Principle
인터페이스 분리 원칙
인터페이스 분리 원칙
클라이언트가 사용하지 않는 인터페이스에 의존하면 안 된다"하나의 범용 인터페이스보다 여러 개의 구체적인 인터페이스가 낫다"
클라이언트가 자신이 사용하지 않는 메서드에 의존하도록 강제해서는 안 됩니다. 인터페이스는 클라이언트의 필요에 맞게 분리되어야 합니다.
interface Worker { work(); eat(); sleep(); attendMeeting(); writeReport(); } class Robot implements Worker { work() { /* OK */ } eat() { throw '로봇은 먹지 않음'; } sleep() { throw '로봇은 자지 않음'; } // 불필요한 메서드를 구현해야 함! }
interface Workable { work(); } interface Eatable { eat(); } interface Sleepable { sleep(); } class Robot implements Workable { work() { /* 필요한 것만! */ } } class Human implements Workable, Eatable, Sleepable { // 필요한 인터페이스만 선택적으로 구현 }
AI에게 코드 생성을 요청할 때, 작고 명확한 인터페이스가 더 정확한 구현을 유도합니다. "Workable 인터페이스를 구현하는 Robot 클래스 만들어줘"는 모호함 없이 명확합니다. 또한, ISP를 따르면 Mock 객체 생성이 쉬워져 AI 기반 테스트 코드 생성에도 유리합니다.
Dependency Inversion Principle
의존성 역전 원칙
의존성 역전 원칙
추상화에 의존하라, 구체화에 의존하지 마라"고수준 모듈은 저수준 모듈에 의존해서는 안 된다. 둘 다 추상화에 의존해야 한다."
구체적인 구현체가 아닌 인터페이스(추상화)에 의존함으로써, 모듈 간의 결합도를 낮추고 유연성을 높입니다.
class OrderService { private mysqlDb = new MySQLDatabase(); private smtpEmail = new SMTPEmailSender(); createOrder(order) { this.mysqlDb.save(order); this.smtpEmail.send(order.email); } } // MySQL, SMTP에 강하게 결합 // 테스트하려면 실제 DB/이메일 필요!
class OrderService { constructor( private db: Database, // 인터페이스 private emailer: EmailSender // 인터페이스 ) {} createOrder(order) { this.db.save(order); this.emailer.send(order.email); } } // 추상화에만 의존 // 테스트 시 Mock 객체 주입 가능!
DIP는 AI 시대에 가장 중요한 원칙입니다. 의존성이 역전된 코드는:
1) AI가 생성한 새로운 구현체를 기존 코드 수정 없이 교체 가능
2) 테스트 코드 자동 생성 시 Mock 객체 주입이 용이
3) AI 서비스 등 외부 의존성을 쉽게 교체 (GPT -> Claude 등)
인터페이스 중심 설계는 AI와의 협업에서 핵심입니다.
핵심 요약
- SRP - 클래스는 하나의 변경 이유만 가져야 한다. AI에게 명확한 단위로 요청 가능.
- OCP - 기존 코드 수정 없이 확장 가능해야 한다. 플러그인 아키텍처의 기반.
- LSP - 서브타입은 기반 타입을 완벽히 대체해야 한다. 상속보다 컴포지션 선호.
- ISP - 작고 구체적인 인터페이스가 뚱뚱한 인터페이스보다 낫다.
- DIP - 구체화가 아닌 추상화에 의존하라. AI 시대에 가장 중요한 원칙.
SOLID 원칙을 따르는 코드베이스는 AI 도구와의 협업에서 더 좋은 결과를 얻습니다. 명확한 책임 분리, 추상화 중심 설계, 느슨한 결합은 AI가 이해하고 확장하기 쉬운 코드의 특징입니다.