'virtual'에 해당되는 글 2건

  1. 2018.04.10 [C#]abstract 한정자 의미와 활용 1
  2. 2018.04.04 [C#] virtual 함수 의미 및 활용 2

abstract 란?

abstract 한정자의 의미는 abstract가 사용되는 곳에 누락되거나 불완전한 구현이 있다는 것을 의미합니다. 즉, 파생 클래스에서 그 불완전하거나 누락된 부분을 구현해야하는거죠. abstract 클래스의 목적은 여러 파생 클래스에서 공유할 수 있는 기본 클래스의 공통적인 정의를 제공하는 것입니다. 일반적으로는 클래스로 묶을 때, 하위 클래스가 반드시 구현해야하는 규칙을 명시하거나 상위 클래스를 통해 접근하고자 할 때 사용하는 것으로 알고있습니다.

쉽게 생각하면 골자만 만들어진 설계도 정도로 보면 되지 않을까요. 자동차를 예를 들면 차는 문이 있고 운전대와 좌석이 있어야 한다. (여기까지가 abstract) 차 종류별로 위의 규칙은 지키되 문이 위로 열릴수도 옆으로 열릴수도 있게끔 만드는 거죠. (하위 클래스의 구현)


특징

  • abstract 한정자는 클래스, 메서드, 프로퍼티, 인덱서, 이벤트와 함께 사용가능하다.
  • abstract가 사용되면 파생클래스에서 구현되어야 한다.
  • 추상 클래스 (abstract class)
    • 추상 클래스는 인스턴스화 될 수 없다.
      • 변수와 메서드 선언은 일반 클래스와 동일하게 할 수 있다. (물론 virtual도 사용가능)
    • 상속을 제한하는 sealed와는 같이 사용할 수 없다.
  • 추상 메서드 (abstract method)
    • 추상 메서드의 선언은 추상 클래스에서만 허용된다.
    • 추상 메서드는 암시적으로 가상(virtual) 함수이다. 
      • 기본적으로 동작은 virtual 함수와 같지만, virtual과 달리 abstract는 파생 클래스에서 반드시 구현해야한다.
    • 추상 메서드는 구현부가 존재하지 않는다. ex) public abstract void test();
    • 파생 클래스에서는 동일한 시그니쳐 메서드에 override 키워드를 통해 재정의한다.
    • private, static, virtual 키워드와는 사용 불가능하다.


예제


    
    public abstract class Monster
    {
        public int hp;
        public void setHp(int _hp)
        {
            hp = _hp;
        }
        public abstract void hit();
    }

    public class Orc : Monster
    {
        
        public override void hit()
        {
            Console.WriteLine("Orc hit HP: " + hp);
        }
    }

    public class Elf : Monster
    {
        public override void hit()
        {
            Console.WriteLine("Elf hit HP: " + hp);
        }
    }

Monster를 abstract 클래스로 만들고,  setHp라는 일반 메서드와 hit라는 추상 메소드를 선언했습니다.

Orc와 Elf는 Monster를 상속받아 hit를 재정의 하고 있습니다.

상속받는 클래스에서 hit를 override하지 않으면 오류가 발생합니다.

    
        static void Main(string[] args)
        {
            Orc monster1 = new Orc();
            Elf monster2 = new Elf();

            monster1.setHp(10);
            monster1.hit();
            monster2.setHp(20);
            monster2.hit();

            Monster monster3 = new Orc();
            Monster monster4 = new Elf();


            Console.WriteLine("////////////////////");
            monster3.setHp(30);
            monster3.hit();
            monster4.setHp(40);
            monster4.hit();

        }


위는 각 클래스 변수에 해당 클래스의 인스턴스를 담고, 아래는 Monster 변수에 하위 클래스 인스턴스를 담고 있습니다.

어느쪽도 abstract에서 구현된 setHp 함수가 잘 동작하고, hit역시 상속받은대로 잘 동작되고있습니다.

abstract 클래스는 자기 자신을 인스턴스로 삼을 수 없습니다.


abstract 의 활용

 이전 포스팅인 virtual에서 언급되었듯이 abstract는 여러 클래스들이 공통적으로 가져야 할 특징들을 정의해 놓는 것이죠. virtual과 크게 다른 점은 virtual은 선택형이고, abstract는 반드시 구현해야 한다는 점과 인스턴스로써 만들 수 있냐의 차이입니다. 때문에 abstract는 몬스터의 hit, attack, move 등 모든 몬스터가 반드시 가져야 할 함수를 선언하여 몬스터라는 그룹을 정의 할 때 쓰입니다.  모든 몬스터가 공통적으로 행동해야하는 함수라면 미리 구현을 하고, 모든 몬스터가 공통적으로 가지되 개별적으로 동작해야하는 함수라면 abstract로 선언하여 상속시 반드시 구현하도록 하죠. 

 예를들어 abstract를 활용해 RPG 게임의 몬스터를 만든다고 생각해보죠. 가장 상위 클래스인 Monster는 타격시 호출되는 Hit는 공통적으로 쓰기위해 구현하고, 나머지 move와 attack은 하위 클래스가 반드시 구현할 수 있도록 abstract로 만듭니다. 그리고 선공형, 비선공형 몬스터로 소분류를 만들어 Move에서 적을 쫒거나 무시하는 로직을 추가해 구현을 하고, 그 하위로 세부 몬스터인 Orc나 Slime에서 특징에 따라 Attack을 만듭니다. ( 몬스터별로 얼음을 쓰기도 독을 쓰기도 하니까요.)

  
    public abstract class Monster
    {
        public void Hit()
        {
            //Do Hit;
        }
        public abstract void Move();
        public abstract void attack();
    }
    
    public abstract class AggressiveMonster:Monster
    {
        public override void Move()
        {
            //Move Aggressive
        }
    }
    
    public class Slime:AggressiveMonster
    {
        public override void attack()
        {
            //Attack with poison
        }
    }

 이런식이지 않을까요. 이렇게 하게 몬스터를 분류별로 나누어 유지보수가 용이해지고, 다른 클래스를 작성하는 사람 입장에서는 Monster 클래스만 보고 어떤 함수를 호출해야할지 감을 잡을 수 있죠.

 * 활용 예시는 지극히 개인적인 생각입니다. 혹시 문제가 있거나 잘못된 점이 있으면 말씀부탁드립니다.


Posted by 검은거북

virtual 함수란?

 부모 클래스에서 virtual 키워드를 사용하여 함수를 만들면, 자식 클래스에서 이 함수를 재정의 할 수 있도록 허용하겠다는 의미입니다.


 특징

 - virtual 이 붙은 함수는 자식 클래스에서 재정의가 가능합니다.

 - 자식 클래스에서는 new 또는 override 키워드가 사용가능하다.

    - override는 재정의를 하겠다는 확장의 의미이고, new 는 기본 클래스를 숨기는 의미이다. 

 - 자식클래스의 함수 시그니쳐가 동일해야 재정의가 가능하다.

 - 자식클래스의 함수는 base 키워드를 사용해 부모 클래스의 함수를 호출 할 수 있습니다.

 - abstract 와는 달리 자식클래스에서 구현은 선택이다. (구현 안하면 부모의 함수 사용)

 - static, abstract, private, override 키워드와는 사용이 불가능하다.

예제

    public class Monster
    {
        public virtual void hit()
        {
            Console.WriteLine("Monster hit");
        }
    }

    public class Orc : Monster
    {
        public override void hit()
        {
            Console.WriteLine("Orc hit");
        }
    }

    public class Elf : Monster
    {
        public new void hit()
        {
            Console.WriteLine("Elf hit");
        }
    }

    public class Wolf : Monster
    {
        public void hit()
        {
            Console.WriteLine("Wolf hit");
        }
    }

위에서 Monster 클래스는 hit 함수에 virtual 키워드를 지정해 재정의를 허용했습니다.

Orc는 override를, Elf는 new 키워드를, Wolf는 별도 지정없이 동일한 시그니쳐의 함수를 작성했습니다.


이제 사용을 해보죠.

   class Program
    {
        static void Main(string[] args)
        {
            Monster monster1 = new Monster();
            Orc monster2 = new Orc();
            Elf monster3 = new Elf();
            Wolf monster4 = new Wolf();

            monster1.hit();
            monster2.hit();
            monster3.hit();
            monster4.hit();

            Monster monster5 = new Orc();
            Monster monster6 = new Elf();
            Monster monster7 = new Wolf();


            Console.WriteLine("////////////////////");
            monster5.hit();
            monster6.hit();
            monster7.hit();

        }
    }

monster 1~4 는 각 클래스별로 몬스터를 만들고,  monster 5~7은 Monster라는 변수에 각 하위 클래스의 인스턴스를 생성해 담아서 출력시켰습니다.


결과는

monster 1~4 는 그대로 잘 출력되네요. 사실 각 클래스에 자기 자신을 인스턴스로 담으면 virtual의 의미가 전혀 없습니다. ( 이렇게 쓸거면 재정의가 필요없죠)

하지만 monster 5~7은 결과가 다르죠.

ovrride를 한 Orc만 제대로 재정의되어 자신의 hit를 출력하고 있고, 나머지는 상위 클래스의 hit를 출력합니다.

여기서 new 키워드의 기본 클래스를 숨긴다는 의미도 알 수 있죠. 상위 클래스 변수에 담기면 하위 클래스가 아닌 상위 클래스의 함수를 호출한다는 것입니다. ( new 키워드를 사용하지 않은 경우, 즉 Wolf의 경우에는 new와 동일한 동작을 합니다.)


그럼 만약에 상위 클래스가 virtual 키워드를 쓰지 않았다면?

virtual 함수를 쓰지않으면 하위 클래스에서는 override를 쓰지 못합니다. 당연히 monster 6~7과 동일하게 동작합니다. 


결국 virtual과 override를 사용하여 재정의하는 이유는 상위 클래스 변수에 하위 클래스 인스턴스를 담을 때, 하위 클래스의 함수를 호출하고 싶기 때문이죠.

(Monster monster5 = new Orc();)



virtual 함수의 활용


 그럼 이런 재정의는 언제 활용을 하는걸까요

 상위 클래스 변수에 하위 클래스 인스턴스를 담아야 할 경우는 언제일까요

 위의 hit 함수를 생각해보죠.

 모든 몬스터가 Monster라는 하나의 클래스로 만들어지면 좋겠지만, 각자의 특징에 따라 Monster 하위 클래스가 만들어질 수 있습니다. 
그리고 Player가 몬스터를 때렸을때, 해당 몬스터의 hit를 호출한다고 칩시다.

virtual 함수를 쓰지 않는다면 어떻게 될까요? 
       void attack(Orc monster)
        {
            monster.hit();
        }
        void attack(Elf monster)
        {
            monster.hit();
        }
        void attack(Wolf monster)
        {
            monster.hit();
        }
 헉... 모든 해당 몬스터 즉, orc, elf, wolf 별로 hit 함수를 오버로딩하여 별도로 호출해야 합니다. 
 왜냐하면... virtual을 사용하지 않으면 저렇게 해야만 해당 몬스터의 hit를 호출할 수 있으니까요. ( 사용하는 입장에서 Orc가 hit를 override를 했을지, 안했을지 모르잖아요?)

만약 virtual 함수를 사용하고, 다른 하위 클래스에서 hit함수를 override했다면?
        void attack(Monster monster)
        {
            monster.hit();
        }

이거 하나면 됩니다.


monster.hit() 가 호출될 때 Monster 변수에 담긴 인스턴트별로 재정의 되어있는 hit 함수를 호출해 줄테니까요.

몬스터 종류가 무수히 많아졌을 때, 마냥 몬스터별로 함수를 오버로딩해서 작성할 수는 없습니다. 반드시 virtual이 필요해지죠.



* 사실 위와 같은 방식은 abstract 또는 interface로도 가능은 하죠. 

 하지만 abstract, interface와는 달리 virtual은 선택적으로 재정의가 가능하다는 점virtual 함수를 가진 상위 클래스도 인스턴스 생성이 가능하다는 점이 있습니다.

 솔직히 위의 Hit함수만 보면 어차피 모든 클래스가 구현을 해야한다면 abstract로 만드는게 맞지 않냐고 할 수 있습니다. Hit만 있다면 맞는 말이죠. 그리고 Monster라는 하나의 그룹을 정의하는 거라면 abstract가 맞습니다. 하지만 Orc 중에서 엘리트 Orc는 Hit시 반격을 하고 싶다면?모든 Orc의 함수는 동일하게 상속받아 행동하고, Move만 재정의 해야 한다면, virtual이 필요해지게 되죠. Orc도 하나의 인스턴스로써 동작해야하고, 엘리트 Orc는 hit를 재정의해서 만들어져야 하니까요. 즉, 특정 개체로부터 특정 함수만을 선택적으로 재정의하고, 그 외에는 그대로 상속 해야할 경우인거죠.

 결국 각자의 특징에 알맞게 사용해야겠죠. 예를 들어 abstract는 하위 클래스가 반드시 구현해야하는 것을 명시하거나 하나로 묶을 때, interface는 class에 한정되지 않고, 범용적으로 사용할 때 또는 디자인 정의가 필요할 때, virtual은 일부 함수에 대해 선택적으로 재정의가 필요할 때.

 






Posted by 검은거북
이전버튼 1 이전버튼

블로그 이미지
프로그래밍 공부 요약 및 공부하며 발생한 궁금증과 해결과정을 포스팅합니다.
검은거북

공지사항

Yesterday
Today
Total

달력

 « |  » 2025.1
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

최근에 올라온 글

최근에 달린 댓글

글 보관함