Chapter 5-5. 객체 지향 : new, 다형성(virtual, override, sealed)

Date:     Updated:

카테고리:

태그:

인프런에 있는 Rookiss님의 강의 Part1: C# 기초 프로그래밍 입문 를 듣고 정리한 필기입니다. 😀

👱‍♀️ 다형성

new (다형성 X)

    class Player
    {
        public int hp;
        public int attack;

        public void Move()
        {
            Console.Write("Player Move");
        }
    }
    class Knight : Player
    {
        public new void Move()
        {
            Console.Write("Kight Move");
        }
    }
Knight knight = new Knight();
knight.Move();
💎출력💎

Kight Move

부모 클래스로부터 상속 받은 함수와 이름은 동일하지만 이와 상관없는 완전히 다른 새로운 함수로서 재정의하고 싶다면 new키워드를 사용하면 된다. 상속 받은 함수와 동일한 이름이긴 하지만 새로운 함수를 만드는 것을 의미한다. PlayerMove()KnightMove()는 완전히 다른 함수다. new는 꼭 붙이지 않아도 된다. new 붙이지 않아도 new가 있는 것처럼 작동해주기 때문에 그냥 Knight 클래스 안에서 new붙이지 않고 public void Move()라고 정의하기만 해도 완전히 새로운 함수라고 정의된다. 그러나 new를 붙여주면 개발자 입장에선 의미를 알기 쉽기 때문에 붙이는 것을 권고하는 것 같다!


다형성

가상함수와 오버라이딩(virtual, override)

상속 하는 함수라도 부모 변수로 자식 객체들을 참조할 때(업캐스팅) 자식들마다 실행 내용을 자동으로 다르게 호출 하고 싶을 때.

    class Player
    {
        public int hp;
        public int attack;

        public virtual void Move()
        {
            Console.Write("Player Move");
        }
    }
    class Knight : Player
    {
        public override void Move()
        {
            Console.Write("Knight Move");
        }
    }

    class Mage : Player
    {
        public override void Move()
        {
            Console.Write("Mage Move");
        }
    }

...

int Test(Player player)
{
    player.Move();
}

매개 변수인 player는 인수로 꼭 Player 타입의 객체 뿐만 아니라 그의 자식들인 Knight, Maze 타입의 객체도 참조할 수 있다. player어떤 타입의 객체가 들어오느냐에 따라서 player.Move()코드 한 줄이 다르게 실행되게 하려면 1️⃣ Player에서 Move()를 가상 함수로 선언하고, 2️⃣ 자식 클래스에서 이 가상함수를 오버라이딩, 재정의 하면 된다. 이것이 바로 다형성 개념이다~!

  • player.Move();
    • Player player = new Player();
      • PlayerMove() 실행. “Player Move” 출력
    • Player player = new Knight();
      • KnightMove() 실행. “Knight Move” 출력
    • Player player = new Mage();
      • MageMove() 실행. “Mage Move” 출력

부모 클래스에서 가상 함수는 virtual 키워드를 붙이면 되고, 자식 클래스에서 이 가상 함수를 오버라이딩 하려면 override 키워드를 붙이며 된다. virtual은 성능 부하가 있기 때문에 남발하여 만들지 않고 꼭 다형성이 필요한 경우만 가상 함수로 선언하는 것이 좋다. 재정의 없이 그대로 상속 받는 경우가 많은게 성능상 좋긴 하다!

    class Player
    {
        public int hp;
        public int attack;

        public virtual void Move()
        {
            Console.Write("Player Move");
        }
    }
    class Knight : Player
    {
        public void Move()
        {
            Console.Write("Kight Move");
        }
    }
Player player = new Knight();

player.Move();
💎출력💎

Player Move

PlayerMove()는 가상 함수이나 자식인 Knight 클래스에선 이를 오버라이딩 하지 않았다. 따라서 부모의 Move()와는 관련 없이 이름만 똑같을 뿐인 새로운 Move()를 정의하는 것과 같다. 즉, new를 붙인 것과 같은 상태다. 따라서 업캐스팅 하였어도 player.Move()PlayerMove()가 호출된다. (C++, C#은 Java와 달리 참조 하는 '변수'의 타입을 기준으로 멤버 함수를 호출한다. 가상 함수여서 다형성을 실형하는게 아니라면 변수의 타입을 기준으로 호출한다.)

    class Player
    {
        public int hp;
        public int attack;

        public virtual void Move()
        {
            Console.Write("Player Move");
        }
    }
    class Knight : Player
    {
        public override void Move()
        {
            Console.Write("Kight Move");
        }
    }
Player player = new Knight();

player.Move();
💎출력💎

Knight Move

자식 클래스인 Knight에서 이를 오버라이딩하여, player.Move()에서 PlayerMove()가 아닌, 재정의한 KnightMove()가 호출되었다.

    class Player
    {
        public int hp;
        public int attack;

        public virtual void Move()
        {
            Console.Write("Player Move");
        }
    }
    class Knight : Player
    {

    }

    class Mage : Player
    {
        public override void Move()
        {
            Console.Write("Mage Move");
        }
    }
Player knightPlayer = new Knight();
Player magePlayer = new Mage();

knightPlayer.Move();
magePlayer.Move();
💎출력💎

Player Move
Mage Move

knightPlayer는 가상 함수 Move()를 오버라이딩 하지 않고 그냥 그대로 부모인 PlayerMove()를 상속 받았다. 따라서 knightPlayer.Move()PlayerMove()를 호출한다. 반면에 magePlayer는 가상 함수 Move()를 오버라이딩 하였다. 따라서 magePlayer.Move()MageMove()를 호출한다.

    class Player
    {
        public int hp;
        public int attack;

        public virtual void Move()
        {
            Console.Write("Player Move");
        }
    }
    class Knight : Player
    {
        public override void Move()
        {
            base.Move(); // 기능 추가
            Console.Write("Kight Move");
        }
    }
Player player = new Knight();

player.Move();
💎출력💎

Player Move
Knight Move

base 키워드를 사용하여 재정의 하는 대상인 부모 클래스의 가상함수를 사용하는 식으로 부모 함수의 기능을 일부 추가할 수 있다. Knight에서 Move()를 오버라이딩 할 때 base.Move() 하는 내용도 추가했기 때문에 PlayerMove()도 같이 실행할 수 있게 되었다.


sealed

sealed는 오버라이딩 할 때 같이 사용된다. 손자, 증손자 클래스들에서 더 이상 오버라이딩 하지 못하게, 딱 나까지만 오버라이딩이 가능하도록 강제한다. C++엔 없고 C#에만 있는 문법이며 그리 자주 쓰이진 않는다.

    class Player
    {
        public int hp;
        public int attack;

        public virtual void Move()
        {
            Console.Write("Player Move");
        }
    }
    class Knight : Player
    {
        public sealed override void Move()
        {
            Console.Write("Kight Move");
        }
    }

    class SuperKnight : Knight
    {
        public override void Move()  // ❌❌❌ 컴파일 에러 ! 
        {
            Console.Write("Kight Move");
        }
    }

Knight에서 Move()를 오버라이딩할 때 sealed 키워드를 통해 봉인해 두었기 때문에 손자 클래스인 SuperKnight에선 Move()를 오버라이딩 할 수 없다. 상속 받은 것을 그대로 써야 한다.



🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

C Sharp 카테고리 내 다른 글 보러가기

댓글 남기기