본문 바로가기

Programming

가상 메서드와 다형성 : 쉽고 재미있는 C# Programming 의 기본

 

가상 메서드와 다형성은 밀접하게 연관된 개념이며, 주로 함께 다뤄집니다. 가상 메서드는 다형성을 구현하기 위한 중요한 도구로 사용됩니다.

 

다형성( Polymorphism, 多形性 )은 한 객체가 여러 형태를 가질 수 있는 능력을 나타내고, 가상( 假想 ) 메서드 (Virtual Method )는 이 다형성을 구현하기 위한 메커니즘 중 하나입니다.

 

자식 클래스에서 부모 클래스의 가상 메서드를 재정의함으로써, 같은 메서드를 호출해도 다양한 동작이 가능해집니다.

이 두 가지 개념을 이해하고 활용함으로써 유연하고 확장 가능한 코드를 작성하는데 도움이 됩니다.


 

1. 가상 메서드 ( Virtual Method )

 

가상 메서드는 상속을 통해 자식 클래스에서 재정의될 수 있는 메서드입니다. 부모 클래스에서 virtual 키워드로 선언되며, 자식 클래스에서 override 키워드를 사용하여 재정의할 수 있습니다.

 

1.1 가상 메서드의 선언 ( virtual )

class 부모클래스
{
    public virtual void 가상메서드()
    {
        // 부모 클래스에서의 구현
    }
}

 

1.2 자식 클래스에서의 재정의 ( override )

class 자식클래스 : 부모클래스
{
    public override void 가상메서드()
    {
        // 자식 클래스에서의 재정의된 구현
    }
}

 

자식 클래스에서는 override 키워드를 사용하여 부모 클래스의 가상 메서드를 재정의 할 수 있습니다.

 

override 키워드를 사용하여 메서드를 재정의하는 이유는 다형성을 지원하고, 상속된 메서드의 행동을 자식 클래스에서 변경하거나 확장하기 위함입니다.

 

1.2 다형성 ( Polymorphism )

 

다형성은 동일한 이름의 메서드나 속성이 여러 클래스에서 다르게 구현될 수 있는 능력을 나타냅니다.

같은 인터페이스나 부모 클래스를 통해 다양한 형태의 객체를 사용할 수 있는 능력을 의미합니다.

 

가상 메서드를 사용하면 부모 클래스의 참조 변수로 자식 클래스의 객체를 다룰 때, 실행 시간에 실제 객체의 타입에 맞게 적절한 메서드가 호출됩니다.

 

이는 코드의 유연성을 높이고, 확장성을 갖추게 해주는 중요한 객체지향 프로그래밍의 특성 중 하나입니다.

부모클래스 객체 = new 자식클래스();
객체.가상메서드(); // 자식 클래스에서의 재정의된 구현이 호출됨

 

※ 메서드 호출의 동적 바인딩 : 다형성은 실행시간 (Runtime)에 실제 객체의 타입에 따라 메서드 호출이 동적으로 바인딩되는 특성을 가지고 있습니다. 즉, 컴파일 시간에는 어떤 메서드가 호출될지 정해지지 않고, 실행 시간에 결정됩니다.

 

* 컴파일 시간과 실행시간

 

- 컴파일 시간은 코드가 컴파일되어 기계어로 변환되는 시간을 의미하고, 실행시간은 프로그램이 실제로 실행되는 시간을 의미합니다.

 

* 정적 바인딩 vs 동적바인딩

 

- 정작 바인딩은 컴파일 시간에 메서드 호출이 결정되는 것을 의미합니다. 즉, 코드가 컴파일되는 동안에 어떤 메서드가 호출될지 결정되며, 이는 주로 정적 타입 언어에서 발생합니다.

 

- 동적 바인딩은 실행 시간에 메서드 호출이 결정되는 것을 의미합니다. 즉, 코드가 실행되는 동안 객체의 실제 타입을 고려하여 어떤 메서드가 호출될지 동적으로 결정됩니다.

 

* 다형성과 동적바인딩

 

- 다형성은 동적 바인딩과 밀접한 관련이 있습니다. 다형성을 통해 부모 클래스의 참조변수가 자식 클래스의 객체를 참조할 수 있습니다. 이때, 메서드 호출이라도 객체의 실제 타입에 따라 다르게 동작됩니다.

 

* 실행 시간에 메서드 결정

 

- 동적 바인딩은 실행 시간에 메서드 호출이 어떤 객체의 실제 타입에 따라 결정됩니다.

이를 통해 같은 메서드 호출이더라도 실제 객체의 특성에 맞게 동작할 수 있습니다.

class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("Animal makes a sound.");
    }
}

class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Dog barks.");
    }
}

class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Cat meows.");
    }
}

class Program
{
    static void Main()
    {
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();

        animal1.MakeSound(); // Dog barks.
        animal2.MakeSound(); // Cat meows.
    }
}

 

위 코드에서 MakeSound 메서드는 부모클래스, Animal 에 정의되어 있습니다.

그런데 실행 시간에 실제 객체 Dog 또는 Cat 일 때, 각 객체의 MakeSound 메서드가 호출됩니다.

각각의 객체는 각기 다른 소리를 내기때문에 실행 시간에 각 객체의 특성에 맞게 지정된 방식으로 호출됩니다.

이것이 동적바인딩의 예시입니다.

 

가상 메서드와 다형성은 코드의 유연성과 확장성을 증가시키는데 중요한 역할을 합니다.

상속 구조에서 부모 클래스의 가상 메서드를 자식 클래스에서 재정의함으로써, 같은 메서드 호출이 다양한 형태의 객체에서 다르게 동작하게 됩니다.

 

이는 객체지향의 설계의 핵심 개념 중 하나로, 복잡한 시스템을 보다 유지보수 가능하고 확장 가능하게 만들어줍니다.


[ 가상 메서드와 다형성에 대한 예제 ]

using System;

// 동물을 나타내는 부모 클래스
class Animal
{
    // 가상 메서드로 소리를 내는 기능을 정의
    public virtual void MakeSound()
    {
        Console.WriteLine("동물이 소리를 냅니다.");
    }
}

// 강아지 클래스 (동물 클래스를 상속)
class Dog : Animal
{
    // 부모 클래스의 가상 메서드를 재정의
    public override void MakeSound()
    {
        Console.WriteLine("강아지가 왈왈 소리를 냅니다.");
    }
}

// 고양이 클래스 (동물 클래스를 상속)
class Cat : Animal
{
    // 부모 클래스의 가상 메서드를 재정의
    public override void MakeSound()
    {
        Console.WriteLine("고양이가 야옹 소리를 냅니다.");
    }
}

class Program
{
    static void Main()
    {
        // 다형성을 활용한 예제

        // 부모 클래스의 참조 변수로 자식 클래스의 객체를 참조
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();

        // 다형성에 따라 각 객체의 실제 타입에 맞게 메서드 호출
        animal1.MakeSound(); // 강아지가 왈왈 소리를 냅니다.
        animal2.MakeSound(); // 고양이가 야옹 소리를 냅니다.

        // 부모 클래스의 객체를 생성
        Animal genericAnimal = new Animal();

        // 부모 클래스의 메서드 호출
        genericAnimal.MakeSound(); // 동물이 소리를 냅니다.
    }
}

* Animal 클래스에서는 MakeSound 라는 가상 메서드가 정의되었습니다.

 

* Dog 및 Cat 클래스에서는 Animal 클래스의 가상 메서드를 override 하여 각 동물이 특유의 소리를 내도록 재정의 했습니다.

 

* Main 메서드에서는 다형성을 활용하여 부모 클래스의 참조 변수로 자식 클래스의 객체를 참조하고, 각 객체의 메서드를 호출했습니다.

강아지가 왈왈 소리를 냅니다.
고양이가 야옹 소리를 냅니다.
동물이 소리를 냅니다.

* animal1은 Dog 클래스의 객체를 Animal 클래스의 참조 변수로 참조하고 있습니다. 따라서 animal1.MakeSound()는 실제 객체인 강아지의 MakeSound 메서드를 호출하며 "강아지가 왈왈 소리를 냅니다."가 출력됩니다.

 

* animal2는 Cat 클래스의 객체를 Animal 클래스의 참조 변수로 참조하고 있습니다. 따라서 animal2.MakeSound()는 실제 객체인 고양이의 MakeSound 메서드를 호출하며 "고양이가 야옹 소리를 냅니다."가 출력됩니다.

 

* genericAnimal은 Animal 클래스의 객체를 나타냅니다. 따라서 genericAnimal.MakeSound()는 부모 클래스의 MakeSound 메서드를 호출하며 "동물이 소리를 냅니다."가 출력됩니다.

 

이처럼 다형성을 통해 부모 클래스의 참조 변수로도 각 객체의 특성에 맞게 메서드가 호출되는 것을 확인할 수 있습니다.

 

공통된 기능(메서드)을 부모 클래스에 넣고, 자식 클래스에서 그중 일부를 필요에 따라 재정의(override) 하고 싶은 경우, 부모 클래스에서 해당 메서드를 virtual 로 선언하고 자식 클래스에서 override 로 재정의 합니다.

이렇게 하면 자식클래스에서 공통된 기능 중 일부를 자신에 맞게 재정의할 수 있습니다.

 

* 요약 : Overloading (오버로딩) : 스타크래프트 게임의 저그 오버로드와 비슷합니다. 각각의 오버로드에 타고 있는 유닛은 다를 수 있습니다. 즉, 같은 메소드의 이름을 가졌을지라도 전달인자의 타입이나 개수가 다른 경우에는 각각 다른 메서드로 취급됩니다.

 

부모에게 상속받은 여러기능을 자식이 원하는 데로 재정의 하는 것을 (Overriding) 오버라이딩이라 합니다.