
12장
읽기 전에 헷갈려서 정리;;
- 객체: 하나의 인스턴스/클래스를 말할 때
- OO: 설계 방식/패러다임 전체를 말할 때
데이터와 그 데이터를 다루는 함수를 하나로 묶자 - OOP의 시작점
이 아이디어가 60년간 발전하며 현대 프로그래밍의 기본이 되었다!
What Is an Object?
객체란 무엇인가?
엘런 케이는 객체를 세포에 비유하기도 했다
세포는 서로 분자 메시지를 주고받지만,
서로의 내부 구조를 모른다.
자신이 통신하는 특정 세포가 무엇인지조차 모를 수 있다.
그저 다른 세포들에게 메시지를 보내기만 한다.
(원문을 생명과학 전공 친구에게 보여줬음: 뭐야 세포탈트붕괴와;;)
OO와 관계형데이터베이스(RDB)는 정반대 철학을 가진다
| OO | 데이터베이스(RDB) | |
| 초점 | 행위(함수) | 데이터(스키마) |
| 숨기는 것 | 데이터 | 없음 |
| 노출하는 것 | 공개 함수 | 테이블 구조 |
| 종속 관계 | 외부가 객체의 행위에 의존 | 프로그램이 스키마에 종속 |
객체는 데이터는 숨기고 "무엇을 할 수 있는지" 행위만 공개한다
DB는 데이터 구조를 공개하고, 이를 사용하는 모든 프로그램은 그에 종속된다.
Data Abstraction
자료 추상화
가짜 추상화와 진짜 추상화...
// ❌ 가짜: private이지만 구현이 그대로 노출됨
class Point {
private x: number;
private y: number;
getX() { return this.x; }
setX(x: number) { this.x = x; }
// → 직교좌표(x,y)로 구현됐음이 뻔함
}
// ✅ 진짜: 내부가 어떻게 생겼는지 알 수 없음
interface Point {
getX(): number;
getY(): number;
getR(): number; // 극좌표로도 접근 가능
getTheta(): number;
setCartesian(x, y): void; // 반드시 쌍으로 설정
setPolar(r, theta): void;
}
// → 직교좌표? 극좌표? 둘 다? 알 수 없음!
구현을 숨기는 것은 단순히 변수와 함수 사이에 함수 계층을 두는 게 아님!
사용자가 데이터의 본질을 조작할 수 있게 하되,
그 데이터가 어떻게 구현되어 있는지 알 필요 없는 추상 인터페이스를 노출함
// 내부 변수가 무엇인지 뻔히 드러남
// gallon 단위에 종속됨
// 가솔린 차에 종속 - 전기차로 바뀌면 대응 x
public interface Vehicle {
double getFuelTankCapacityInGallons();
double getGallonsOfGasoline();
}
// 내부 구조 알 수 없음. 전기차로 바뀌어도 대응 가능
public interface Vehicle {
double getPercentFuelRemaining();
}
단순히 모든 필드에 getter/setter 추가한다고 캡슐화가 아님
'이 객체가 무엇을 할 수 있는가?'를 노출하고,
'이 객체가 어떤 데이터를 가지고 있는가?'를 숨겨야 한다.
이렇게 하면 '사용자에게 어떤 추상적 개념을 제공할지'가 명확해진다.
Data/Object Antisymmetry
자료/객체 비대칭
객체는 추상화 뒤에 데이터를 숨기고, 그 데이터를 조작하는 함수를 노출한다
자료 구조는 데이터를 노출하고, 비즈니스 로직이나 액션을 수행하는 메서드가 없다
절차적 프로그래밍 (Procedural Programming)은
함수(프로시저)가 중심이 되어 자료구조를 외부에서 조작하는 방식이다
절차적 프로그래밍은 새 함수 추가는 쉽지만, 새 타입 추가는 어렵다
----새 함수 추가----
// 자료 구조 (데이터만)
interface Square { side: number; }
interface Circle { radius: number; }
// 함수 추가 = Geometry만 수정
class Geometry {
area(shape) { /* ... */ }
perimeter(shape) { /* ... */ } // ✅ 추가 쉬움
draw(shape) { /* ... */ } // ✅ 추가 쉬움
}
// Square, Circle은 전혀 안 건드림!
---- 새 타입 추가----
// Triangle 추가하려면?
// ❌ area() 수정
// ❌ perimeter() 수정
// ❌ draw() 수정
// → 함수가 10개면 10개 전부 수정!
OO는 새 타입은 추가는 쉽지만, 새 함수 추가는 어렵다
----새 타입 추가----
interface Shape { area(): number; }
class Square implements Shape { area() { /*...*/ } }
class Circle implements Shape { area() { /*...*/ } }
class Triangle implements Shape { area() { /*...*/ } } // ✅ 추가 쉬움
// 기존 코드 전혀 안 건드림!
----새 함수 추가----
interface Shape {
area(): number;
perimeter(): number; // ❌ 인터페이스 수정
}
// ❌ Square 수정
// ❌ Circle 수정
// ❌ Triangle 수정
// → 클래스가 10개면 10개 전부 수정!
모든 것이 반드시 객체여야 한다는 것은 아니다.
새 타입이 자주 추가된다면 객체,
새 함수가 자주 추가된다면 절차적 코드를 사용한다.
변경의 방향을 예측해서 적절한 도구를 선택하면 된다.
The Law of Demeter
디미터 법칙
객체 내부를 파고들어 탐색하지 말고, 직접 아는 객체에게만 요청하라
(Be shy, talk to friends, and don't talk to strangers.)
기차 충돌
// ❌ 디미터 법칙 위반 - 기차 충돌
const outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
// ↑ ↑ ↑
// 친구 낯선 사람 더 낯선 사람
이 코드의 문제: 한 함수가 너무 많은 객체의 내부 구조를 알고 있음
ctxt → Options → ScratchDir → absolutePath
(알아야 함) (왜 알아야 해?) (이것까지?)
객체 vs 자료 구조에서 차이
// 자료 구조라면: 괜찮음 (데이터 노출이 목적)
const outputDir = ctxt.options.scratchDir.absolutePath;
// 객체라면: 위반! (내부를 숨겨야 함)
const outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
잡종(Hybrid)의 문제
// ❌ 잡종: 객체도 자료 구조도 아닌 최악의 형태
class User {
// 자료 구조처럼 데이터 노출
getName(): string { return this.name; }
setName(name: string) { this.name = name; }
getAge(): number { return this.age; }
setAge(age: number) { this.age = age; }
// 객체처럼 행위도 있음
calculateBonus(): number { /* ... */ }
}
// → 새 함수 추가도 어렵고, 새 타입 추가도 어려움
// → 양쪽의 최악만 모음
해결책: Tell, Don't Ask
// ❌ 질문하고 직접 처리 (Ask)
const dir = ctxt.getOptions().getScratchDir().getAbsolutePath();
const outFile = dir + "/" + className + ".class";
const stream = new FileOutputStream(outFile);
// ✅ 그냥 시키기 (Tell)
const stream = ctxt.createScratchFileStream(classFileName);
// ctxt가 알아서 내부적으로 처리
// 우리는 내부 구조를 몰라도 됨
OO를 한 문장으로 요약하면:
Keep it DRY, Shy, and Tell the Other Guy
- DRY: 반복하지 마라
- Shy: 디미터 법칙 - 내부를 노출하지 마라
- Tell the Other Guy: 질문하지 말고 명령하라
Data Transfer Objects
자료 전송 객체
ORM은 객체를 만들지 않는다.
자료 구조를 로드할 뿐이다.
객체 vs 자료 구조 선택은 '무엇이 더 자주 변하는가?'에 달려있다
DTO(Data Transfer Object)
// DTO: 그냥 데이터 담는 그릇
interface UserDTO {
id: number;
name: string;
email: string;
}
// Java Bean 스타일 (불필요한 복잡함)
class UserDTO {
private name: string;
getName() { return this.name; }
setName(name: string) { this.name = name; }
// → getter/setter가 캡슐화처럼 보이지만 실제 이점 없음
// → 그냥 public 필드로 해도 됨
}
ORM은 테이블을 자료 구조로 변환할 수 있다.
그러나, ORM은 진짜 '객체'를 생성할 수 없다
왜냐하면 '객체와 자료구조는 orthogonal(직교)하기 때문이다
완전히 다른 개념이라 맵핑이 불가능
이 둘 중 어느 것을 사용할지는 '무엇이 더 자주 변하는가?'를 기준으로 한다
| 상황 | 변하는 것 | 안정적인 것 | 선택 |
| 기업 DB | 비즈니스 규칙(행위) | DB 스키마(구조) | 절차적 |
| IDE | 언어별 구조(데이터) | 편집 기능(행위) | OO |
대기업의 데이터 베이스 구조는 비교적 정적이다.
새 레코드가 추가되고, 오래된 레코드가 삭제되면서 데이터는 변경될 수 있지만, 구조가 변경되는 경우는 드물다.
하지만, 이 데이터 베이스를 사용하는 프로그램들의 행위(비즈니스 규칙)는 자주 변경된다.
-> 자료 구조 노출 + 함수 추가가 쉬운 절차적 코딩 선택
IDE의 행위(텍스트 파일 편집)는 비교적 정적이다.
하지만 데이터의 구조는 새 언어가 기능에 추가될 때마다 변경된다.
-> 행위 노출 + 새 타입 추가가 쉬운 OO 스타일 선택
휘발성(자주 변하는 것)을 숨기고
정적인 것(안 변하는 것)을 노출하라
Switch Statements
OO는 항상 정답이 아니다.
switch문이 더 나을 때도 있다. (스위치문 극혐하는 저자가 이런 말을?!)
무엇이 변할 것인가?에 따라 선택해야 한다.
Switch 문의 문제점
// Triangle 추가하려면?
// ❌ enum 수정
// ❌ renderShapes의 switch 수정
// ❌ dragShapes의 switch 수정
// ❌ eraseShapes의 switch 수정
// ❌ rotateShapes의 switch 수정
스위치문에 새 타입을 추가한다면
→ 모든 switch문을 찾아서 수정해야 함!
→ 하나라도 놓치면 버그
하지만, 이런 타입추가보다 더 큰 문제는
독립 배포가 불가능하다는 것이다
만약 ShapeManager는 무료, 도형은 유료로 팔고 싶다고 하자.
하지만, Circle/Square가 모든 switch문에 하드코딩되어 있어서 분리할 수 없다.
ShapeManager 코드 안에:
switch(CIRCLE) → Circle 로직 포함
switch(SQUARE) → Square 로직 포함
→ ShapeManager만 따로 줄 수 없음
→ 모든 게 한 덩어리로 묶여있음
OO로 해결해보자
interface Shape {
render(): void;
}
class Circle implements Shape {
render() { /* ... */ }
}
class Square implements Shape {
render() { /* ... */ }
}
// Triangle 추가? 클래스 하나만 만들면 끝!
class Triangle implements Shape {
render() { /* ... */ }
}
// 기존 코드 수정 없음 ✅
// 독립 배포 가능 ✅
ShapeManager는 Shape 인터페이스만 알고 Circle/Square를 모른다
각각 독립된 패키지로 분리할 수 있고, 따로 판매도 할 수 있다
하지만, OO도 만능은 아니다
// 새 함수 추가 요청: renderDropShadow()
// OO에서는?
interface Shape {
render(): void;
renderDropShadow(): void; // ❌ 인터페이스 수정
}
// ❌ Circle 수정
// ❌ Square 수정
// ❌ Triangle 수정
// → 모든 클래스 수정!
// 절차적(switch)에서는?
class ShapeManager {
renderDropShadow(shape) { /* switch문 하나 추가 */ } // ✅ 여기만
}
// Circle, Square 안 건드림!
만약 고객이 새 도형이 아니라 drop-shadow를 렌더링하는 shape의 새 메서드를 요청했다고 하면
모든 단일 클래스를 건드려야 한다
Shape, Circle, Square는 모두 renderDropShadow 메서드를 추가해야 한다
하지만, 만약 switch 문이었다면
그냥 ShapeManager 클래스에 switch 문 하나 추가하는 게 전부였을 것
아까도 나온 말인 것 같지만
맹목적으로 OO를 쓰지 말고, 변경의 방향을 예측해서 선택하라는 뜻
Switch문 새 타입 추가에는 모든 함수를 수정해야하지만, 새 함수 추가는 간단함
OO는 새 타입 추가에 클래스 하나만 추가하면 되지만, 새 함수 추가는 모든 클래스를 수정해야 함
The OO/Procedural Trade-Off
OO vs 절차적
뭐가 더 좋냐가 아니라, 뭐가 더 자주 변하냐의 문제임
새 타입 추가에는 OO가 좋음 (클래스 하나만 추가)
새 함수 추가에는 절차적(switch 문)이 좋음 (함수 하나만 추가하면 됨)
저자는 개인적으로 OO를 선호하고 (그래보여요...)
변경을 새 타입으로 해석하는 설계(OO에 유리하도록)를 만든다고 한다
switch 문 사용 규칙
// ✅ Switch는 시스템 주변부(진입점)에만
// ✅ 타입당 최대 하나만
// ✅ 다형적 객체를 생성하는 용도로만
function createShape(type: string): Shape { // 팩토리
switch (type) {
case 'circle': return new Circle();
case 'square': return new Square();
}
}
// 이후로는 다형성 사용
shape.render(); // switch 없음
shape.drag(); // switch 없음
둥근 못(OO)을 사각형 구멍(절차적)에 강제로 끼우지 마라
맹목적으로 OO를 고집하거나, switch를 무조건 피하는 것 둘 다 잘못된 접근
But What About Performance?
Switch가 OO보다 약간 빠름
// Switch: 컴파일러가 점프 테이블로 최적화 가능
switch (type) {
case 0: /* ... */ break;
case 1: /* ... */ break;
case 2: /* ... */ break;
}
// → 메모리 한 번 접근으로 바로 점프
// 다형성: 가상 함수 테이블(vtable) 조회 필요
shape.render();
// → 객체 → vtable → 함수 주소 → 호출
// → 간접 참조가 더 많음
하지만, 나노 초 차이이므로 일반적인 코드 상황은 무관
성능이 크리티컬한 내부 루프에서나 고려해볼만한 문제
몇몇 시간에 민감한 내부 루프가 switch를 써야 한다고 해서
전체 시스템에서 OO를 포기하지 마라!
Conclusion
객체는 행위를 노출하고 데이터를 숨긴다.
OO는 기존 행위를 변경하지 않고, 새로운 종류의 객체를 추가하기 쉽게 만든다.
기존 객체에 새 행위를 추가하는 건 어렵게 만든다.
자료 구조는 데이터를 노출하고, 비즈니스 로직이나 액션을 수행하는 메서드가 없다.
기존 자료 구조에 새 액션을 추가하기 쉽다.
기존 함수에 새 자료 구조를 추가하기 어렵다.
각자 장점이 최대한 발휘될 수 있는 상황에 적절히 사용하면 된다.
좋은 sw 개발자들은 편견 없이 이런 문제들을 이해하고, 당연한 작업에 가장 좋은 접근 방식을 선택한다.
저자가 OOP 이야기를 하도 본문에서 많이 해왔는데
이 장이 그 총집합체라는 느낌이 듦
작성 시작: 2026.02.03. 21:30
작성 종료: 2026.02.04. 00:08
'개발자 강화 > 개발 독서' 카테고리의 다른 글
| [Clean Code 2판] 1-14: Testing Disciplines (0) | 2026.02.25 |
|---|---|
| [Clean Code 2판] 1-13: Clean Classes (1) | 2026.02.23 |
| [Clean Code 2판] 1-11: Be Polite (0) | 2026.02.02 |
| [Clean Code 2판] 1-10: One Thing (0) | 2026.01.25 |
| [Clean Code 2판] 1-9: The Clean Method (0) | 2026.01.12 |