1. 들어가기
현대 자동차를 설계한다고 상상해봐. 내부에는 수백 개의 복잡한 부품, 민감한 전자장치, 미세한 세팅들이 있지. 아무나 엔진 컨트롤러에 손대서 나사 돌리면 안 되는 건 당연하지. 만약 아무나 막 만지면, 차가 이상하게 움직일 수도 있고, 누가 뭘 망가뜨렸는지 찾으려고 정비소에서 머리 아플 거야.
프로그래밍도 똑같아. 클래스를 만들 때, 내부를 아무나 건드리지 못하게 보호하고 싶지? 누가 실수로 중요한 데이터를 망치거나, 외부에서 몰라도 되는 메서드에 간섭하면 안 되니까. 이게 바로 캡슐화의 핵심이야. 캡슐화는 객체지향 프로그래밍의 세 가지 핵심 원칙 중 하나야.
캡슐화는 구현 세부사항을 숨기고, "밖에서" 뭘 쓸 수 있고, 뭘 숨길지 명확하게 정해주는 거야. 예를 들어, 사용자는 글로브박스는 열 수 있지만, 보닛은 잠가두는 것처럼. 클래스가 외부 코드에 어떤 데이터를 보여줄지, 어떤 건 비밀로 할지 스스로 결정하는 거지. 그래서 코드가 더 믿을만하고, 논리적이고, 유지보수도 쉬워져.
좀 더 엄밀하게 말하면, 캡슐화는 데이터(필드)와 그 데이터와 관련된 동작(메서드)을 하나의 구조로 묶고, 객체 내부 구성요소에 직접 접근을 제한하는 방법이야.
2. 접근 제한자: 내 영역 지키기
C# (그리고 다른 OOP 언어들)에서 캡슐화는 접근 제한자로 구현돼. 이건 클래스의 멤버(필드, 메서드, 프로퍼티 등)가 어디서 접근 가능한지 정해주는 "라벨" 같은 거야.
이미 몇 번 봤겠지만, 다시 한 번 정리하고, 몇 가지 더 추가해볼게:
| 제한자 | 접근 가능... |
|---|---|
|
모두! 프로젝트 내 모든 코드(그리고 프로젝트가 라이브러리면 외부에서도) |
|
오직 이 클래스 내부에서만 |
|
이 클래스와 모든 자식 클래스에서 |
|
현재 어셈블리(프로젝트) 내에서만 |
|
현재 어셈블리 또는 자식 클래스에서 |
|
현재 어셈블리 내 자식 클래스에서만 |
이번 강의에서는 가장 자주 쓰는 public, private에 집중하고, protected는 간단히만 다룰게. 머리 아프지 않게!
내부와 외부를 나누기
클래스 Dog를 한번 만들어보자:
public class Dog
{
public string Name;
public int Age;
public void Bark()
{
Console.WriteLine($"{Name} 말한다: 멍멍!");
}
}
여기서는 모든 필드와 메서드가 public으로 선언돼 있어. 즉, 아무나 강아지 이름이나 나이를 바꿀 수 있다는 뜻이야:
Dog rex = new Dog("렉스", 5);
rex.Name = "샤릭"; // 갑자기 이름이 바뀜!
rex.Age = -999; // 나이에 심각한 문제 발생
근데 Name이랑 Age는 진짜 중요한 데이터라 아무나 막 바꾸게 하고 싶지 않잖아.
이렇게 하면 안 돼
모든 필드를 public으로 두면, 클래스의 논리가 깨질 위험이 있어. 외부에서 아무나 건드리면 객체가 이상해질 수 있지. 예를 들어, 강아지 나이를 음수로 하거나, 이름을 "%$#!??"처럼 이상하게 만들 수도 있어.
3. 필드 숨기기: private 쓰기
보통 클래스의 필드는 비공개(private)로 만들어. 그러면 값은 클래스 내부에서만 바꿀 수 있고, 외부에서는 절대 못 건드려.
public class Dog
{
private string name;
private int age;
public void Bark()
{
Console.WriteLine($"{name} 말한다: 멍멍!");
}
}
이제 외부에서 필드에 직접 접근하려고 하면:
Dog rex = new Dog("렉스", 5);
rex.name = "샤릭"; // 컴파일 에러
컴파일러가 바로 "접근 불가!"라고 알려줄 거야.
왜 이렇게 빡세게 막지? 유연성은?
"유연성"은 특별한 메서드나 프로퍼티(이건 다음 강의에서 더 자세히!)를 통해서 제공해. 이렇게 하면 데이터 변경을 규칙에 따라 통제할 수 있어.
4. 캡슐화 실전: 제어 예제
강아지 나이를 음수로 못 넣게 하고 싶다고 해보자:
public class Dog
{
private string name;
private int age;
public Dog(string name, int age)
{
this.name = name;
if (age >= 0)
this.age = age;
else
this.age = 0; // "이상한" 나이 못 넣게 막음
}
public void Bark()
{
Console.WriteLine($"{name} 말한다: 멍멍!");
}
// 나이 안전하게 바꾸는 메서드
public void SetAge(int newAge)
{
if (newAge >= 0)
age = newAge;
// else: 값이 이상하다고 메시지 추가할 수도 있음
}
}
이제 외부에서 필드를 직접 망칠 수 없어. 나이를 바꿀 땐 전용 메서드로 체크하고 바꿔야 해.
5. 필드 vs 접근 메서드 (getter/setter)
이렇게 필드를 private로 두고, 전용 메서드로 다루는 걸 데이터 캡슐화(data encapsulation)라고 해. 필드 값을 읽을 땐 getter, 쓸 땐 setter 메서드를 만들어.
public class Dog
{
private string name;
public Dog(string name)
{
this.name = name;
}
public string GetName()
{
return name;
}
public void SetName(string newName)
{
// 여기서 이름이 맞는지 체크할 수도 있음
name = newName;
}
}
근데! C#에 properties가 생기면서 이런 메서드는 거의 안 써. 프로퍼티가 훨씬 깔끔하고 편하거든(이건 다음 강의에서 더 자세히!).
6. 메서드와 클래스의 접근 제한자
필드만이 아니야! 접근 제한자는 메서드(클래스 멤버 함수)나 클래스 자체에도 쓸 수 있어.
클래스 내부에서만 쓰는 메서드(예: 내부 로직용 보조 함수)는 private로 만드는 게 좋아.
공개 메서드는 클래스의 "인터페이스"야(interface 키워드랑 헷갈리지 마!), 즉 클래스 사용자들이 쓸 수 있는 부분이지.
7. 캡슐화에서 흔한 실수
실수 1: 전부 public으로 선언함.
뭔가 다 열려 있으면 쓰기 편할 것 같지만, 사실 내부를 외부에서 막 바꿀 수 있어서 코드가 위험하고 예측 불가해져. 특히 큰 프로젝트에서 진짜 문제됨.
실수 2: 접근 제한자 바꿨다가 외부 코드가 깨짐.
리팩토링하다가 실수로 메서드나 필드의 접근 제한자를 바꾸면, 그걸 쓰던 다른 코드가 갑자기 컴파일이 안 되거나 이상하게 동작할 수 있어. 특히 공개 API에서는 진짜 조심해야 해.
실수 3: 지역 변수랑 클래스 필드 헷갈림.
가끔 개발자들이 메서드 안에서 선언한 변수는 그 메서드 안에서만 살아있다는 걸 까먹어. 클래스 필드는 모든 메서드에서 쓸 수 있는데 말이지. 변수 이름이 겹치면 진짜 헷갈리고, 이상한 버그가 생길 수 있어.
실수 4: private랑 protected를 무시함.
제한된 접근을 쓰면 나중에 못 쓸까봐 걱정하는데, 사실 캡슐화의 목적이 바로 불필요한 걸 숨기고, 진짜 필요한 것만 밖으로 내보내는 거야.
GO TO FULL VERSION