1. 소개
이번 강의에서는 C#에서 상속 문법이 어떻게 동작하는지 알아볼 거야: 파생 클래스 선언하는 법, base 키워드가 뭔지, 그리고 그걸로 부모 클래스에 어떻게 접근하는지 배울 거야. 이걸 알면 코드 쓸 때 훨씬 편해지고, 프로그램도 더 깔끔해질 거야 (가끔은 복붙 실수 고치느라 밤새는 일도 줄어들고!).
실생활 예시 하나 들어볼게: Vehicle (탈것)라는 클래스가 있어 — 기본, 범용 설계도지. 모든 탈것은 달릴 수 있고, 색깔, 제조사 등등이 있지:
public class Vehicle
{
public string Brand { get; set; }
public string Color { get; set; }
public void Drive()
{
Console.WriteLine("출발!");
}
}
이제 새로운 탈것 — 자동차를 추가하고 싶어. 자동차도 탈것이지만, 특징이 있지 (예: 문 개수). 색깔이나 제조사 같은 건 이미 정의되어 있는데 또 쓸 필요는 없잖아!
상속은 이렇게 말할 수 있게 해줘: "자동차는 탈것이야, 근데 추가 기능이 더 있어."
2. 자식 클래스 선언하는 법
C#에서 상속받는 클래스를 만들 때는 콜론 :을 써. 클래스 이름 뒤에 어떤 클래스를 상속받는지 적어주면 돼.
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
}
이렇게 읽으면 돼: Car 클래스는 Vehicle 클래스의 모든 필드와 메서드를 상속받고, 거기에 자기만의 기능(예: 문 개수)을 추가하는 거야.
참고: C#에서는 단일 상속만 지원해 (즉, 클래스는 부모 클래스를 하나만 상속받을 수 있어). 근데 interface는 몇 개든 상관없어! (이건 나중에 얘기할게).
어떻게 동작하냐고?
이렇게 선언하면:
Car myCar = new Car();
...myCar 객체는 Vehicle의 모든 필드/메서드와 자기만의 속성에 다 접근할 수 있어. 예시:
myCar.Brand = "Toyota";
myCar.Color = "파랑";
myCar.NumberOfDoors = 4;
myCar.Drive(); // 상속받은 메서드
실전 시나리오:
우리 "진화하는" 앱에서 탈것 메뉴를 만들어서, 사용자가 탈것 종류를 고르면 프로그램이 그 속성을 다 보여줄 수 있어. 새로운 탈것 타입을 추가할 때마다 코드를 최소한만 쓰면 돼!
3. 생성자 상속
파생 클래스 객체를 만들 때, 기본 클래스의 속성도 생성자로 초기화하고 싶을 때가 있지. 예를 들어, 브랜드랑 색깔을 꼭 지정하게 하고 싶을 때:
public class Vehicle
{
public string Brand { get; set; }
public string Color { get; set; }
public Vehicle(string brand, string color)
{
Brand = brand;
Color = color;
}
}
이제 상속 클래스의 생성자에서 기본 클래스 생성자를 꼭 호출해야 해! 이럴 때 base 키워드를 써:
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public Car(string brand, string color, int numberOfDoors)
: base(brand, color) // Vehicle 생성자 호출!
{
NumberOfDoors = numberOfDoors;
}
}
실제로 어떻게 보일까?
Car bmw = new Car("BMW", "검정", 4);
Console.WriteLine($"{bmw.Brand}, {bmw.Color}, 문: {bmw.NumberOfDoors}");
bmw.Drive(); // 여전히 동작!
결국 bmw는 자동차이면서 동시에 탈것이야. 부모의 기억을 갖고 있지.
4. base와 this의 차이
C#에는 클래스 멤버를 다룰 때 this와 base 두 키워드가 있어.
- this — 현재 객체(자기 자신의 필드나 메서드)에 접근할 때 써.
- base — 기본 클래스의 멤버(즉, 부모)에 접근할 때 써.
언제 base를 써야 하냐고? 예를 들어, 기본 클래스의 메서드 로직을 확장하고 싶거나, 파생 클래스에서 기본 클래스를 초기화하고 싶을 때. 예시:
public class Bicycle : Vehicle
{
public int NumberOfGears { get; set; }
public Bicycle(string brand, string color, int gears)
: base(brand, color) // 기본 클래스 생성자 호출
{
NumberOfGears = gears;
}
public void ShowInfo()
{
// 기본 클래스 속성 사용!
Console.WriteLine($"{Brand} ({Color}), 기어: {NumberOfGears}");
}
}
5. 메서드에서 base 키워드
base는 생성자에서만 쓰는 게 아니야! 가끔 기본 클래스의 메서드 동작을 바꾸고 싶지만, 그 로직 일부는 그대로 쓰고 싶을 때가 있지.
예를 들어, Car 클래스의 Drive 메서드가 어떤 차를 운전하는지도 출력하게 하고 싶어:
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public Car(string brand, string color, int numberOfDoors)
: base(brand, color)
{
NumberOfDoors = numberOfDoors;
}
public void DriveCar()
{
base.Drive(); // 기본 구현 호출
Console.WriteLine($"운전 중: {Brand}, 문 {NumberOfDoors}개!");
}
}
설명:
base.Drive(); 호출은 이렇게 말하는 거야: "아빠, 네 얘기 먼저 해주고, 내가 디테일 좀 더할게!" 그래서 DriveCar()를 호출하면 이렇게 출력돼:
출발!
운전 중: BMW, 문 4개!
메서드 오버라이드(override)는 다음 강의에서 다룰 거야 :P
6. 상속 구조도
클래스 간 관계를 시각화해보자. 간단한 그림이야 (UML은 아침에 보면 무섭잖아!):
Vehicle (기본)
/ \
Car Bicycle
- 화살표는 항상 파생 클래스에서 기본 클래스로 향해.
- 모든 자식 클래스는 public/protected 멤버에 접근할 수 있어.
public, protected, private 차이
헷갈리지 말라고 짧게 정리!
파생 클래스에서 보이는 건:
| 한정자 | 상속받은 클래스에서 보임? |
|---|---|
| public | 응 |
| protected | 응 |
| private | 아니 |
자주 하는 실수:
파생 클래스에서 private 필드에 접근하려고 하는 거:
public class Vehicle
{
private string secret = "아무도 모를 거야!";
}
public class Car : Vehicle
{
public void RevealSecret()
{
Console.WriteLine(secret); // 오류! 안 보여.
}
}
비교: 기본 vs. 파생 클래스
| Vehicle | Car (상속) | |
|---|---|---|
| Brand | 있음 | 상속받음 |
| Color | 있음 | 상속받음 |
| Drive() | 있음 | 상속(또는 확장) |
| NumberOfDoors | 없음 | 자기만의 (고유 속성) |
7. 예제
간단한 "탈것 관리" 앱을 계속 만든다고 하면, 이렇게 구성할 수 있어:
public class Vehicle
{
public string Brand { get; set; }
public string Color { get; set; }
public Vehicle(string brand, string color)
{
Brand = brand;
Color = color;
}
public void Drive()
{
Console.WriteLine($"{Brand} {Color} 출발!");
}
}
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public Car(string brand, string color, int numberOfDoors)
: base(brand, color)
{
NumberOfDoors = numberOfDoors;
}
public void ShowCar()
{
Console.WriteLine($"브랜드: {Brand}, 색상: {Color}, 문: {NumberOfDoors}");
}
}
public class Bicycle : Vehicle
{
public int NumberOfGears { get; set; }
public Bicycle(string brand, string color, int numberOfGears)
: base(brand, color)
{
NumberOfGears = numberOfGears;
}
}
메인 메서드에서 탈것 리스트를 만들고 다양하게 출력할 수 있어:
Car ford = new Car("Ford", "빨강", 4);
ford.ShowCar();
ford.Drive();
Bicycle trek = new Bicycle("Trek", "초록", 21);
trek.Drive();
8. 실수, 특징, 그리고 약간의 농담
진짜 자주 하는 실수 — 파생 클래스에서 기본 생성자 호출을 까먹는 거. Vehicle에 매개변수 없는 생성자가 없는데 base(...) 호출 안 하면 컴파일 에러 나: 컴파일러가 "기반을 초기화 못 하겠다"고 화내. 1층 없이 2층 집 짓는 거랑 똑같아. 안 돼. 데이터를 위로 넘겨줘야 해.
두 번째 실수: 기본 클래스의 private 필드/메서드는 파생 클래스에서 안 보인다는 걸 까먹는 거. "자식"에게 뭔가 남기고 싶으면 protected를 써 (이건 다음 강의에서 더 자세히!).
그리고 제일 중요한 거: 상속은 편리하지만, 너무 복잡한 계층 구조는 만들지 마. 현실에서 상속 체인이 두세 단계 넘어가면 뭔가 잘못된 거야. 누가 누구한테 상속받았는지, 왜 이렇게 했는지 금방 헷갈려.
GO TO FULL VERSION