OOP 와 함수형 — 두 시각의 공존
OOP 와 함수형 — 두 시각의 공존
"객체지향이냐 함수형이냐" 라는 질문은 한때 진영 다툼처럼 다뤄졌습니다. 오늘날의 모던 언어는 둘을 함께 지원하고, 실무 코드는 두 패러다임을 자연스레 섞어 씁니다. 이 글은 두 패러다임의 출자 · 핵심 원칙 · 모던 언어가 둘을 어떻게 흡수했는지 · 실무에서 어느 자리에 어느 시각이 잘 맞는지.
1. OOP 와 FP 에 대한 이야기
OOP — 가장 자주 인용되는 출자는 1972 년 Alan Kay 가 Xerox PARC 에서 만든 Smalltalk. 그 전에 1967 년 Simula 67 (Ole-Johan Dahl · Kristen Nygaard) 이 클래스 · 상속 같은 핵심 개념을 도입했고, Smalltalk 가 "메시지 전달" 이라는 모델을 명확히. Alan Kay 본인이 "객체지향이라 부를 때 핵심은 메시지 전달이지 클래스가 아니다" 라고 한 발언이 자주 인용. 대중화는 1980 년대 C++ (1985 년 Bjarne Stroustrup), 1995 년 Java 가 결정적.
FP — 뿌리는 1958 년의 Lisp (John McCarthy). 람다 계산 (1930 년대 Alonzo Church) 을 모델로 한 첫 실용 언어. 이후 ML (1973) · Miranda (1985) · Haskell (1990) 이 순수 함수형의 형태를 다듬었고, Erlang (1986) 이 동시성 · 내고장성 자리에서 자리 잡음. 대중화는 2010 년대 멀티코어 시대에 부수효과 없는 코드의 가치가 재조명되며 진행.
2. OOP 의 4 원칙
| 원칙 | 의미 |
|---|---|
| 캡슐화 | 데이터와 그 위에 작용하는 행동을 한 객체에 묶음 |
| 상속 | 부모의 정의를 자식이 물려받아 확장 |
| 다형성 | 같은 인터페이스에 다른 구현 |
| 추상화 | 복잡한 내부를 간단한 인터페이스로 가림 |
// Java 예
abstract class Animal {
abstract String speak();
}
class Dog extends Animal {
@Override String speak() { return "멍멍"; }
}
class Cat extends Animal {
@Override String speak() { return "야옹"; }
}
Animal a = new Dog();
a.speak(); // "멍멍" ← 다형성
상속은 처음 익히면 강력해 보이지만, 깊은 상속 트리는 결합을 늘려 유지가 힘들다는 비판이 누적되어 2000 년대부터는 "상속보다 합성 (composition over inheritance)" 이 자주 권장 (GoF 책에도 명시).
3. 함수형의 핵심 개념
| 개념 | 의미 |
|---|---|
| 순수 함수 | 같은 입력에 항상 같은 출력. 외부 상태 변경 없음 |
| 불변성 | 데이터는 만든 후 바뀌지 않음. 변경 = 새 값 생성 |
| 1 급 함수 | 함수도 값. 인자로 전달, 반환, 변수에 저장 |
| 고차 함수 | 함수를 받거나 반환하는 함수 |
| 참조 투명성 | 식을 그 결과로 치환해도 의미 불변 |
| 재귀 | 반복 대신 재귀로 표현 (꼬리 재귀 최적화 포함) |
// 명령형
const xs = [1, 2, 3, 4];
let sum = 0;
for (let i = 0; i < xs.length; i++) sum += xs[i] * 2;
// 함수형
const sum2 = xs.map(x => x * 2).reduce((acc, x) => acc + x, 0);
map · filter · reduce 는 함수형 사고가 명령형 언어에 흡수된 가장 친숙한 자리.
4. 두 시각의 비교
| 항목 | OOP | FP |
|---|---|---|
| 상태 | 객체에 캡슐화 | 가능한 한 불변 |
| 동작 | 객체의 메서드 | 자유로운 함수 |
| 결합 | 클래스 계층 | 함수 합성 |
| 부수효과 | 자연스러움 | 격리 권장 |
| 동시성 | 락 · 뮤텍스 필요 | 불변성 덕에 자연스러움 |
| 모델링 | 도메인을 객체로 | 도메인을 데이터 + 변환으로 |
어느 한쪽이 절대 우월하지 않습니다. 같은 문제에 다른 모양이 자연스러운 자리가 있음. UI · 도메인 모델 · 시뮬레이션은 OOP, 데이터 변환 · 동시성 · 검증은 FP 가 자연스럽다는 평이 자주.
5. 모던 언어의 공존
| 언어 | 출자 | 양쪽 지원 |
|---|---|---|
| Scala | 2003 | JVM 위 OOP + FP 융합 |
| Kotlin | 2011 | OOP 기본, FP 친화 |
| Rust | 2010 | OOP 없음. 트레잇 · 소유권. FP 영향 큼 |
| Swift | 2014 | OOP + 프로토콜 · 값 타입 · 고차함수 |
| TypeScript | 2012 | 클래스 + 1 급 함수 + 불변 권장 |
| Java (8+) | 2014 | 람다 · Stream API 도입 |
| Python | 1991 | 클래스 · 1 급 함수 · 내포 |
| C# | 2002 | 람다 · LINQ · 레코드 타입 |
신규 언어 다수가 "OOP 만" 또는 "FP 만" 이 아니라 "양쪽" 의 좋은 부분을 담는 방향. 패러다임 자체가 더 이상 진영의 깃발이 아닙니다.
6. 다른 길
OOP / FP 외의 패러다임:
- 명령형 (Imperative) — 가장 오래된 갈래. C 가 대표.
- 선언형 (Declarative) — "무엇을" 만 표현. SQL · HTML · 정규식.
- 로직 프로그래밍 — Prolog. 사실과 규칙으로 추론.
- 데이터 흐름 / Reactive — RxJS · Akka Streams. 시간에 따른 데이터 변화를 그래프로.
- 액터 모델 — Erlang · Akka. 작은 액터들이 메시지로 소통.
- CSP — Go 의 채널 · 고루틴.
7. 같은 작업의 두 시각
// 사용자 목록에서 활성 사용자 이름만, 알파벳 순으로
// OOP 풍 (가상)
class UserService {
constructor(users) { this.users = users; }
getActiveSortedNames() {
const active = [];
for (const u of this.users) if (u.active) active.push(u);
active.sort((a, b) => a.name.localeCompare(b.name));
return active.map(u => u.name);
}
}
// FP 풍
const getActiveSortedNames = users =>
users
.filter(u => u.active)
.map(u => u.name)
.sort((a, b) => a.localeCompare(b));
두 코드 모두 같은 결과지만 후자가 짧고 부수효과가 없음.
// React 컴포넌트 — 두 패러다임이 자연스레 만나는 자리
function UserList({ users }) {
const visible = users.filter(u => u.active).map(u => u.name); // FP
return <ul>{visible.map(n => <li key={n}>{n}</li>)}</ul>; // 컴포넌트는 OOP 적
}
8. 자주 걸리는 자리
상속의 함정 — 깊은 상속 트리는 변경 비용이 폭증. 합성 또는 인터페이스 / 트레잇이 자주 더 나음.
불변성의 비용 — 큰 자료를 매번 복사하면 성능이 나빠질 수 있음. Immer · Immutable.js · 영속 자료구조가 도움.
순수 강박 — 모든 코드를 순수 함수로만 짜려 하면 I / O · 외부 통신 자리에서 코드가 비비 꼬임. "경계에 부수효과를 모은다" 정도가 실용적.
OOP 강박 — 모든 것을 클래스로 모델링. 단순한 함수면 될 자리에 5 줄짜리 빈 클래스가.
모나드 공포증 — Haskell 식 용어가 처음에 무섭지만, JS 의 Promise.then 도 모나드 형태. 문법적 친숙함부터 익힘.
양쪽을 섞는 일관성 결여 — 한 코드베이스 안에서 어디는 OOP, 어디는 FP 의 기준이 흐려지면 읽기 어려움. 팀 약속 필요.
"이 언어는 OOP 가 아니다" 라는 단언 — 거의 모든 모던 언어가 양쪽을 지원. 특정 스타일이 더 자연스러운 차이일 뿐.
하고픈 말
OOP 와 FP 는 이제 진영이 아니라 같은 도구함의 두 자리. 모던 언어 거의 모두가 양쪽을 지원하고, 실무 코드는 자연스럽게 섞어 씁니다. UI · 도메인 모델 → OOP, 데이터 변환 · 검증 · 동시성 → FP. "경계에 부수효과를 모은다" 가 두 시각을 가장 잘 결합하는 한 줄.
Next
- (programming 끝)
Alan Kay The Early History of Smalltalk (1993) · Design Patterns: Elements of Reusable Object-Oriented Software (1994) · Effective Java (Joshua Bloch) · Why Functional Programming Matters (John Hughes 1990) · Structure and Interpretation of Computer Programs (SICP) · Haskell.org Learn · Out of the Tar Pit (Moseley · Marks 2006) · Composing Software (Eric Elliott) · Scala 공식 · Rust Book · Kotlin Docs 를 참고합니다.