상속 (Inheritance)
상속은 객체지향(OOP)의 핵심 특징 중 하나다.
이미 만들어진 부모 클래스(base class)의 속성과 기능을 자식 클래스(derived class)가 물려받아 사용할 수 있는 구조를 말한다.
상속을 통해 중복 코드를 줄이고 유지 보수성을 크게 높일 수 있으며 계층 구조 설계가 가능해진다.
예시를 통해 상속이 왜 필요한지 알아보자.
class Sedan
{
float _maxSpeed;
string _brand;
public void SedanAction()
{
Console.WriteLine("동승자 태우기");
}
public void Drive()
{
Console.WriteLine("주행");
}
}
class Truck
{
float _maxSpeed;
string _brand;
public void TruckAction()
{
Console.WriteLine("짐 싣기");
}
public void Drive()
{
Console.WriteLine("주행");
}
}
class Bulldoser
{
float _maxSpeed;
string _brand;
public void BulldoserAction()
{
Console.WriteLine("밀어버리기");
}
public void Drive()
{
Console.WriteLine("주행");
}
}
이렇게 Car, Truck, Bulldoser 세가지 클래스를 만들었다.
그런데 이 세가지 클래스는 모두 겹치는 필드나 메서드가 존재한다.
지금은 세개 뿐이지만 만약 더 많은 클래스를, 더 많은 필드와 메서드가 중복되게 관리한다면 영 좋지 못한 상황이 발생할 것이다.
유지 보수도 힘들어질 뿐더러 혹시나 수정이 필요한 경우 실수가 발생할 확률도 올라간다.
그래서 이 세가지들의 공통점이라고 할, 상위 요소인 Car 클래스를 만들어 줄 거다.
class Car
{
protected float _maxSpeed; // 자식 클래스에서 접근 가능
private string _brand = "default";
public Car()
{
Console.WriteLine($"{_brand}의 차가 만들어졌습니다.");
}
public void Drive()
{
Console.WriteLine("주행");
}
}
protected 접근자가 처음 등장했는데 이건 외부에는 공유하지 않지만 자식 클래스를 포함한 클래스 내부에는 공유한다는 의미 정도로 생각하면 된다.
추가로 상속 관계에서 자주 쓰이는 것이 sealed 접근자인데, 이걸 사용하면 더 이상 그 클래스 아래로 상속이 불가능하게 봉인된다.
자 이제 Car와 겹쳐지는 부분들을 세 클래스에서 제거하고 Car를 상속해서 구성해보자.
class Sedan : Car
{
int _passengers;
public void SedanAction()
{
Console.WriteLine("동승자 태우기");
Console.WriteLine($"최고 속도는 {_maxSpeed} 입니다.");
}
}
class Truck : Car
{
public void TruckAction()
{
Console.WriteLine("짐 싣기");
}
}
class Bulldoser : Car
{
public void BulldoserAction()
{
Console.WriteLine("밀어버리기");
}
}
아주 깔끔해졌다.
이렇게 부모, 자식 상속 관계를 잘 구성하면 클래스들이 공유하는 필드값이나 메서드를 추가, 수정 할 때 훨씬 편리하고 안전하게 작업할 수 있다.
base()
부모 클래스에 생성자가 있으면, 자식 클래스는 반드시 이를 호출해야 한다.
이때 사용하는 게 base다.
class Car
{
private string _carPlate;
public Car(string inputPlate)
{
_carPlate = inputPlate;
Console.WriteLine($"차가 만들어졌습니다. 번호판: {_carPlate}");
}
}
class Sedan : Car
{
int _passengers;
public Sedan(int passengers, string inputCarPlate) : base(inputCarPlate)
{
_passengers = passengers;
Console.WriteLine($"{_passengers}인승 세단 생성 완료");
}
}
기본적으로 자식 요소에 있는 생성자는 base()가 숨겨져있다고 생각하면 된다.
만약 부모 요소의 생성자가 어떠한 인자값을 요구할 시, base를 이용해 전달하는 방식으로 구성된다.
이렇게 구성하면 Car의 생성자 -> Sedan의 생성자 순으로 실행되어 아래처럼 출력된다
차가 만들어졌습니다. 번호판 ~~~
~~ 인승 세단 생성 완료
오버라이딩 (Override)
오버로딩과 이름도 동작도 뭔가 비슷해서 자주 혼동되는 개념이다.
자식 클래스에서 부모의 메서드를 덮어쓰는 거다.
사용법은 매우 간단하다.
부모 메서드에 virtual을 붙이고, 자식 메서드에 override를 붙이면 된다.
class Car
{
public virtual void Drive()
{
Console.WriteLine("주행");
}
}
class Sedan : Car
{
public override void Drive()
{
Console.WriteLine("세단만의 특별한 주행 방식");
}
}
상속의 상속
상속 아래 상속을 더 할 수 있다.
하지만 이 깊이가 깊어지는 건 지양하는 게 좋다.
일단 다른 이유를 떠나 몇 뎁스나 깊이 상속된 클래스를 나는 작업하고 싶지 않다...
class Sedan : Car
{
public Sedan(int passengers, string inputCarPlate) : base(inputCarPlate)
{
Console.WriteLine($"{passengers}인승 세단 생성");
}
}
class SportsCar : Sedan
{
public SportsCar(int passengers, string inputCarPlate) : base(passengers, inputCarPlate)
{
_maxSpeed = 220;
Console.WriteLine("스포츠카 생성");
}
}
다형성 (Polymorphism)
상속의 강력한 특징이 바로 다형성이다.
부모 타입 변수에 자식 객체를 담을 수 있는 놀라운 사실!
Car monster = new SportsCar(2, "12가3456");
// monster는 Car 타입이지만 실제로는 SportsCar 객체
monster.Drive(); // 오버라이딩된 Drive() 실행
다형성을 잘 이용하면 더 유연하게 코딩이 가능하다.
상속에 대해서는 말로 제대로 풀어 설명하기가 쉽지 않아 코드 위주의 글이 됐다...
어떻게 하면 잘 설명할 수 있을까...
'C#' 카테고리의 다른 글
| C# - 오버로딩과 오버라이딩의 차이 (0) | 2025.09.19 |
|---|---|
| C# - 클래스 Static 실습 문제 풀이 (0) | 2025.09.19 |
| C# - 클래스와 Static (0) | 2025.09.18 |
| C# - 클래스와 구조체의 차이 (0) | 2025.09.17 |
| C# - 메모리와 가비지 컬렉터 (GC) (0) | 2025.09.17 |