나만의 작은 도서관
[C#] 문법 정리 #5. 상속과 다형성(virtual, abstract 키워드) 본문
상속(Inheritance)
상속은 기존의 클래스(부모 클래스 또는 상위 클래스)를 확장하거나 재사용하여 새로운 클래스를 정의하는 것을 의미한다. 상속받은 클래스는 부모 클래스와 동일한 멤버(필드, 메서드, 속성 등)를 상속받으며, 상속받은 멤버들을 사용할 수 있게된다. C#에서 상속은 상속받을 클래스 이름 오른쪽에 ': [부모_클래스_이름]'을 추가하여 상속한다.
선언부 예제
// 부모 클래스
public class Animal
{
public string Name { get; set; }
public int Age { get; set; }
public void Eat()
{
Console.WriteLine("Animal is eating.");
}
public void Sleep()
{
Console.WriteLine("Animal is sleeping.");
}
}
// 자식 클래스
public class Dog : Animal
{
}
public class Cat : Animal
{
}
실행부 예제
class Program
{
static void Main(string[] args)
{
Dog dog = new Dog();
Cat cat = new Cat();
dog.Eat(); // Animal is eating.
cat.Eat(); // Animal is eating.
}
}
상속의 종류
상속의 종류는 다음과 같다.
- 단일 상속: 하나의 자식 클래스가 하나의 부모 클래스만 상속.
- 다중 상속: 하나의 자식 클래스가 여러 개의 부모 클래스를 동시에 상속. C#은 다중 상속을 지원하지 않는다.
- 인터페이스 상속: 클래스가 인터페이스를 상속. C#은 인터페이스는 다중 상속이 가능하다.
한 마디로, C#은 하나의 부모 클래스와 여러 개의 인터페이스를 상속받을 수 있다.
C#에서 다중 상속을 지원하지 않는 이유
1. 다이아몬드 문제(Diamond Problem)
하나의 클래스에서 다중 상속을 할 경우, 부모 클래스가 같은 클래스로부터 상속받는 경우가 있을 수 있다. 위 사진을 예로 들자면, FatherA 클래스와 FatherB 클래스가 같은 GrandFather클래스로부터 상속받는 상황에서 Son클래스는 FatherA, FatherB로부터 상속 받으려한다. 이 때, Son클래스는 FatherA, FatherB 클래스로부터 동일한 이름의 멤버와 메소드들을 상속받게 되는데( GrandFather 클래스의 멤버), 상속받는 입장인 Son클래스는 둘 중 어느 멤버를 사용해야하는 지 모호해지는 문제가 발생한다.(추가로, FatherA, FatherB 클래스에서 오버라이딩으로 재정의한 메서드라도 존재한다면, 더욱 문제가 커진다.)
이렇듯 같은 클래스를 상속받은 서로 다른 부모 클래스들로부터 상속 받았을 때 동일한 이름의 멤버의 상속으로 인한 모호성이 발생하는 문제를 다이아몬드 문제라고 한다. (추가로, 다이아몬드 형태가 아니더라도 동일한 이름의 멤버의 상속으로 인한 모호성은 충분히 발생할 수 있다.)
이러한 모호성을 해결하기 위한 규칙이 필요하게 되는데, 이로 인해 코드가 복잡해지고 가독성이 저하될 수 있다.
2. 설계의 복잡성 증가
다중 상속을 허용하면 클래스 간의 관계가 복잡해지게 된다. 클래스가 다중 상속을 받을 경우, 어떤 클래스로부터 어떤 멤버를 상속받을 지 결정하는 것이 더욱 어려워진다. 이렇듯 다중 상속을 하게되면 상속 관계를 파악하기 어려워지고, 코드의 유지 보수를 더욱 힘들게 하는 요인이 될 수 있다.
결론
C#은 다중 설계를 했을 때 클래스 간 관계의 복잡성 증가, 멤버 이름 충돌 문제 해결의 어려움을 이유로 설계의 일관성과 단순성을 유지하기 위해 다중 상속을 지원하지 않는다.
다형성(Polymorphism)
다형성은 같은 타입, 같은 이름의 메서드가 다양한 동작들을 수행할 수 있는 특성을 말한다. 다형성은 메서드 오버로딩(Overloading)과 오버라이딩(Overriding)으로 구현되며, 오버라이딩은 가상 메서드(Virtual Method)와 추상 클래스(Abstract Class)를 통해 구현된다. 주로 같은 부모 타입으로 묶어서 메서드를 실행해도 각각의 자식 메서드가 호출되도록 하고 싶을 때 사용한다.
오버라이딩(Overriding)
오버라이딩은 부모 클래스에서 이미 정의된 메서드를 자식 클래스에서 재정의하는 것을 의미한다. 부모-자식 관계에 있는 클래스 간에 발생하며, 오버라이딩할 메서드는 내부 로직을 제외한 나머지 부분(메서드의 이름, 매개변수 및 반환 타입)이 동일해야한다.
가상 메서드(Virtual Method)
가상 메서드는 부모 클래스에서 이미 정의된 메서드를 자식 클래스에서 재정의 할 수 있도록 허용한 메서드이다. 가상 메서드는 virtual 키워드로 선언하며, 가상 메서드로 선언된 메서드는 자식 클래스에서 override 키워드를 통해 오버라이딩 하거나, 오버라이딩 하지않고 그대로 사용할 수도 있다.
+) 왜 가상(virtual)이라고 이름이 붙었는지 궁금해서 좀 찾아봤다. C#이나 C++에서 가상(virtual)이라는 개념은 "가상 세계에서는 상황에 따라 다른 모습을 가질 수 있다"는 개념을 가져와 컴파일 시점에서 호출한 메서드가 고정되지 않고 런타임에서 동적으로 바인딩되는(객체 타입에 따라 호출하는 메서드가 달라지는) 것을 가상이라고 하는 것 같더라...
선언부 예제
public class Shape
{
public virtual void Draw()
{
Console.WriteLine("Drawing a shape.");
}
}
public class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle.");
}
}
public class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a rectangle.");
}
}
실행부 예제
class Program
{
static void Main(string[] args)
{
Shape shape1 = new Circle();
Shape shape2 = new Rectangle();
List<Shape> shapes = new List<Shape>() {shape1, shape2};
.
foreach (Shape shape in shapes)
{
shape.Draw();
}
// 가상 메서드를 사용해서 가능한 결과(동적 바인딩이 되었기 때문!)
// [출력 결과]
// Drawing a circle.
// Drawing a rectangle.
}
}
추상 클래스(Abstract Class)
추상 클래스는 직접적으로 인스턴스를 생성할 수 없는 클래스이다. 주로 상속을 위한 베이스 클래스로 사용되며, 메서드 구현에 강제성을 부여하는 것이 목적이다. 추상 클래스는 abstract 키워드를 사용하여 선언되며, 추상 메서드를 포함할 수 있게된다. 추상 메서드 선언 시, 상속받은 클래스는 해당 클래스를 override를 통해 반드시 구현해야한다.
선언부 예제
abstract class Shape
{
public abstract void Draw();
}
class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle");
}
}
class Square : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a square");
}
}
실행부 예제
List<Shape> shapes = new List<Shape>();
shapes.Add(new Circle());
shapes.Add(new Square());
foreach (Shape shape in shapes)
{
shape.Draw();
}
// [출력 결과]
// Drawing a circle.
// Drawing a square.
'Unity > 문법 및 메소드(System)' 카테고리의 다른 글
[C#] 문법 정리 #7. 값형과 참조형, 박싱과 언박싱 (0) | 2024.11.22 |
---|---|
[C#] 문법 정리 #6. 인터페이스 (0) | 2024.11.21 |
[C#] 문법 정리 #4. 클래스 (0) | 2024.11.21 |
[C#] 문법 정리 #3. 배열과 컬렉션(+제너릭) (1) | 2024.11.21 |
[C#] 문법 정리 #2. 문자열 (0) | 2024.11.20 |