캡슐화 (Encapsulation)
핵심 인사이트 (3줄 요약)
- 본질: 캡슐화는 객체의 내부 데이터(속성)와 로직(메서드)을 하나로 묶고, 외부에서 불필요한 Details를 숨기는 OOP의 핵심 특성으로, 정보 은닉(Information Hiding)을 통해 구현 변경이 사용자 코드에 영향주지 않도록 한다.
- 가치: 1970년대 David Parnas가 "信息 은닉" 개념을 제안한 이래, 캡슐화는 소프트웨어 모듈화의基石가 되었으며,大型システム wherechange 관리에 필수적이다.
- 융합: OOP의 캡슐화는 MSA의 서비스 경계, DDD의 Aggregate 경계, API의 공개/비공개 接口設計의 기반으로 적용된다.
Ⅰ. 개요 및 필요성 (Context & Necessity)
-
개념: 캡슐화는 객체 지향 프로그래밍에서 데이터(속성)와 해당 데이터를操作하는 메서드를 하나의 단위(클래스)로 묶고, 내부 구현 Details를 외부에서 접근할 수 없도록 숨기는 기법이다. 외부에서는 객체가 노출하는 공개 인터페이스(Public API)를 통해서만 객체와交互할 수 있다. 이는 정보 은닉(Information Hiding)과 동일하지만, 캡슐화가 더 넓은 개념으로 데이터와 메서드의打包을 강조한다.
-
필요성: 만약 객체의 내부 데이터를 외부에서 직접 접근할 수 있다면, 아무 검증 없이 데이터가 변경될 수 있어 객체의 불변式(Invariant)이 깨질 수 있다. 예를 들어, 은행 계좌 객체에서 잔고를 외부에서 음수로 변경할 수 있다면, 계좌는 음수 잔고라는 논리적으로 잘못된 상태가 될 수 있다. 캡슐화는 setter 메서드를 통해 항상 유효한 값만 설정하도록 하고, 내부 데이터를 외부에서 직접 변경하지 못하게 함으로써 이러한 문제를 방지한다.
-
💡 비유: 캡슐화는 은행 금고 시스템과 같습니다. 금고 내부(객체)에 무엇이 들어있는지(데이터), 어떻게 작동하는지(메서드)는 은행 직원(개발자)만 알고, 고객(사용자)은 창구(公开 인터페이스)를 통해서만取引할 수 있습니다. 고객이 직접 금고 내부에 손을 넣을 수 없습니다.
-
등장 배경 및 발전 과정:
- 1970년대 Parnas의 정보 은닉: David Parnas가 模块間의 Interface를 통해 정보 은닉의 중요성을 제시했다.
접근 제어자와 캡슐화의 관계를 시각화하면 다음과 같다.
┌─────────────────────────────────────────────────────────────────────┐ │ 접근 제어자와 캡슐화 수준 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ public class BankAccount { │ │ │ // private: 클래스 내부에서만 접근 (캡슐화의 핵심) │ │ private BigDecimal balance; │ │ │ // package-private: 동일 패키지 내에서만 접근 │ │ int accountNumber; │ │ │ // protected: 같은 패키지 + 자식 클래스에서 접근 │ │ protected String accountType; │ │ │ // public: 어디서든 접근 가능 │ │ public String bankName; │ │ │ // private 메서드: 내부 구현을 위한 보조 메서드 │ │ private boolean validateAmount(BigDecimal amount) { │ │ return amount.compareTo(BigDecimal.ZERO) > 0; │ │ } │ │ │ // public 메서드: 외부와 상호작용하는 유일한 창구 │ │ public void deposit(BigDecimal amount) { │ │ if (!validateAmount(amount)) { │ │ throw new IllegalArgumentException("유효하지 않은 금액");│ │ } │ │ this.balance = this.balance.add(amount); │ │ } │ │ │ public void withdraw(BigDecimal amount) { │ │ if (!validateAmount(amount)) { │ │ throw new IllegalArgumentException("유효하지 않은 금액");│ │ } │ │ if (this.balance.compareTo(amount) < 0) { │ │ throw new IllegalStateException("잔고 부족"); │ │ } │ │ this.balance = this.balance.subtract(amount); │ │ } │ │ │ public BigDecimal getBalance() { │ │ return this.balance; │ │ } │ │ } │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 金のアクセス制御レベル │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ │ │ public (전체 공개) │ │ │ │ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ │ │ │ │ protected (상속+패키지) │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────┐ │ │ │ │ │ │ │ │ │ package-private (패키지 내) │ │ │ │ │ │ │ │ │ │ ┌───────────────────────────────────────┐ │ │ │ │ │ │ │ │ │ │ │ private (클래스 내부) │ │ │ │ │ │ │ │ │ │ │ │ ※ 가장 안전한 수준 │ │ │ │ │ │ │ │ │ │ │ └───────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ └─────────────────────────────────────────────┘ │ │ │ │ │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘[다이어그램 해설] BankAccount 클래스에서 balance는 private으로 선언되어 외부에서 직접 접근할 수 없다. deposit()과 withdraw() 메서드는 public으로 선언되어 외부에서 호출 가능한 유일한 창구 역할을 한다. 이 메서드들 내부에서 validateAmount()라는 private 메서드를 호출하여 입금액이 양수인지 검증하고, 잔고 부족 시 예외를 발생시킨다. 이 구조에서 중요한 점은, 외부 코드가 잔고를 음수로 설정하는 것이 불가능하다는 것이다. 만약 private이 아닌 public 필드였다면, account.balance = -1000;과 같이 잘못된 값이 설정될 수 있었을 것이다. 또한 계좌 검증 로직이 변경되어도(예: 마이너스 인출 허용) public 인터페이스는 변하지 않으므로, 사용하는側의 코드를 변경할 필요가 없다.
- 1990년대 Java의 접근 제어자 체계 확립: Java가 public, protected, package-private, private의 4단계 접근 제어자 체계를 확립했다.
-
📢 섹션 요약 비유: 캡슐화는 호텔 방 열쇠 카드 시스템과 같습니다. 손님(사용자)은 카드키(公开 인터페이스)로 방에 들어오지만, 방 내부의 금고 비밀번호(데이터)는职员(개발자)만 알고, 카드키로 금고까지 열리지 않습니다.
Ⅱ. 아키텍처 및 핵심 원리 (Deep Dive)
접근 제어자 요약
| 제어자 | 클래스 내부 | 동일 패키지 | 자식 클래스 | 외부 |
|---|---|---|---|---|
| private | O | X | X | X |
| package-private | O | O | X | X |
| protected | O | O | O | X |
| public | O | O | O | O |
캡슐화가 깨지는 경우와防范
캡슐화는 코딩 규칙만이 아니라 설계 원칙이기도 하다. 내부 데이터가 외부로 노출되는 다양한安候패턴과 그防范 방법을 알아본다.
┌─────────────────────────────────────────────────────────────────────┐
│ 캡슐화 깨짐과防范 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ [안티패턴 1: Mutable 객체 직접 노출] │
│ │
│ // ❌ 잘못된 예 │
│ public class Order { │
│ public List<OrderItem> items; // 내부 List 직접 노출 │
│ } │
│ │
│ // 사용자 코드 │
│ order.items.add(new OrderItem(...)); // 주문 아이템 마음대로 추가 │
│ order.items.clear(); // 주문 아이템 마음대로 삭제 │
│ │
│ // ✅ 올바른 예: 방어적 복사 (Defensive Copy) │
│ public class Order { │
│ private List<OrderItem> items; │
│ │
│ public List<OrderItem> getItems() { │
│ return new ArrayList<>(items); // 복사본 반환 │
│ } │
│ │
│ public void addItem(OrderItem item) { │
│ items.add(item); // 내부에서만 추가 │
│ } │
│ } │
│ │
│ [안티패턴 2: 세터(setter)를 통한 무분별한 변경] │
│ │
│ // ❌ 잘못된 예 │
│ public class User { │
│ public void setName(String name) { │
│ this.name = name; // 어디서든 변경 가능 │
│ } │
│ } │
│ │
│ // ✅ 올바른 예: 필요한 경우에만 setter 제공 │
│ public class User { │
│ private String name; │
│ private final String userId; // 불변 필드 │
│ │
│ public User(String userId) { │
│ this.userId = userId; // 생성 시에만 설정 │
│ } │
│ │
│ public void rename(String newName) { // 명시적 의도 │
│ this.name = newName; │
│ } │
│ } │
│ │
│ [캡슐화 좋은 예: 불변 객체 (Immutable Object)] │
│ │
│ public final class Money { // final: 상속 불가 │
│ private final BigDecimal amount; // final: 변경 불가 │
│ private final Currency currency; │
│ │
│ public Money(BigDecimal amount, Currency currency) { │
│ this.amount = amount; │
│ this.currency = currency; │
│ } │
│ │
│ // 불변 객체이므로 Setter 없음 │
│ // 새 Money 객체를 반환하는 메서드만 존재 │
│ public Money add(Money other) { │
│ if (!this.currency.equals(other.currency)) { │
│ throw new IllegalArgumentException("통화 불일치"); │
│ } │
│ return new Money(this.amount.add(other.amount), │
│ this.currency); │
│ } │
│ } │
│ │
│ 핵심 규칙: │
│ 1. 속성은 private으로 선언 (거의 항상) │
│ 2. 내부 Collection은 방어적 복사본 반환 │
│ 3. Setter는 필요한 경우가 아니면 제공하지 않음 │
│ 4. 불변 객체는 변경 대신 새 객체를 반환 │
│ │
└─────────────────────────────────────────────────────────────────────┘
[다이어그램 해설] Mutable 객체의 직접 노출(첫 번째 안티패턴)에서 order.items가 public으로 선언되면, 외부 코드에서 마음대로 아이템을 추가/삭제할 수 있다. 이는 주문의 불변식(최소 1개 아이템, 총액과 아이템 합계 일치 등)이 깨질 수 있음을 의미한다. 올바른 예에서는 getItems()가 복사본을 반환하므로 외부에서 직접 변경이 불가능하고, addItem() 메서드를 통해서만 아이템을 추가할 수 있다. 이 메서드 내부에서 검증 로직을 추가할 수 있다. 두 번째 안티패턴은 세터의 무분별한 사용이다. 불변 필드(userId)에 setter가 존재하면, 어느 순간에든 값이 변경될 수 있어 예측 불가능한 버그가 발생할 수 있다. 불변 객체(Money)는 클래스 자체가 final이고 모든 필드가 final이므로, 생성 후 절대 상태가 변하지 않는다. 이는 동시성 프로그래밍에서 매우 유리하다.
- 📢 섹션 요약 비유: 캡슐화 위반은 공공기관의 민원 발급 창구와 같습니다. 직원이 내무실(데이터)를 열어서复印을 직접 가져가면(직접 접근), 서류 순서가 틀어지거나(불변식 깨짐), 누군가 서류를 없애버릴 수(데이터 삭제) 있습니다.窓口(公开 인터페이스)를 통해서만 서류를 발급받으면,职员이 검증과秩序를 유지할 수 있습니다.
Ⅲ. 융합 비교 및 다각도 분석
비교: 캡슐화 vs 정보 은닉 vs 추상화
| 구분 | 캡슐화 | 정보 은닉 | 추상화 |
|---|---|---|---|
| 초점 | 데이터+메서드 packaging | 구현 Details 숨김 | 복잡성 줄이기 |
| 측면 | 구조적 (Object 결합) | 보호적 (접근 제한) | 認知적 (界面 단순화) |
| 관계 | 캡슐화의 일종이 정보 은닉 | 정보 은닉은 캡슐화의 이유 | 캡슐화에서 도출 |
- 📢 섹션 요약 비유: 캡슐화는 음식점 주방의 완급 장치와 같습니다. 손님(사용자)은메뉴판(界面)만 보지만, 주방 내부(객체)에서는재료가切되는 방식, 양념量등이 상세히 진행됩니다. 주방장을 교체(구현 변경)해도 손님은모릅니다.
Ⅳ. 실무 적용 및 기술사적 판단
실무 시나리오
- 시나리오 — 좌석 예약 시스템: 좌석(Seat) 객체의 가용 상태를 외부에서 직접 변경하지 못하도록 private으로 선언하고, reserve(), cancel() 메서드만을 public으로 제공한다. 이를 통해 좌석 상태 변경에 대한 검증(중복 예약 방지 등)을 항상 통과해야만 상태가 변경되도록 보장한다.
도입 체크리스트
- 기술적: 모든 속성이 적절한 접근 제어자로 선언되어 있는가?
- 운영·보안적: 방어적 복사가 필요한 Collection이 있는가?
안티패턴
-
** getter连锁**: 객체 A가 getter로 B를 얻고, B의 getter로 C를 얻고, C의 setter로 값을 설정하는 식의连锁적 접근은 캡슐화를 사실상 무력화한다.
-
📢 섹션 요약 비유: getter连锁은 여러 사람이 손을 잡고 물건을 전달하는 것과 같습니다. 맨 처음 사람은 물건이 어디서 오는지, 마지막 사람이 어디에 두는지 모르고, 중간에 한 명이 실수하면 전체受影响됩니다.
Ⅴ. 기대효과 및 결론
미래 전망
- 불변성 중심 설계: 동시성 문제와 예측 불가능한 버그를 줄이기 위해, 가변 객체 대신 불변 객체를 우선 사용하는 설계 철학이 주목받고 있다.
📌 관련 개념 맵 (Knowledge Graph)
| 개념 명칭 | 관계 및 시너지 설명 |
|---|---|
| 접근 제어자 | Java의 private, protected, public 등 속성/메서드의 접근 범위를 지정하는修饰자다. |
| 정보 은닉 (Information Hiding) | Parnas가 제안한-module 내부 구현을 숨기는 원칙으로, 캡슐화의 이론적 기반이다. |
| 방어적 복사 (Defensive Copy) | Collection이나 객체의 복사본을 반환하여 원본이 변경되는 것을 방지하는 기법이다. |
| 불변 객체 (Immutable Object) | 생성 후 상태가 절대 변하지 않는 객체로, 동시성 프로그래밍에서 유리하다. |
👶 어린이를 위한 3줄 비유 설명
- 캡슐화는 은행 금고와 같습니다. 금고 안의 내용물(데이터)은 행원(개발자)만 알 수 있고, 고객(사용자)은 창구(公开 메서드)를 통해서만取引할 수 있어요.
- 만약 금고 문이 열려있으면(캡슐화 위반) 누군가 마음대로 돈을 가져가거나(데이터 변경), 액수를 바꿔버릴 수(불변식 깨짐) 있어요.
- 그래서 금고 문을 꼭 닫아두고( private 속성), 행원만 열쇠( public 메서드)로 관리하면, bank's资产(데이터)이 안전하게 보호돼요!