---
title: "안은 감추고 창구만 열어 둔다"
description: "필드를 열어 두면 잘못된 값이 들어갈 길도 같이 열립니다. 창구를 좁히면 확인할 기회가 생깁니다."
author: "유니얼"
pubDate: 2026-09-09
category: "programming/csharp/basics"
tags: ["C#", "기초"]
canonical: https://blog.unialgames.com/posts/csharp-encapsulation/
---
지금까지 필드를 전부 `public` 으로 열어 뒀습니다.

```csharp
class Player
{
    public int Hp;
}

Player p = new Player();
p.Hp = -500;      // 막을 방법이 없습니다
```

체력이 `-500` 인 플레이어가 만들어졌습니다. 문법적으로 아무 문제가 없습니다.

문제는 **이걸 누가 넣었는지 찾을 수가 없다**는 것입니다.
`p.Hp = ...` 는 프로그램 어디에서나 쓸 수 있습니다.
값이 이상해졌을 때 후보가 코드 전체입니다.

> **밖에서 들어오는 길을 하나로 좁히면, 거기서 확인할 수 있습니다.**

## private 으로 막고 메서드로 연다

```csharp
class Player
{
    private int hp = 100;

    public void TakeDamage(int amount)
    {
        if (amount < 0) return;

        hp -= amount;
        if (hp < 0) hp = 0;
    }

    public int GetHp()
    {
        return hp;
    }
}
```

`private` 은 **이 클래스 안에서만** 쓸 수 있다는 표시입니다.

```csharp
p.hp = -500;          // 컴파일 에러
p.TakeDamage(30);     // 이 길로만 들어옵니다
```

이제 `hp` 가 바뀌는 자리가 클래스 안뿐입니다.
값이 이상해지면 볼 곳이 이 파일 하나입니다.

그리고 들어오는 자리가 하나라 **확인할 기회**가 생깁니다.
음수 피해를 막고, 0 아래로 안 내려가게 하는 코드를 여기 한 번만 적으면 됩니다.

`public` 필드로 열어 두면 그 기회 자체가 없습니다. 확인할 자리가 없습니다.

## 접근 지정자

| | 어디서 쓸 수 있나 |
| --- | --- |
| `public` | 어디서나 |
| `private` | 그 클래스 안에서만 |
| `protected` | 그 클래스와 물려받은 클래스에서 |

아무것도 안 적으면 클래스 멤버는 `private` 입니다.
그래서 앞의 예제에서 `public` 을 계속 붙였던 것입니다.

`protected` 는 상속을 쓸 때 필요합니다.

**기본은 `private`, 필요한 것만 `public`** 입니다.
반대로 하면 나중에 막을 수가 없습니다.
누가 이미 쓰고 있으면 `private` 으로 바꾸는 순간 그 코드가 다 깨지니까요.

## 감추는 것은 값이 아니라 방식이다

`private` 을 값을 숨기는 것으로만 보면 절반만 본 것입니다.

```csharp
class Player
{
    private int hp = 100;
    private int shield = 50;

    public int GetHp()
    {
        return hp + shield;      // 안에서 어떻게 계산하든 밖은 모릅니다
    }
}
```

밖에서는 `GetHp()` 만 압니다. 안에 필드가 하나인지 둘인지, 어떻게 계산하는지는 모릅니다.

그래서 **안을 바꿔도 밖이 안 깨집니다.** 나중에 방어구를 추가하고 계산식을 바꿔도
`GetHp()` 라는 창구가 그대로면 쓰는 쪽은 아무것도 안 고쳐도 됩니다.

`public` 필드는 이게 안 됩니다. `p.Hp` 라고 쓰던 코드가 밖에 흩어져 있으면
필드 이름 하나 바꾸는 것도 큰일이 됩니다.

> **밖에 드러난 것은 전부 약속입니다.** 적게 드러낼수록 나중에 자유롭습니다.

## 읽기만 열어 두는 것도 있다

바꾸는 것은 막고 읽는 것만 열고 싶을 때가 많습니다.

```csharp
class Player
{
    private int hp = 100;

    public int GetHp() { return hp; }
}
```

`GetHp` 는 있고 `SetHp` 는 없습니다. 밖에서는 볼 수만 있습니다.
바꾸는 것은 `TakeDamage` 나 `Heal` 처럼 **의미가 있는 이름**으로만 열어 둡니다.

`SetHp(50)` 은 무슨 상황인지 모릅니다. 맞은 것인지 회복한 것인지 치트인지요.
`TakeDamage(50)` 은 압니다. 그리고 그 안에서 무엇을 확인해야 하는지도 정해집니다.

**필드마다 `Get` 과 `Set` 을 기계적으로 만들면 `public` 필드와 다를 게 없습니다.**
창구를 여는 것이 아니라 벽에 구멍을 낸 것입니다.

## 생성자와 같이 쓴다

앞 글의 생성자와 합치면 이렇게 됩니다.

```csharp
class Player
{
    private string name;
    private int hp;

    public Player(string name, int hp)
    {
        if (hp <= 0) hp = 1;

        this.name = name;
        this.hp = hp;
    }

    public void TakeDamage(int amount) { ... }
    public int GetHp() { return hp; }
}
```

이제 `Player` 객체는 **태어날 때부터 끝까지** `hp` 가 0 아래로 안 내려갑니다.

- 생성자가 시작값을 확인합니다.
- `private` 이라 밖에서 못 건드립니다.
- 바꾸는 길은 `TakeDamage` 하나고 거기서도 확인합니다.

이렇게 "이 객체는 항상 이렇다" 를 보장할 수 있게 됩니다.
클래스를 쓰는 이유의 나머지 절반이 이것입니다.
데이터와 코드를 묶는 것에 더해, **그 데이터가 망가지지 않게 지키는 것**입니다.

## 그런데 메서드가 번거롭다

`GetHp()` 는 괄호를 써야 하고, 필드처럼 자연스럽지 않습니다.

```csharp
Console.WriteLine(p.GetHp());
Console.WriteLine(p.Hp);        // 이렇게 쓰고 싶은데
```

밖에서는 필드처럼 쓰면서 안에서는 코드가 실행되게 하는 문법도 있습니다. 프로퍼티라고 합니다.
생김새가 무엇이든 노리는 것은 같습니다. **값이 바뀌는 자리를 한 곳으로 좁히는 것**입니다.

## 정리

- `private` 은 그 클래스 안에서만 쓸 수 있다는 표시입니다. 아무것도 안 적으면 `private` 입니다.
- 필드를 막고 메서드로만 열면 값이 바뀌는 자리가 한 곳으로 좁혀집니다.
- 자리가 좁아지면 확인할 기회가 생깁니다. `public` 필드는 그 기회 자체가 없습니다.
- 감추는 것은 값만이 아니라 **안에서 어떻게 하는지**입니다. 그래야 나중에 안을 바꿔도 밖이 안 깨집니다.
- 밖에 드러난 것은 전부 약속입니다. 적게 드러낼수록 자유롭습니다.
- 바꾸는 것은 의미가 있는 이름으로 엽니다. `Set` 을 기계적으로 만들면 `public` 필드와 같습니다.
- 생성자와 같이 쓰면 태어날 때부터 끝까지 상태를 지킬 수 있습니다.