Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |
Tags
- MySQL
- MCP
- mcp client
- Tool Calling
- SQL
- Agent Loop
- dhgrp
- PHP객체지향
- linux 권한
- 소프트웨어설계
- SQL문법
- 데이터베이스
- ai 에이전트
- PHP
- 디자인패턴
- 데코레이터패턴
- 어댑터패턴
- 객체복제
- 매직메서드
- LLM
- docker network
- SQL기초
- ai agent
- docker
- Laravel
- mcp server
- OOP
- 객체지향
- 인덱스
- 라라벨
Archives
- Today
- Total
개발블로그
[객체지향] SOLID 원칙 : 좋은 객체지향 설계를 판단하는 5가지 기준 본문
Contents
접기
왜 SOLID 원칙이 필요할까?
객체지향으로 코드를 작성한다고 해서 자동으로 좋은 설계가 되는 것은 아님.
클래스를 나누었지만 하나의 클래스가 너무 많은 일을 할 수 있고, 기능을 하나 추가할 때마다 기존 코드를 계속 수정해야 할 수도 있다. 또한 상속을 사용했지만 자식 클래스가 부모 클래스처럼 동작하지 않아 예상치 못한 오류가 생길 수도 있다.
즉, 객체지향의 핵심은 단순히 클래스를 사용하는 것이 아니라 변경에 강하고 이해하기 쉬운 구조를 만드는 것
5가지 기준
| 원칙 | 이름 | 핵심 질문 |
| SRP | 단일 책임 원칙 | 이 클래스가 바뀌는 이유가 하나인가? |
| OCP | 개방-폐쇄 원칙 | 기능 추가 때 기존 코드를 계속 수정해야 하는가? |
| LSP | 리스코프 치환 원칙 | 자식 객체를 부모 객체처럼 사용해도 문제가 없는가? |
| ISP | 인터페이스 분리 원칙 | 사용하지 않는 기능까지 의존하고 있지는 않은가? |
| DIP | 의존성 역전 원칙 | 상위 로직이 구체적인 구현에 직접 의존하고 있지는 않은가? |
SRP : 단일 책임 원칙
하나의 클래스는 하나의 책임을 가져야 한다.
하나의 클래스가 바뀌는 이유가 너무 많으면 안 됨.
=> 변경 이유가 같은 코드끼리 모으고, 다른 코드끼리는 분리
ex)
# 이 클래스는 결제 처리, 결제 내역 저장, 알림 발송을 모두 담당함
class PaymentService:
def pay(self, amount):
pass
def save_history(self):
pass
def send_notification(self):
pass
# 문제 : 결제 방식이 바뀔 때, 저장소가 바뀔 때, 알림 방식이 바뀔 때 모두 같은 클래스가 수정됨
# 변경 이유가 다르다면, 책임도 분리하는 것이 좋다.
class PaymentService:
def pay(self, amount):
pass
class PaymentHistoryRepository:
def save(self, payment):
pass
class NotificationService:
def send(self, message):
pass
OCP : 개방-폐쇄 원칙
확장에는 열려있고, 변경에는 닫혀 있어야 함.
새로운 기능을 추가할 때 기존 코드를 계속 수정해야 한다면, 그 코드는 변경에 약한 구조일 수 있음.
=> 자주 바뀌는 부분을 분리해서, 확장할 때 수정 범위를 줄이기
EX)
# 쿠폰 결제가 추가되면 pay() 함수 자체를 수정해야 함
def pay(payment_type, amount):
if payment_type == "card":
return "카드 결제"
elif payment_type == "point":
return "포인트 결제"
# => 쿠폰 결제가 추가되면 기존 pay() 함수 안에 조건문을 또 추가해야 함
# => 확장할 때 기존 코드가 계속 열려있다.
# 결제 방식을 객체로 분리하면 새로운 결제 수단을 추가하기 쉬워짐
class CardPayment:
def pay(self, amount):
return "카드 결제"
class PointPayment:
def pay(self, amount):
return "포인트 결제"
class CouponPayment:
def pay(self, amount):
return "쿠폰 결제"
LSP : 리스코프 치환 원칙
자식 클래스는 부모 클래스를 대체할 수 있어야 함
상속 관계를 만들었는데 자식 클래스가 부모 클래스의 기대 동작을 지키지 못하면 문제가 생긴다.
ex)
# 모든 결제 수단은 pay()를 할 수 있다고 가정
class Payment:
def pay(self, amount):
pass
class CardPayment(Payment):
def pay(self, amount):
return "카드 결제 완료"
class PointPayment(Payment):
def pay(self, amount):
return "포인트 결제 완료"
# 주문 코드는 구체적인 결제 방식이 무엇인지 몰라도 됨
def checkout(payment, amount):
return payment.pay(amount)
# 여기까지는 LSP를 잘 지킨 구조 : 자식 클래스들이 모두 부모의 기대 "결제할 수 있다"는 약속을 지킴
# 만약 무료 쿠폰을 결제 수단이라고 보고, Payment를 상속하게 만들었을 때
class FreeCouponPayment(Payment):
def pay(self, amount):
if amount > 0:
raise Exception("무료 쿠폰은 결제 금액을 처리할 수 없습니다.")
return "무료 쿠폰 적용"
# 문제 : checkout()은 Payment라면 당연히 pay(amount)를 처리할 수 있다고 기대,
# 그런데 FreeCouponPayment는 특정 금액에서 예외를 던짐
# => 부모 타입으로 사용할 수 있을거라 믿었는데 자식 클래스가 그 기대를 깨버림
# 이때는 FreeCouponPayment를 Payment의 자식으로 두는 것이 어색할 수 있다.
# 무료쿠폰은 결제수단이라기 보다는 할인 정책에 가까울 수 있음
class DiscountPolicy:
def discount(self, amount):
pass
class FreeCouponDiscount(DiscountPolicy):
def discount(self, amount):
return amount
ISP : 인터페이스 분리 원칙
사용하지 않는 기능에 의존하지 않도록 인터페이스를 나눠라
=> 사용하는 쪽이 필요한 기능에만 의존하게 만드는 것
EX)
class Machine:
def print(self):
pass
def scan(self):
pass
def fax(self):
pass
# 이 인터페이스를 모든 기계가 따라야 한다면, 단순 프린터도 scan()과 fax()를 억지로 구현해야 함
# => 불필요한 기능까지 강제로 의존하게 만듦
class SimplePrinter(Machine):
def print(self):
pass
def scan(self):
raise Exception("지원하지 않습니다.")
# 역할별로 나누기
class Printer:
def print(self):
pass
class Scanner:
def scan(self):
pass
DIP : 의존성 역전 원칙
상위 수준의 로직이 구체적인 구현에 직접 의존하지 않도록 하라
EX]
class OrderService:
def __init__(self):
self.payment = CardPayment()
def order(self, amount):
return self.payment.pay(amount)
# OrderService는 CardPayment에 직접 의존함
# => 다른 결제 수단을 사용하려면 OrderService를 수정해야 함
# 결제 방식을 외부에서 주입받으면 더 유연해짐
class OrderService:
def __init__(self, payment):
self.payment = payment
def order(self, amount):
return self.payment.pay(amount)
# => OrderService는 구체적인 결제 방식이 무엇인지 알 필요가 없음.
# 그저 pay()를 수행할 수 있는 객체에 의존하면 됨.
'STUDY > CS' 카테고리의 다른 글
| [디자인패턴] 팩토리 패턴 (1) | 2026.09.04 |
|---|---|
| [디자인 패턴] 전략패턴 (0) | 2026.09.04 |
| [디자인 패턴] 디자인 패턴이란? 반복되는 설계 문제를 해결하는 방법 (0) | 2026.09.03 |
| [객체지향] 클래스를 쓰면 객체지향일까? 객체지향이 필요한 진짜 이유 (0) | 2026.09.03 |
| REST API (1) | 2026.08.21 |