상속을 통해 코드를 재사용하고, 다형성을 통해 유연하고 확장 가능한 구조를 만들 수 있습니다. 이 글에서는 상속의 개념과 구현 방법, 메서드 오버라이딩과 new 키워드, 추상 클래스와 인터페이스, 다형성의 활용을 자세히 설명합니다.
상속의 개념과 구현 방법
**상속(Inheritance)**은 기존 클래스의 속성과 메서드를 새 클래스에서 재사용할 수 있는 기능입니다. 상속을 통해 부모 클래스의 기능을 확장하거나 새로운 기능을 추가할 수 있습니다. 부모 클래스는 기본 클래스(Base Class), 자식 클래스는 **파생 클래스(Derived Class)**라고도 합니다.
상속 구현 예제
using System;
class Animal // 부모 클래스
{
public string Name { get; set; }
public void Eat()
{
Console.WriteLine($"{Name}가 먹고 있습니다.");
}
}
class Dog : Animal // 자식 클래스 Dog가 Animal을 상속받음
{
public void Bark()
{
Console.WriteLine($"{Name}가 짖고 있습니다.");
}
}
class Program
{
static void Main()
{
Dog dog = new Dog();
dog.Name = "바둑이";
dog.Eat(); // 부모 클래스의 메서드 사용
dog.Bark(); // 자식 클래스의 메서드 사용
}
}
💡 Dog 클래스는 Animal 클래스를 상속받아 Name 속성과 Eat() 메서드를 사용할 수 있습니다. 또한, Dog 클래스만의 Bark() 메서드를 추가하여 기능을 확장할 수 있습니다.
메서드 오버라이딩과 new 키워드
상속을 통해 부모 클래스의 메서드를 **오버라이딩(Overriding)**하여 자식 클래스에서 재정의할 수 있습니다. 메서드 오버라이딩은 부모 클래스의 동작을 자식 클래스에 맞게 변경할 때 유용합니다. 오버라이딩하려는 메서드에는 virtual 키워드를, 자식 클래스에서 재정의할 메서드에는 override 키워드를 사용합니다.
메서드 오버라이딩 예제
using System;
class Animal
{
public string Name { get; set; }
public virtual void MakeSound() // virtual 키워드로 오버라이딩 허용
{
Console.WriteLine($"{Name}이(가) 소리를 냅니다.");
}
}
class Dog : Animal
{
public override void MakeSound() // override 키워드로 재정의
{
Console.WriteLine($"{Name}이(가) 멍멍 짖습니다.");
}
}
class Program
{
static void Main()
{
Dog dog = new Dog();
dog.Name = "바둑이";
dog.MakeSound(); // 출력: 바둑이가 멍멍 짖습니다.
}
}
💡 Dog 클래스에서 MakeSound() 메서드를 재정의하여 Dog에 맞는 소리를 출력하도록 했습니다. 이렇게 하면 상속받은 메서드를 자식 클래스에서 더 구체적으로 구현할 수 있습니다.
new 키워드
new 키워드를 사용해 부모 클래스의 메서드를 숨기고 자식 클래스의 메서드를 호출할 수 있습니다. new 키워드를 사용하면 부모 클래스의 메서드는 여전히 남아 있지만, 자식 클래스에서 호출 시 숨겨집니다.
class Animal
{
public void MakeSound()
{
Console.WriteLine("동물이 소리를 냅니다.");
}
}
class Dog : Animal
{
public new void MakeSound() // new 키워드를 사용해 메서드 숨김
{
Console.WriteLine("강아지가 멍멍 짖습니다.");
}
}
class Program
{
static void Main()
{
Dog dog = new Dog();
dog.MakeSound(); // 출력: 강아지가 멍멍 짖습니다.
}
}
💡 new 키워드는 자식 클래스에서 부모의 메서드를 숨기고자 할 때 사용합니다. 단, 이 방식은 오버라이딩이 아니며 부모 메서드를 재정의하지 않고 숨기는 것임을 주의해야 합니다.
추상 클래스와 인터페이스
**추상 클래스(Abstract Class)**와 **인터페이스(Interface)**는 객체 지향 프로그래밍에서 클래스 간의 관계를 정의하고, 특정 기능을 강제할 때 사용됩니다.
추상 클래스
추상 클래스는 인스턴스를 생성할 수 없으며, 다른 클래스가 상속을 통해 구현해야 하는 메서드를 포함할 수 있습니다. 추상 클래스는 abstract 키워드로 선언되며, 추상 메서드도 abstract 키워드를 사용하여 선언합니다.
using System;
abstract class Animal
{
public string Name { get; set; }
public abstract void MakeSound();
}
class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name}이(가) 멍멍 짖습니다.");
}
}
class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name}이(가) 야옹 소리를 냅니다.");
}
}
class Program
{
static void Main()
{
Animal myDog = new Dog { Name = "바둑이" };
Animal myCat = new Cat { Name = "나비" };
myDog.MakeSound(); // 출력: 바둑이가 멍멍 짖습니다.
myCat.MakeSound(); // 출력: 나비가 야옹 소리를 냅니다.
// 다양한 Animal 객체들을 한 번에 관리할 수 있음
Animal[] animals = { myDog, myCat };
foreach (Animal animal in animals)
{
animal.MakeSound(); // 각각의 객체에 맞는 소리를 냄
}
}
}
💡 추상 클래스는 상속을 통해서만 사용 가능하며, 추상 메서드는 반드시 자식 클래스에서 구현해야 합니다.
인터페이스
인터페이스는 클래스가 구현해야 할 메서드나 속성의 계약을 정의하며, 클래스는 여러 인터페이스를 동시에 구현할 수 있습니다. 인터페이스는 interface 키워드로 정의합니다.
using System;
abstract class Animal
{
public string Name { get; set; }
public abstract void MakeSound();
}
class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name}이(가) 멍멍 짖습니다.");
}
}
class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name}이(가) 야옹 소리를 냅니다.");
}
}
class Program
{
static void Main()
{
Animal myDog = new Dog { Name = "바둑이" };
Animal myCat = new Cat { Name = "나비" };
myDog.MakeSound(); // 출력: 바둑이가 멍멍 짖습니다.
myCat.MakeSound(); // 출력: 나비가 야옹 소리를 냅니다.
// 다양한 Animal 객체들을 한 번에 관리할 수 있음
Animal[] animals = { myDog, myCat };
foreach (Animal animal in animals)
{
animal.MakeSound(); // 각각의 객체에 맞는 소리를 냄
}
}
}
💡 인터페이스는 클래스가 여러 인터페이스를 구현할 수 있어 다중 상속의 효과를 얻을 수 있습니다. 인터페이스는 객체의 행동을 정의하는 데 유용하며, 다양한 클래스에서 동일한 기능을 구현하도록 강제할 수 있습니다.
다형성의 활용
**다형성(Polymorphism)**은 하나의 인터페이스로 다양한 데이터 타입을 처리할 수 있게 하는 특성입니다. 상속과 인터페이스를 사용하여 다형성을 구현할 수 있습니다. 다형성을 통해 코드의 재사용성과 유연성이 향상됩니다.
다형성 예제
다형성은 부모 클래스 타입의 변수가 자식 클래스 인스턴스를 참조할 수 있게 하여, 동일한 메서드를 호출하더라도 각 자식 클래스에서 재정의된 메서드가 실행되도록 합니다.
using System;
abstract class Animal
{
public string Name { get; set; }
public abstract void MakeSound();
}
class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name}이(가) 멍멍 짖습니다.");
}
}
class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name}이(가) 야옹 소리를 냅니다.");
}
}
class Program
{
static void Main()
{
Animal myDog = new Dog { Name = "바둑이" };
Animal myCat = new Cat { Name = "나비" };
myDog.MakeSound(); // 출력: 바둑이가 멍멍 짖습니다.
myCat.MakeSound(); // 출력: 나비가 야옹 소리를 냅니다.
// 다양한 Animal 객체들을 한 번에 관리할 수 있음
Animal[] animals = { myDog, myCat };
foreach (Animal animal in animals)
{
animal.MakeSound(); // 각각의 객체에 맞는 소리를 냄
}
}
}
💡 다형성을 활용하면 Animal 배열에 여러 종류의 동물을 추가하고, 동일한 MakeSound() 메서드를 호출하여 각 객체에 맞는 동작을 수행할 수 있습니다. 이를 통해 코드의 유연성과 확장성을 높일 수 있습니다.