🕐 ~8분 읽기
📝 이 글에 대한 메모
이 글은 제 개인 학습 메모에 기반하여 OOP와 SOLID 원칙에 대해 작성되었습니다. 메모를 더 쉽게 읽고 유용하게 만들기 위해 — 제 자신과 다른 사람들에게 — 저는 AI의 도움을 받아 개발하고 블로그 게시물로 구성했습니다. 아이디어, 학습 여정, 그리고 이해는 제 것이며, AI는 작성 및 전달 부분에서 도움을 주었습니다.
좋은 코드는 읽기 쉽고 수정하기 쉽습니다. 우연이 아닙니다. 매력적인 코드베이스의 뒤에는 모든 것이 어떻게 구성되고 연결되는지를 안내하는 아이디어의 집합이 있습니다.
이 글은 그 완전한 여정을 다룹니다 — OOP가 무엇인지부터 시작하여 객체가 어떻게 서로 관계를 맺는지, 나쁜 설계는 어떤지, 그리고 마지막으로: 모든 것을 통합하는 SOLID의 다섯 원칙.
시작해 봅시다.
1부: OOP는 무엇인가요?
객체 지향 프로그래밍 (Object Oriented Programming, OOP)는 현실 세계를 반영하는 코드 작성 방식입니다. 긴 명령 목록 대신, 관련된 데이터와 행동을 서로 연관된 객체로 묶습니다.
🤖 로봇 장난감을 상상해 보세요. 로봇은 속성 (색상, 높이, 배터리)과 행동 (걷기, 말하기, 손 흔들기). 객체 지향 프로그래밍에서 로봇은 객체로서 — 그것을 만들기 위해 사용되는 설계도는 클래스이라고 합니다.
두 개의 핵심 용어:
- 클래스 — 설계도(템플릿)
- 객체 — 그림자로 만들어진 실체
네 개의 OOP 기둥
| 기둥 | 기능 |
|---|---|
| 상속 | 자식 클래스는 부모 클래스의 속성과 동작을 상속받습니다 |
| 캡슐화 | 내부 데이터가 숨겨집니다 — 외부에서의 접근은 지정된 경로를 통해만 가능합니다 |
| 추상화 | 내부 기능은 숨겨져 있으며, 필요한 것만 표시됩니다 |
| 다형성 | 같은 동작이 객체에 따라 다르게 행동할 수 있습니다 |
부분 2: 객체가 어떻게 서로 관계를 맺는가
객체는 따로 살아있지 않습니다. 그들은 연결되어, 함께 일하고 서로 의존합니다 — 그리고 종류와의 연결은 중요합니다.
협회
두 개체가 서로 인식하고 상호작용할 수 있습니다. 세 가지 주요성이 있습니다.
- 일대일 — 한 사람, 한 여권
- 일대다 — 한 선생님, 많은 학생들
- 다대다 — 많은 학생들, 많은 과목들
연관관계에서, 한 객체가 다른 객체를 "소유"할 때, 그 관계는 다음과 같을 수 있습니다:
- 연관성 (가볍게) — 자식 객체는 부모 객체 없이도 존재할 수 있습니다. 가방에 책이 들어있지만, 가방을 버려도 책은 여전히 존재합니다.
- 구성 (밀접하게) — 자식 객체는 부모 객체 없이는 존재할 수 없습니다. 집이 방을 가지고 있으며, 집을 파괴하면 방도 함께 사라집니다.
의존성
사용 기반 일시적 관계. 한 클래스가 메서드 내에서 다른 클래스를 사용하지만 영구적으로 저장하지 않는다. 연관 관계보다 약하다.
일반화 & 구현
- 일반화 — 여러 클래스의 공통 특징을 하나의 슈퍼 클래스로 상속하여 끌어올린다.
- 구현 — 인터페이스를 구현하는 클래스로, 정의된 계약을 이행할 것을 약속합니다
부분 3: 어떤 나쁜 설계인가요?
규칙을 배우기 전에, 우리가 피해야 할 것을 이해하는 것이 중요합니다. Robert C. Martin은 세 가지 나쁜 설계 징후를 식별했습니다:
🪨 유연성 부족 — 시스템은 변경하기 어렵습니다. 하나를 건드리면 많은 다른 것들도 업데이트해야 합니다. 개발자들은 변경 사항을 두려워하게 됩니다.
🍪 Fragility (Kerapuhan) — 시스템은 예상치 못한 곳에서 변경 사항을 적용할 때 손상됩니다. 결제 모듈의 버그를 수정하려고 하니, 갑자기 이메일 알림이 작동하지 않습니다.
🏗️ Immobility (Ketidakmampuan Dipindahkan) — 유용한 구성 요소는 재사용할 수 없습니다. 그들은 주변 환경과 너무 마모되어 있어 제거하는 데는 처음부터 다시 작성하는 것보다 더 많은 시간이 걸립니다.
세 가지 모두 동일한 원인을 가지고 있습니다: 구성 요소 간 의존성 관리가 불량.
부분 4: SOLID
SOLID는 위의 문제를 직접적으로 해결하는 다섯 가지 원칙입니다. 각각은 특정 설계 실패를 표적으로 합니다.
S — 단일 책임 원칙
"모듈은 한 가지, 그리고 오직 한 가지 역할에 책임져야 합니다."
각 클래스는 정확히 하나의 이유만으로 변해야 합니다 — 하나의 역할(스택홀더 그룹)을 서비스합니다.
👨🍳 레스토랑 직원이 동시에 셰프, 계산원, 경비원, 식사 배달원이 되는 것은 예기치 않은 재앙을 기다리는 일입니다. 하나의 역할의 변화가 다른 모든 역할을 방해합니다.
클래스를 분리하세요. 하나의 클래스당 하나의 책임.
O — 개방/폐쇄 원칙
"소프트웨어 장치는 추가될 수 있어야 하지만, 수정할 수는 없어야 합니다."
새로운 동작을 추가하는 방법은 확장을 통해 — 기존에 작동 중인 코드를 편집하는 대신.
🔌 전원 플러그는 새로운 장치를 연결할 수 있게 하여 케이블을 분리할 필요가 없게 합니다. 이것이 OCP의 실제 적용입니다.
필요가 변하면, 기존 코드 옆에 새 코드를 추가하세요 — 작동하는 코드를 다시 작성하지 마세요.
L — 리스코프 대체 원칙
"S가 T의 서브타입이라면, T 타입의 객체를 S 타입의 객체로 대체할 수 있어야 프로그램의 정확성이 변경되지 않아야 합니다."
하위 클래스는 완전히 대체할 수 있어야 합니다 클래스는 부모 클래스입니다. 부모가 만든 모든 약속은 자식이 이행해야 합니다.
🤖 만약 부모 로봇이 요리할 수 있다면, 자식 로봇도 요리할 수 있어야 합니다 — 에러를 던지지 말고 아무것도 하지 않고 침묵하지 마세요.
만약 자식 클래스가 빈 메서드를 가지고 있거나 stub 구현을 하거나 "지원되지 않음"을 던진다면 — 그것은 리스코프 침해입니다.
I — 인터페이스 분리 원칙
"클라이언트는 사용하지 않는 인터페이스에 의존하도록 강요될 수 없습니다."
클라이언트는 클래스가 필요하지 않은 메서드를 구현하도록 강요하는 큰 인터페이스를 만들지 마세요. 인터페이스를더 집중적인 작은 부분으로 나누세요.
🍴레스토랑의 각 고객에게 스테이크봉, 스튜숟, 찢어먹는 도끼, 폰두 스푼을 주지 마세요. 그들은 단순히 스튜만 주문했을 때.
더 작은 인터페이스는 더 좁은 종속성 = 지속적으로 로컬화된 변경
D — 종속성 반전 원칙
"고수준 모듈은 낮은 수준의 모듈에 의존해서는 안 됩니다. 둘 다 추상화에 의존해야 합니다."
비즈니스 로직은 구체적인 구현과 직접적으로 관련되어서는 안 됩니다. 대신 인터페이스와 통신해야 합니다. — 낮은 수준의 단일 상세 구현으로 인터페이스를 구현합니다.
🤖 스테이플러를 로봇의 손에 실드하지 마세요. 표준 커넥터를 손에 넣고, 도구들을 교체할 수 있도록 해주세요.
이것이 인프라스트럭처(데이터베이스, API, 파일 시스템)를 변경할 수 있게 하는 것이며, 비즈니스 로직을 건드리지 않아도 됩니다.
요약: SOLID 간단히
| 원칙 | 해결해야 할 문제 | 핵심 질문 |
|---|---|---|
| SRP | 클래스가 너무 많은 일을 처리 | "이 클래스가 여러 스테이크홀더 그룹을 서비스할까요?" |
| OCP | 기존 코드를 수정하여 기능을 추가 | "이를 확장으로 추가할 수 있을까요, 수정 대신?" |
| LSP | 파괴된 계층적 상속 | "아이가 부모를 대체할 수 있을까요?" |
| ISP | 과부하된 인터페이스와 사용되지 않는 메서드 | "이 클래스가 필요하지 않은 것을 구현하도록 강제될까요?" |
| DIP | 비즈니스 로직이 인프라에 결합되어 있습니다 | "고수준 코드가 구체적인 클래스에 의존해야 하는가요?" |
어디서부터 시작해야 할까요?
이것을 처음으로 적용한다면:
- SRP로 시작하세요 — "너무 많은 일을 하고 있는" 클래스를 찾아 분리하세요
- LSP 위반을 확인하세요 — 빈 메서드는 그것을 알리는 신호입니다
- 인프라의 경계에 인터페이스를 추가 — 이것이 실제 운영에서의 DIP
이러한 원칙은 체크리스트가 아닙니다. 코드를 작성할 때마다 반드시 제기해야 할 질문입니다.
원래 게시처: sanudin.dev











