OOP의 탄생 배경과 핵심 개념
요약
본 글은 객체 지향 프로그래밍(OOP)의 탄생 배경과 핵심 개념을 설명합니다. OOP는 전역 데이터 조작으로 인한 버그와 코드 재사용성 문제를 해결하기 위해 등장했으며, 세상을 상호작용하는 사물들의 집합으로 모델링하려는 시도에서 비롯되었습니다. 핵심 기둥인 캡슐화, 추상화, 상속, 다형성을 통해 시스템의 안정성과 확장성을 확보하는 방법을 제시합니다.
핵심 포인트
- OOP는 전역 데이터 문제와 코드 재사용성 문제를 해결하기 위해 등장했습니다.
- 객체 지향은 '클래스/상속'과 '메시징(messaging)' 두 가지 관점이 있습니다.
- 캡슐화는 상태를 숨기고 불변성을 강제하여 안정성을 높입니다.
- 다형성은 하나의 인터페이스로 여러 구현체를 처리하며 확장성을 보장합니다.
1. OOP가 해결하려 했던 문제
1950년대 후반에서 1960년대 초반, 프로그램은 공유된 전역 데이터(global data)를 조작하는 긴 일련의 절차(procedures)로 작성되었습니다. 프로그램이 커지면서 다음과 같은 문제가 발생했습니다:
- 어떤 함수라도 어떤 데이터를든 변경할 수 있었기 때문에 버그가 전체 코드베이스에 퍼졌습니다.
- 실제 세계의 사물(선박, 고객, 은행 계좌 등)은 코드 내에 단일한 위치를 가지고 있지 않았고, 데이터와 동작이 여기저기 흩어져 있었습니다.
- 코드를 재사용한다는 것은 복사-붙여넣기(copy-paste)를 의미했습니다.
연구자들은 세상을 서로 대화하는 자체 포함적인 사물들의 집합으로 모델링할 방법을 원했습니다. 이 아이디어가 객체 지향 프로그래밍(object-oriented programming, OOP)이 되었습니다.
2. 타임라인: OOP가 발명된 과정
| 연도 | 마일스톤 | 누가 | 중요성 |
|---|---|---|---|
| 1963 | Sketchpad | Ivan Sutherland (MIT) | '마스터' 그림과 '인스턴스(instances)'를 가진 그래픽 시스템으로, 클래스/객체의 초기 형태입니다. |
| ... |
OOP의 두 가지 학파
- Simula / C++ / Java 학파 — 객체 지향을 _클래스와 상속(inheritance)_으로 간주: 타입을 정의하고 계층 구조를 구축합니다.
- Smalltalk / Alan Kay 학파 — 객체 지향을 _메시지를 보내는 객체(objects sending messages)_로 간주: 상태를 숨긴 독립적인 행위자(actors)입니다.
Kay는 나중에 큰 아이디어는 클래스가 아니라 **메시징(messaging)**이라고 말했습니다. 이 관점은 놀라울 정도로 현대의 마이크로서비스(microservices) 및 액터 시스템(actor systems)(Erlang, Akka)과 가깝습니다.
3. 네 가지 기둥 (The four pillars)
3.1 캡슐화(Encapsulation) — 상태는 숨기고 동작은 노출하기
class BankAccount {
#balance = 0; // private: 외부에서 직접 접근할 수 없음
...
불변성(Invariants, 잔액이 유효하지 않게 되는 것을 방지)은 한 곳에서 강제됩니다.
3.2 추상화(Abstraction) — '무엇'을 노출하고 '어떻게'는 숨기기
interface PaymentGateway {
charge(userId: string, amount: number): Promise<string>;
}
호출자들은 Razorpay/Stripe의 세부 사항이 아닌 계약(contract)에 의존합니다.
3.3 상속(Inheritance) — 재사용 및 특수화
class Notification {
constructor(protected to: string) {}
send() { /* 공통 로깅, 재시도 */ }
...
유용하지만 과도하게 사용하기 쉬우므로(
유용하지만 과도하게 사용하기 쉬우므로(
3.4 다형성(Polymorphism) — 하나의 인터페이스, 여러 구현체
const channels: Notification[] = [new EmailNotification("[email protected]"), new SmsNotification("+91...")];
channels.forEach(c => c.send()); // 각자 자신의 일을 수행함
호출자는 타입에 대한 if/else가 필요하지 않으며, 새로운 타입은 기존 코드를 변경하지 않고 플러그인 방식으로 추가될 수 있습니다.
4. OOP가 시스템 설계의 구성 요소인 이유
시스템 설계는 두 가지 레벨로 나뉩니다:
- 고수준 설계(High-Level Design, HLD): 서비스(services), 데이터베이스(databases), 메시지 큐(queues), 캐시(caches), 로드 밸런서(load balancers).
- 저수준 설계(Low-Level Design, LLD): 각 서비스 내부의 클래스(classes), 인터페이스(interfaces), 관계(relationships), 패턴(patterns).
OOP는 LLD의 언어이며, 그 아이디어는 HLD까지 확장될 수 있습니다.
4.1 객체와 도메인 개념 매핑하기
주차장, Uber, 또는 LMS를 설계하는 것은 명사(nouns)에서 시작하여 클래스(classes)로 만듭니다:
User, Driver, Ride, Payment, Location
Course, Module, Lesson, Quiz, Attempt, Progress
동사(verbs)는 메서드(methods)가 됩니다: ride.start(), quiz.submit(), payment.refund().
4.2 관계가 구조를 정의한다
| 관계 | 의미 | 예시 |
|---|---|---|
| 연관(Association) | 사용함 / 알고 있음 (uses / knows about) | Driver — Ride |
| ... |
4.3 SOLID — 시스템을 변경 가능하게 유지하는 규칙
| 원칙 | 한 줄 요약 의미 | 시스템 설계의 이점 |
|---|---|---|
| S — 단일 책임(Single Responsibility) | 클래스는 변경해야 할 이유가 하나여야 함 | 작고 테스트 가능한 단위; "하나의 서비스, 하나의 역할"을 반영함 |
| ... |
// 실제 적용된 의존성 역전 (Dependency Inversion)
class OrderService {
constructor(
...}
### 4.4 상속보다는 합성(Composition over inheritance)
깊은 상속 트리는 경직됩니다. 현대적인 설계는 동작을 **합성(composing)**하는 것을 선호합니다:
class Car {
constructor(private engine: Engine, private gps: Navigator) {}
}
새로운 서브클래스 없이 `PetrolEngine`를 `ElectricEngine`으로 교체할 수 있습니다.
### 4.5 디자인 패턴 — 재사용 가능한 OOP 솔루션
### 4.5 디자인 패턴 — 재사용 가능한 OOP 솔루션
| 카테고리 | 패턴 | 일반적인 사용 사례 |
| :--- | :--- | :--- |
| 생성(Creational) | **Factory**, **Builder**, **Singleton** | 알림 채널 생성, 복잡한 쿼리 구축, 단일 DB 풀 관리 |
| ... |
### 4.6 같은 아이디어는 HLD에도 적용됩니다
| OOP 개념 | 분산 시스템 등가물 |
| :--- | :--- |
| 객체(Object) | 마이크로서비스(Microservice) / 액터(actor) |
| ... |
이것이 인터뷰어들이 LLD 질문(주차장, 엘리베이터, BookMyShow 등)을 하는 이유입니다. 즉, HLD가 요구하는 것과 동일한 사고방식, 즉 객체, 계약(contract), 책임(responsibility)에 따라 생각할 수 있는지 테스트하기 위함입니다.
## 5. 비판 및 균형 잡기
- **과잉 설계(Over-engineering):** 간단한 문제에 너무 많은 계층, Factory, 추상 클래스를 사용하는 경우.
- **상속 오용(Inheritance misuse):** 취약한 기본 클래스(fragile base classes), 깊은 계층 구조.
- **공유 가변 상태(Shared mutable state):** 객체는 여전히 상태를 변경하며, 동시성 처리가 어려워집니다.
- **함수형 프로그래밍(Functional programming)** (불변성(immutability), 순수 함수(pure functions))가 이에 반기를 들었으며, 대부분의 현대 코드(TypeScript, Kotlin, Rust, Swift)는 **다중 패러다임(multi-paradigm)**입니다. 구조를 위해 객체를 사용하고, 변환을 위해 함수를 사용하는 식입니다.
일반적인 규칙: **OOP는 경계와 계약을 그리는 데 사용하고; 그 경계 내부의 로직은 단순하게 유지하세요.**
## 6. 빠른 LLD 레시피
1. 요구사항과 사용 사례를 명확히 합니다.
2. 명사(nouns)를 추출하여 후보 클래스를 만들고, 동사(verbs)를 메서드로 만듭니다.
3. 경계(저장소, 결제, 알림 등)에서 인터페이스를 정의합니다.
4. 관계(클래스 다이어그램)를 그립니다.
5. SOLID 원칙을 적용하고; 실제 복잡성을 제거하는 곳에만 패턴을 선택적으로 적용합니다.
6. 동시성, 오류 처리, 확장성을 고려합니다.
7. 하나의 사용 사례를 처음부터 끝까지 따라가 봅니다.
## 7. 요약
- **발명 역사:** 노르웨이의 **Dahl & Nygaard**가 1967년에 Simula를 발명한 것이 최초의 OOP 언어였으며, **Alan Kay**는 '객체 지향(object-oriented)'이라는 용어를 만들고 1970년대 Xerox PARC에서 Smalltalk을 개발했습니다. 이후 **C++ (1983)**와 **Java (1995)**가 이를 주류로 만들었습니다.
- **핵심 개념:** 캡슐화(encapsulation), 추상화(abstraction), 상속(inheritance), 다형성(polymorphism).
- **시스템 설계에 중요한 이유:** OOP는 '책임이 있는 컴포넌트들이 계약을 통해 통신한다'는 어휘를 제공하며, 이는 저수준(low-level) 및 고수준(high-level) 설계가 다루는 핵심 내용과 정확히 일치합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기