출발점
OOP를 학교에서 배운 직후엔 그 가치가 잘 보이지 않는다. Rectangle, Animal, Shape 같은 예제로는 캡슐화·다형성의 진짜 효용을 체감하기 어렵다. "내가 아직 복잡한 시스템까지 안 만들어봐서 그런가?"라는 자가진단은 절반은 맞고 절반은 틀리다 — 이 글에서 그걸 풀어본다.
OOP가 1990~2000년대를 지배한 이유
OOP의 진짜 적은 "코드의 알고리즘적 복잡도"가 아니라 여러 사람이 협업하는 대규모 코드베이스의 구조화 문제였다. 1990년대 초 Java가 등장하던 시점, enterprise software 업계가 풀려고 했던 문제가 그것이다 — 50명이 한 코드베이스를 만지는데 어떻게 한 사람의 변경이 다른 사람을 안 깨뜨릴 것인가.
OOP가 제시한 답은 셋이다.
데이터와 행동의 묶음. "이 데이터로 뭘 할 수 있는가"를 한 곳에서 찾을 수 있게 한다. Procedural 시대에 함수가 사방에 흩어져있던 문제를 해결.
Abstraction boundary로서의 interface. 다른 사람이 내 코드를 쓸 때 구현 디테일을 안 봐도 되게. interface만 보고 의지하니까 내가 내부를 바꿔도 안 깨진다. 캡슐화의 진짜 가치는 협업에서 나타난다.
다형성을 통한 plug-and-play. 같은 interface 뒤에 여러 구현을 둘 수 있어 디자인 패턴(Strategy, Observer, Factory 등)의 기반이 됐다. 런타임에 행동을 바꿔낄 수 있다는 게 거대 시스템에선 강력했다.
여기에 상속이 코드 재사용 메커니즘으로 더해졌고 — 당시엔 그게 강력해 보였다. UML, GoF 디자인 패턴, J2EE 같은 게 다 이 시기의 산물이다.
2010년대 — OOP의 한계가 드러나다
OOP가 2010년대에 흐름을 바꾼 게 아니라, 2010년대에 OOP의 한계가 누적된 경험으로 드러나기 시작했다. 동시에 일어난 변화들.
멀티코어와 분산 시스템. 2000년대 후반부터 CPU가 코어 수를 늘리는 방향으로 진화했고, 클라우드가 분산 시스템을 일반화했다. OOP의 default 디자인인 "공유 가변 상태(shared mutable state)"가 동시성 환경에서 재앙이라는 게 분명해졌다 — 데이터 레이스, 락 경쟁, 데드락. immutability + message passing이 더 잘 맞는 모델이라는 인식.
상속의 진짜 비용. Fragile base class, 다이아몬드 문제, 잘못된 추상화 강요. "Prefer composition over inheritance"는 GoF 시절(1994)부터 있던 격언이지만 2010년대에 와서 강하게 받아들여졌다. 2010년대에 등장한 주요 시스템/모바일 언어들 — Go(2009), Rust(2015 안정화), Swift(2014), Kotlin(2011) — 이 하나같이 상속을 빼거나 약화시켰다.
Enterprise OOP의 자기풍자. AbstractFactoryBuilderManagerImpl 같은 거대 클래스 계층이 농담거리가 됐다. Steve Yegge의 "Execution in the Kingdom of Nouns"(2006)가 OOP가 모든 것을 명사화해서 불필요한 복잡도를 만든다는 비판을 대표한다.
함수형 르네상스. Scala(2003), Clojure(2007), Elixir(2011)가 주목받고 JavaScript도 함수형으로 기울었다. React가 2018년에 Hooks를 발표하면서 클래스 컴포넌트를 함수 컴포넌트로 대체한 사건이 상징적이다 — "OOP 패턴이 필수가 아니라 선택"이라는 시그널.
데이터 중심 사고의 부상. 빅데이터·ML 시대에 "데이터 + 변환 함수" 모델이 OOP보다 직접적이라는 점이 부각됐다.
2020년대 트렌드
한 줄로 — OOP의 좋은 부분(캡슐화·다형성)은 가져오고 나쁜 부분(상속·가변상태)은 버린다.
- 상속 거부, composition + trait/protocol/interface로 대체 — Rust trait, Swift protocol, Go interface, Kotlin trait
- ADT(algebraic data types) + pattern matching — class 계층보다 enum/sum type이 더 잘 표현되는 경우가 많다
- immutability default — Rust
let(mut 명시), Kotlinval, Swiftlet - boilerplate 없는 데이터 컨테이너 — Java record, Kotlin data class, Python dataclass, Rust struct
- multi-paradigm 언어가 표준 — TypeScript, Kotlin, Swift, Rust. "OOP 언어"라는 분류 자체가 약해짐
- 함수 우선 디자인의 부분 채택 — React Hooks, Redux, Vue Composition API
이건 OOP의 종말이 아니라 OOP가 multi-paradigm의 일부로 흡수된 형태다. enterprise Java backend, ASP.NET 같은 거대 시스템은 여전히 OOP가 강하게 남아있다. 다만 새 언어들은 OOP를 default로 두지 않는다.
"내가 복잡할 때까지 안 만들어봐서인가?"라는 질문에
절반은 맞다. 학교 과제 수준의 예제로는 OOP의 진짜 가치(협업 boundary, plug-and-play polymorphism)가 안 드러난다. 그건 다음 같은 환경에서 나타난다.
- 5만 줄 이상의 코드베이스를 6개월 이상 유지보수
- 외부 사용자에게 라이브러리/API를 제공
- 여러 변형이 있는 도메인 모델링 (결제 수단, 인증 백엔드, DB 어댑터 등)
- GUI 프레임워크나 게임 엔진 — 다만 React/ECS가 다른 방식으로도 풀 수 있음을 보여줌
절반은 틀리다. "OOP만이 답"이라는 분위기는 더 이상 기본값이 아니다. Rust 같은 언어를 보면서 "굳이 class 형태가 아니어도 OOP의 좋은 패턴들이 다 가능하구나"라고 느낀다면, 그건 정확한 직관이고 현대 언어 디자인이 그 방향으로 가고 있다.
학교에서 Java로 OOP 배우는 건 여전히 의미 있다 — 디자인 패턴 사고, 인터페이스 분리, 의존성 주입 같은 개념은 다른 패러다임에서도 유용하다. 다만 "이게 유일한 방법"으로 받아들이지 말고 "여러 도구 중 하나"로 보는 게 현대적 시각이다.
결론
OOP는 대규모 협업 코드베이스를 모듈화하는 도구로 빛났다. 2010년대에 멀티코어·분산·함수형의 도전 속에서 "OOP만이 답"이라는 분위기가 깨졌고, 2020년대 트렌드는 OOP의 캡슐화·다형성을 trait/protocol로 흡수하면서 상속·가변상태를 거부하는 multi-paradigm이다.
학교 예제로 OOP 가치를 체감하기 어려운 건 사실이지만, "복잡해지면 OOP가 답"이라는 함정에 빠지지 않는 게 더 중요하다. 큰 코드베이스에서도 OOP는 한 가지 도구일 뿐이고, 다른 도구들도 같은 문제를 다르게 푼다.
관련: is-rust-object-oriented — Rust가 어떻게 OOP의 일부만 흡수하고 상속을 거부했는지.