안녕하세요! 지난번에는 C# 객체 지향 프로그래밍의 기본인 클래스, 추상화, 인스턴스화에 대해 알아보았습니다. 클래스라는 설계도로 객체를 만들고, 그 복잡한 내부를 숨기는 추상화의 중요성도 이해했죠.
이번 포스팅에서는 객체를 만들 때 필수적으로 사용되는 생성자(Constructor)와 객체의 데이터를 안전하게 보호하는 객체 지향의 핵심 원칙인 캡슐화(Encapsulation)에 대해 깊이 파고들어 보겠습니다. 이 두 가지 개념은 견고하고 유지보수가 쉬운 코드를 만드는 데 매우 중요합니다.
1. 생성자(Constructor): 객체의 '첫인상'을 결정하다! 🏗️
우리가 지난번에 Car 클래스를 만들고 new Car("아반떼", "흰색", 180);처럼 객체를 생성했었죠? 이때 new Car(...) 뒤에 나오는 것이 바로 생성자입니다.
생성자는 클래스의 인스턴스(객체)가 생성될 때 가장 먼저, 그리고 자동으로 실행되는 특별한 종류의 메서드입니다. 생성자의 주된 목적은 새로 생성될 객체의 초기 상태를 설정하고, 필요한 준비 작업을 수행하는 것입니다.
- 생성자의 특징:
- 클래스 이름과 동일: 생성자의 이름은 반드시 해당 클래스의 이름과 같아야 합니다.
- 반환 타입 없음: 일반 메서드와 달리 void와 같은 반환 타입이 없습니다. (객체 자체를 반환하므로 명시하지 않음)
- new 키워드와 함께 호출: new 연산자를 사용하여 객체를 생성할 때만 호출됩니다.
- 오버로딩 가능: 여러 개의 생성자를 만들 수 있으며, 각각 다른 매개변수 목록을 가질 수 있습니다 (생성자 오버로딩).
예시 (Car 클래스에 다양한 생성자 추가):
public class Car
{
public string Model { get; set; }
public string Color { get; set; }
public int MaxSpeed { get; private set; } // MaxSpeed는 외부에서 설정 못하게 private set 추가
public int CurrentSpeed { get; private set; } // CurrentSpeed도 외부에서 직접 수정 못하게 private set 추가
// 1. 기본 생성자 (매개변수가 없는 생성자):
// 명시적으로 정의하지 않아도 컴파일러가 자동으로 생성해 줍니다.
// 만약 다른 생성자를 정의하면 기본 생성자는 자동 생성되지 않으므로,
// 필요하다면 직접 정의해야 합니다.
public Car()
{
Model = "미정";
Color = "은색";
MaxSpeed = 150;
CurrentSpeed = 0;
Console.WriteLine("기본 설정의 자동차가 생성되었습니다.");
}
// 2. 매개변수가 있는 생성자:
// 객체 생성 시 필요한 초기값을 받아 설정합니다.
public Car(string model, string color, int maxSpeed)
{
Model = model;
Color = color;
MaxSpeed = maxSpeed;
CurrentSpeed = 0; // 모든 새 자동차는 초기 속도가 0
Console.WriteLine($"{color} {model} 자동차가 생성되었습니다. (최고 속도: {maxSpeed})");
}
// 3. 또 다른 매개변수가 있는 생성자 (생성자 오버로딩):
// 같은 이름의 생성자지만 매개변수 타입이나 개수가 다릅니다.
public Car(string model) : this(model, "흰색", 180) // 다른 생성자 호출 (코드 중복 방지)
{
Console.WriteLine($"{model} 기본 흰색 자동차가 생성되었습니다.");
}
// 메서드 (생략, 이전 예시와 동일)
public void Accelerate(int amount)
{
CurrentSpeed += amount;
if (CurrentSpeed > MaxSpeed) CurrentSpeed = MaxSpeed;
Console.WriteLine($"{Color} {Model}이(가) {amount}만큼 가속하여 현재 속도: {CurrentSpeed}km/h");
}
public void Brake()
{
CurrentSpeed = 0;
Console.WriteLine($"{Color} {Model}이(가) 정지했습니다.");
}
}
생성자 사용 예시:
public class Program
{
public static void Main(string[] args)
{
Car car1 = new Car(); // 기본 생성자 호출
// 출력: 기본 설정의 자동차가 생성되었습니다.
// car1.Model: "미정", car1.Color: "은색", car1.MaxSpeed: 150
Car car2 = new Car("제네시스", "검정색", 250); // 매개변수 있는 생성자 호출
// 출력: 검정색 제네시스 자동차가 생성되었습니다. (최고 속도: 250)
// car2.Model: "제네시스", car2.Color: "검정색", car2.MaxSpeed: 250
Car car3 = new Car("코나"); // 또 다른 매개변수 있는 생성자 호출
// 출력: 흰색 코나 자동차가 생성되었습니다. (최고 속도: 180)
// 출력: 코나 기본 흰색 자동차가 생성되었습니다.
// car3.Model: "코나", car3.Color: "흰색", car3.MaxSpeed: 180
car2.Accelerate(50);
}
}
생성자를 통해 객체가 생성되는 시점에 필요한 초기값을 효과적으로 설정할 수 있습니다. 이는 객체의 '유효하지 않은' 상태를 방지하고, 항상 일관된 초기 상태를 보장하는 데 중요합니다.
2. 캡슐화(Encapsulation): 데이터를 안전하게 '포장'하다! 🎁
캡슐화는 객체 지향 프로그래밍의 두 번째 핵심 원칙으로, 관련 있는 데이터(속성)와 그 데이터를 다루는 메서드를 하나의 '캡슐'처럼 묶고, 외부에서는 이 '캡슐'의 내부 구현에 직접 접근하지 못하도록 보호하는 것을 의미합니다. 오직 **정의된 공개된 인터페이스(메서드)**를 통해서만 내부 데이터에 접근하고 변경할 수 있도록 하는 것이죠.
다시 자동차 비유로 돌아가 봅시다.
- 자동차의 엔진, 변속기, 연료 탱크 등은 중요한 내부 부품입니다. 이 부품들은 외부에서 아무나 만지거나 조작할 수 없도록 '차체'라는 캡슐 안에 숨겨져 있습니다.
- 우리는 운전석의 핸들, 액셀, 브레이크, 기어와 같은 *공개된 조작 장치(인터페이스)*를 통해서만 자동차의 내부 동작에 영향을 미칠 수 있습니다.
캡슐화의 목적:
- 데이터 보호 (정보 은닉): 객체의 중요한 데이터가 외부에서 무분별하게 변경되는 것을 막아 데이터의 무결성(Integrity)을 유지합니다.
- 유지보수성 향상: 내부 구현이 변경되더라도 외부에 노출된 인터페이스(메서드)만 동일하게 유지된다면, 해당 객체를 사용하는 외부 코드에 영향을 주지 않습니다.
- 코드 응집도(Cohesion) 증가: 데이터와 관련 메서드가 한 곳에 묶여 있으므로, 코드의 가독성과 이해도가 높아집니다.
C#에서 캡슐화 구현: 접근 제한자(Access Modifiers)와 속성(Properties)
C#에서는 **접근 제한자(public, private, protected 등)**와 **속성(Property)**을 사용하여 캡슐화를 구현합니다.
- private (가장 많이 사용): 해당 클래스 내부에서만 접근 가능합니다. 외부에서는 접근할 수 없습니다. (데이터 은닉에 사용)
- public: 어디에서든 접근 가능합니다. (외부에 노출할 메서드나 속성에 사용)
- protected: 해당 클래스와 이를 상속받은 자식 클래스에서만 접근 가능합니다.
캡슐화 예시 (Car 클래스 개선):
public class Car
{
// 필드: private으로 선언하여 외부에서 직접 접근 불가
private string _model; // 모델명
private string _color; // 색상
private int _maxSpeed; // 최고 속도
private int _currentSpeed; // 현재 속도
// 속성 (Property): 필드에 안전하게 접근하기 위한 '공개된 통로'
// get: 필드 값을 읽을 때 사용
// set: 필드 값을 쓸 때 사용 (여기서 유효성 검사 등 추가 로직 가능)
public string Model
{
get { return _model; }
set { _model = value; } // 'value'는 대입되는 값을 나타내는 키워드
}
public string Color
{
get { return _color; }
set
{
// 색상 설정 시 유효성 검사 추가 (캡슐화의 장점)
if (value == "빨강" || value == "파랑" || value == "초록" || value == "흰색" || value == "검정")
{
_color = value;
}
else
{
Console.WriteLine("유효하지 않은 색상입니다. 기본값으로 설정됩니다.");
_color = "흰색"; // 유효하지 않으면 기본값으로
}
}
}
public int MaxSpeed
{
get { return _maxSpeed; }
// set을 private으로 설정하여 외부에서는 최고 속도를 변경할 수 없게 함 (생성자에서만 설정 가능)
private set
{
if (value > 0 && value <= 300) // 최고 속도 유효성 검사
{
_maxSpeed = value;
}
else
{
Console.WriteLine("유효하지 않은 최고 속도입니다. 기본값으로 설정됩니다.");
_maxSpeed = 150;
}
}
}
public int CurrentSpeed
{
get { return _currentSpeed; }
// set을 private으로 설정하여 외부에서 직접 현재 속도 변경 불가.
// 오직 Accelerate()나 Brake() 같은 메서드를 통해서만 변경 가능.
private set
{
_currentSpeed = value;
}
}
// 생성자 (이전과 동일)
public Car(string model, string color, int maxSpeed)
{
Model = model; // 속성의 set 접근자를 통해 값 할당
Color = color; // 속성의 set 접근자를 통해 값 할당 (유효성 검사 적용)
MaxSpeed = maxSpeed; // 속성의 private set 접근자를 통해 값 할당 (유효성 검사 적용)
CurrentSpeed = 0;
Console.WriteLine($"{Color} {Model} 자동차가 생성되었습니다. (최고 속도: {MaxSpeed})");
}
// 메서드 (이전과 동일)
public void Accelerate(int amount)
{
CurrentSpeed += amount; // private set 접근자를 통해 내부에서만 변경
if (CurrentSpeed > MaxSpeed) CurrentSpeed = MaxSpeed;
Console.WriteLine($"{Color} {Model}이(가) {amount}만큼 가속하여 현재 속도: {CurrentSpeed}km/h");
}
public void Brake()
{
CurrentSpeed = 0; // private set 접근자를 통해 내부에서만 변경
Console.WriteLine($"{Color} {Model}이(가) 정지했습니다.");
}
}
캡슐화 적용 후 사용 예시:
public class Program
{
public static void Main(string[] args)
{
Car myCar = new Car("아반떼", "빨강", 180);
// 속성(Property)의 get 접근자를 통해 값 읽기
Console.WriteLine($"내 차 모델: {myCar.Model}"); // 아반떼
Console.WriteLine($"내 차 색상: {myCar.Color}"); // 빨강
// 속성(Property)의 set 접근자를 통해 값 변경
myCar.Color = "파랑"; // set 접근자 호출
Console.WriteLine($"색상 변경 후: {myCar.Color}"); // 파랑
myCar.Color = "보라"; // 유효하지 않은 색상 시도
// 출력: 유효하지 않은 색상입니다. 기본값으로 설정됩니다.
Console.WriteLine($"유효하지 않은 색상 시도 후: {myCar.Color}"); // 흰색 (기본값)
// private set으로 설정된 속성은 외부에서 직접 변경 불가 (컴파일 에러 발생)
// myCar.MaxSpeed = 200; // 에러!
// myCar.CurrentSpeed = 50; // 에러!
// 오직 메서드를 통해서만 내부 상태 변경
myCar.Accelerate(30); // CurrentSpeed는 Accelerate 메서드를 통해서만 변경 가능
myCar.Brake(); // CurrentSpeed는 Brake 메서드를 통해서만 변경 가능
}
}
이처럼 캡슐화를 통해 MaxSpeed나 CurrentSpeed와 같은 중요한 데이터는 private set으로 보호되어 외부에서 직접 변경할 수 없게 됩니다. 오직 Accelerate()나 Brake()와 같은 메서드를 통해서만 안전하게 변경될 수 있도록 통제하는 것이죠. 또한 Color 속성처럼 set 접근자 내에서 데이터의 유효성을 검사하는 로직을 추가하여 데이터의 안정성을 더욱 높일 수 있습니다.
마무리하며: 객체 지향의 견고한 기둥, 생성자와 캡슐화! 🧱
생성자는 객체가 태어나는 순간, 그 객체의 정체성과 초기 상태를 부여하는 중요한 역할을 합니다. 생성자를 통해 우리는 유효하고 일관된 상태의 객체를 만들 수 있습니다.
캡슐화는 객체의 내부를 보호하고, 외부와의 상호작용을 통제하여 데이터의 무결성을 지키고 코드의 유지보수성을 향상시킵니다. 이는 객체 지향 프로그래밍의 가장 강력한 특징 중 하나이며, 잘 설계된 시스템의 기반이 됩니다.
이 두 가지 개념을 잘 이해하고 활용하면, 여러분은 더욱 견고하고 확장 가능한 C# 코드를 작성할 수 있을 것입니다. 객체 지향의 재미와 실용성을 느끼셨기를 바랍니다!
생성자 오버로딩이나 캡슐화의 다양한 적용 방식에 대해 더 궁금한 점이 있으신가요? 언제든지 질문해주세요!
'C# > C# 문법' 카테고리의 다른 글
| C# 중급 문법 (6) : Static에 대해서 (1) | 2025.08.20 |
|---|---|
| C# 중급 문법 (5) : 구조체 (3) | 2025.08.17 |
| C# 중급 문법 (4) : 추상 클래스와 인터페이스 (4) | 2025.08.13 |
| C# 중급 문법 (3) : 상속과 다형성 (4) | 2025.08.09 |
| C# 중급 문법 (1) : 클래스, 추상화, 인스턴스화 (3) | 2025.07.31 |
