캡슐화

AI
gemma-4-31b
작성자
익명
작성일
2026.08.05
조회수
None
버전
v1

캡슐화 (Encapsulation)

1. 개요

캡슐화란 [객체지향 프로그래밍]에서 데이터(속성)와 그 데이터를 조작하는 메서드(행위)를 하나의 단위인 클래스로 묶고, 내부 구현 상세를 외부로부터 숨겨 객체의 상태를 보호하는 소프트웨어 설계 원칙이다. 캡슐화의 핵심 목적은 객체의 내부 상태가 외부의 임의적인 조작으로 인해 오염되는 것을 방지하고, 객체가 스스로 자신의 상태를 관리하게 함으로써 시스템의 복잡도를 낮추는 데 있다.

2. 핵심 원리와 작동 방식

캡슐화는 단순히 데이터를 묶는 '그룹화'를 넘어, 객체의 내부 작동 방식을 외부에 노출하지 않는 '[[추상화]]'의 과정을 포함한다. 클래스 내부의 필드는 외부에서 직접 접근할 수 없도록 제한하고, 오직 정의된 공개 메서드를 통해서만 상호작용하게 함으로써 객체의 자율성을 보장한다.

캡슐화 전후의 구조적 차이 비교

구분 캡슐화 적용 전 (절차적/개방적 구조) 캡슐화 적용 후 (객체지향적 구조)
데이터 접근 외부에서 필드에 직접 접근 및 수정 가능 공개된 메서드(Interface)를 통해서만 접근 가능
의존성 데이터 구조 변경 시 이를 사용하는 모든 코드 수정 필요 내부 구현 변경 시에도 외부 인터페이스가 유지되면 영향 없음
데이터 무결성 잘못된 값(예: 음수 나이)이 입력되어도 막을 방법이 없음 Setter 메서드 내 검증 로직을 통해 유효한 값만 수용 가능
책임 소재 데이터를 사용하는 외부 로직이 상태 변경 책임을 가짐 객체 스스로가 자신의 상태 변경 로직을 관리함

3. 정보 은닉과 캡슐화의 차이점

흔히 캡슐화와 정보 은닉(Information Hiding)을 동일한 개념으로 사용하지만, 엄밀히 말하면 서로 다른 목적을 가진 보완적 개념이다.

  • 캡슐화 (Encapsulation): 데이터와 기능을 하나의 단위로 '묶는 것'에 초점이 맞춰져 있다. 이는 관련된 요소를 응집시켜 모듈화를 달성하는 행위이다.
  • 정보 은닉 (Information Hiding): 캡슐화된 내부의 상세 구현을 외부에서 보이지 않게 '숨기는 것'에 초점이 맞춰져 있다. 이는 접근 제어자를 통해 외부의 간섭을 차단하여 보안성과 안정성을 높이는 행위이다.

즉, 캡슐화는 정보 은닉을 구현하기 위한 수단이며, 정보 은닉은 캡슐화를 통해 얻고자 하는 핵심 결과물이라고 할 수 있다.

4. 접근 제어자 (Access Modifiers)

접근 제어자는 클래스, 메서드, 필드의 가시성(Visibility)을 결정하여 정보 은닉을 실현하는 도구이다.

언어별/제어자별 접근 가능 범위 요약 (Java 언어 기준)

참고: 언어마다 세부 명칭과 범위는 다를 수 있습니다.

제어자 클래스 내부 동일 패키지 자식 클래스 전체(외부) 설명
private O X X X 해당 클래스 내부에서만 접근 가능 (가장 강력한 은닉)
default O O X X 동일 패키지 내의 클래스들만 접근 가능
protected O O O X 동일 패키지 및 [[상속]]받은 자식 클래스에서 접근 가능
public O O O O 어디서든 접근 가능 (외부 인터페이스 역할)

타 언어의 접근 제어 예시

  • Python: 엄격한 접근 제어자가 없으나, 변수명 앞에 언더바(_ 또는 __)를 붙여 관례적으로 내부 변수임을 명시한다. (__variable은 Name Mangling을 통해 접근을 어렵게 함)
  • C++: public, protected, private 키워드를 사용하여 접근 범위를 지정하는 블록 구조를 가진다.
  • C#: Java와 유사하며, 추가적으로 internal(동일 어셈블리 내 접근 가능) 제어자를 제공한다.

5. Getter와 Setter

private으로 설정된 필드에 안전하게 접근하기 위해 제공하는 public 메서드를 Getter와 Setter라고 한다.

  • Getter: 내부 필드 값을 읽어오는 메서드.
  • Setter: 내부 필드 값을 수정하는 메서드. 이때 입력값의 유효성을 검사하는 로직을 추가하여 데이터 무결성을 유지한다.

코드 예제: 직접 접근 vs 제어 접근 (Java 기준)

class UserBad {
    public int age; // public으로 노출되어 캡슐화 위반
}

class UserGood {
    private int age; // private으로 은닉

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        if (age < 0) {
            System.out.println("에러: 나이는 음수일 수 없습니다.");
            return;
        }
        this.age = age;
    }
}

public class EncapsulationDemo {
    public static void main(String[] args) {
        // [잘못된 방식] 직접 접근으로 인한 데이터 오염
        UserBad badUser = new UserBad();
        badUser.age = -10; 
        System.out.println("Bad User Age: " + badUser.age); // -10 출력 (논리적 오류)

        // [올바른 방식] 캡슐화와 Setter를 통한 제어
        UserGood goodUser = new UserGood();
        goodUser.setAge(-10); // "에러: 나이는 음수일 수 없습니다." 출력
        System.out.println("Good User Age: " + goodUser.getAge()); // 0 출력 (기본값 유지)
    }
}

6. 캡슐화와 [[인터페이스]]의 관계

캡슐화는 '무엇을 하는가(What)'와 '어떻게 하는가(How)'를 분리하는 과정이며, 여기서 [[인터페이스]]는 '무엇을 하는가'에 해당하는 외부 소통 창구 역할을 한다.

  1. 추상화의 구현: 인터페이스는 객체가 제공하는 기능의 명세서이다. 사용자는 인터페이스만 보고 기능을 호출하며, 실제 내부 구현(캡슐화된 로직)이 어떻게 돌아가는지는 알 필요가 없다.
  2. 결합도 감소: 인터페이스를 통해 캡슐화된 객체와 상호작용하면, 내부 구현 코드를 완전히 변경하더라도 인터페이스의 시그니처(메서드 이름, 파라미터 등)만 유지된다면 외부 코드를 수정할 필요가 없다.

7. 캡슐화의 장점과 효과

  • 유지보수성 향상: 내부 로직을 수정해도 외부 인터페이스가 동일하다면 영향을 받는 범위가 최소화된다.
  • 데이터 무결성 보장: Setter 등을 통해 잘못된 데이터가 입력되는 것을 원천 차단하여 객체의 일관된 상태를 유지한다.
  • 코드의 독립성(낮은 결합도): 객체 간의 의존성이 줄어들어 개별 모듈의 테스트와 교체가 용이해진다.
  • 복잡성 감소: 사용자는 객체의 내부 동작 원리를 이해하지 않고도 제공되는 메서드만으로 기능을 사용할 수 있다.

8. 실제 설계 사례: 은행 계좌 시스템

은행 계좌(Account) 클래스를 설계할 때 캡슐화가 어떻게 적용되는지 살펴보자.

  • 은닉 대상: balance(잔액) 필드. 잔액을 public으로 두면 외부에서 account.balance = 1000000000;과 같이 임의로 금액을 조작할 수 있다.
  • 캡슐화된 인터페이스: deposit()(입금), withdraw()(출금) 메서드.
  • 제어 로직: withdraw() 메서드 내부에서 잔액 부족 시 출금을 거부하는 로직을 구현하여 비즈니스 규칙을 강제한다.

구현 예시 (Java)

class Account {
    private long balance = 0; // 잔액 은닉

    public void deposit(long amount) {
        if (amount <= 0) throw new IllegalArgumentException("입금액은 0보다 커야 합니다.");
        this.balance += amount;
    }

    public void withdraw(long amount) {
        if (amount <= 0) throw new IllegalArgumentException("출금액은 0보다 커야 합니다.");
        if (this.balance < amount) {
            System.out.println("잔액이 부족합니다.");
            return;
        }
        this.balance -= amount;
    }

    public long getBalance() {
        return this.balance;
    }
}

9. 실무 적용 시 주의사항

과도한 Getter/Setter의 함정

모든 필드에 기계적으로 Getter와 Setter를 만드는 것은 캡슐화의 본질을 훼손하는 행위이다. 이는 사실상 필드를 public으로 공개한 것과 다름없으며, 객체의 상태 변경 권한을 다시 외부에 넘겨주는 결과를 초래한다.

'Tell, Don't Ask' 원칙

진정한 캡슐화를 위해서는 객체에게 상태를 물어보고(Ask) 외부에서 판단하여 수정하는 것이 아니라, 객체에게 특정 동작을 명령(Tell)하는 방식으로 설계해야 한다.

  • Bad (Ask): if (account.getBalance() > 1000) { account.setBalance(account.getBalance() - 1000); }
  • Good (Tell): account.withdraw(1000); (출금 가능 여부 판단과 잔액 차감 로직을 모두 Account 클래스 내부로 캡슐화)

캡슐화 위반 안티 패턴: 데이터 클래스 (Data Class)

로직 없이 오직 데이터 저장만을 위해 모든 필드에 Getter/Setter를 열어둔 클래스를 '데이터 클래스' 또는 '빈약한 도메인 모델(Anemic Domain Model)'이라고 한다.

안티 패턴 예시:

// 캡슐화가 결여된 데이터 클래스
class Order {
    private int status; // 0: 준비, 1: 배송중, 2: 완료
    public int getStatus() { return status; }
    public void setStatus(int status) { this.status = status; }
}

// 외부에서 상태 전이 로직을 직접 관리 (캡슐화 위반)
Order order = new Order();
if (order.getStatus() == 0) {
    order.setStatus(1); // 상태 변경 로직이 외부에 노출됨
}
개선 방향: order.ship()과 같이 상태 변경의 의미를 담은 메서드를 내부에 구현하고, 상태 전이 규칙(예: '완료' 상태에서는 '배송중'으로 갈 수 없음)을 클래스 내부에서 제어해야 한다.

AI 생성 콘텐츠 안내

이 문서는 AI 모델(gemma-4-31b)에 의해 생성된 콘텐츠입니다.

주의사항: AI가 생성한 내용은 부정확하거나 편향된 정보를 포함할 수 있습니다. 중요한 결정을 내리기 전에 반드시 신뢰할 수 있는 출처를 통해 정보를 확인하시기 바랍니다.

이 AI 생성 콘텐츠가 도움이 되었나요?