유니티 부트캠프 8기/Ch02. 프로그래밍 기초(C#)

상속과 다형성, 숨기기, 재정의, 가상 메서드, 추상 클래스, 인터페이스

Imperor 2025. 1. 27. 11:51
        // 부모 클래스
        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 void Bark()
            {
                Console.WriteLine("Dog is bark");
            }
        }
        public class Cat : Animal
        {
            public void Meow()
            {
                Console.WriteLine("Cat is Meow");
            }
            public void Sleep()
            {
                Console.WriteLine("Cat Sleep");
            }
        }

 

Cat의 Sleep은 다음과 같은 경고창이 생성된다

 

override를 붙이지 않았으니 재정의를 하지 않은 것이다

 

        static void Main(string[] args)
        {
            Dog dog = new Dog();
            dog.Name = "Bobby";
            dog.Age = 3;
            dog.Eat();
            dog.Sleep();
            dog.Bark();

            Cat cat = new Cat();
            cat.Name = "kkami";
            cat.Age = 10;
            cat.Eat();
            cat.Sleep();
            cat.Meow();
        }

 

 

그런데도 호출하면 Cat.Sleep이 호출된다

C++에서는 Animal.Sleep이 호출되는데...

이상하다

 

이건 재정의를 숨기는 방법이라 다형성이 아니다

재정의를 숨기는 기능은 override와 별개의 기능이다

 

new를 붙여서 해결할 수 있다고 하는데

이러면 이름이 같은 다른 메서드가 생성되는 것이니 다형성이라고 할 수 없다

https://dreamzelkova.tistory.com/entry/C-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%93%9C%EB%A9%94%EC%86%8C%EB%93%9C-%EC%9E%AC%EC%A0%95%EC%9D%98override

 

C# - 메소드 메소드 재정의(override), 숨기기

메소드 재정의(override) ※ override와 overload를 햇갈리지 말자. 메소드 재정의(override)는 상속 관계의 클래스 사이에서 부모클래스의 메소드를 자식클래스가 부모의 메소드를 받아서 다른 형태로 바

dreamzelkova.tistory.com

 

 

 

 

문제가 없지 않을까? 싶지만

다형성을 사용할 때 문제가 발생한다

 

부모의 자료형으로 자식을 저장하여 관리할 수 있다

이는 자식 클래스의 종류, 자식의 인스턴스 개수가 많을 때 효과적으로 관리하는 기법인데

재정의를 숨기는 방법으로는 이게 안된다!

            List<Animal> animals = new List<Animal>();
            animals.Add(dog);
            animals.Add(cat);
            foreach (Animal animal in animals)
            {
                animal.Sleep();
            }

 

 

Cat의 Sleep 호출이 안된다!

Animal으로 저장하고 있기 때문에

Animal에서 가장 가까운 곳의 Sleep을 호출하기 때문에 Animal의 Sleep이 호출되는 것이다

 

이러한 문제를 해결하기 위해 나온 것이 가상 메서드

 

virtual, override 실습

        public class Unit
        {
            // virtual: Unit이지만, 실제 형태는 Marine 또는 Zergling 일 수 있다
            // 실형태가 다를 수 있으니 실형태에 재정의가 되어있는지 확인
            // 참조형태와 실형태가 다를때 부모의 형태로서 관리를 하고 각각의 형태들이 다르게 동작을 할 수 있게 한다
            public virtual void Move()  // 자식이 재정의를 했을 수 있다(안했다면 부모의 Move를 사용)
            {
                Console.WriteLine("두발로 걷기");
            }

            public void Attack()
            {
                Console.WriteLine("Unit 공격");
            }
        }

        public class Marine : Unit
        {

        }

        public class Zergling : Unit
        {
            public override void Move()
            {
                Console.WriteLine("네발로 걷기");
            }
        }

 

        static void Main(string[] args)
        {
            // Unit: 참조의 형태(잡힌 메모리의 일정 부분을 가지고 있다)
            // Marine, Zerglind: 실형태(실제로 메모리에 잡혀있는 형태)
            List<Unit> list = new List<Unit>();
            list.Add(new Marine());
            list.Add(new Zergling());
            foreach (Unit one in list)
            {
                one.Move();
            }
        }

호출해보았다

 

C++ 공부할때 virtual table을 잠깐 공부했던 기억이 난다

복습해야겠다

 

virtual

1. 재정의(override)가 선택 사항

2. virtual 키워드가 포함된 클래스는 인스턴스화가 가능

3. 일반 메소드는 new를 통해 재정의할 수 있지만 부모클래스를 기반으로 자식을 인스턴스화하면 부모 메소드가 나온다

4. 일반 메소드와 달리 override를 통해 재정의하면 부모 클래스를 기반으로 자식을 인스턴스화 하더라도 제대로 자식 메소드가 나온다

 

abstract

1. 자식 클래스에서 반드시 재정의

2. 필드 혹은 메소드에 abstract 사용하려면 클래스도 abstract (추상 클래스)

3. abstract 클래스라고 해서 모든 구성요소까지 모두 abstract여야 하는 것은 아니다

4. abstract 클래스는 인스턴스화 불가능 

 

 

  virtual(가상) abstract(추상)
재정의(override) 선택 필수
인스턴스화 가능 불가
메서드 일반 클래스에서도 사용 가능 추상 클래스에서만 사용 가능

 

그리고

가상함수를 추상클래스에서 사용 가능하다

 

using System;

// 추상 클래스
abstract class Base
{
    public abstract void Show();  // 추상 메서드
    public virtual void Display() // 일반 가상 메서드
    {
        Console.WriteLine("Base Display()");
    }
}

class Derived : Base
{
    public override void Show()  // 반드시 구현해야 함
    {
        Console.WriteLine("Derived Show()");
    }

    public override void Display() // 선택적으로 재정의 가능
    {
        Console.WriteLine("Derived Display()");
    }
}

class Program
{
    static void Main()
    {
        Base obj = new Derived();
        obj.Show();    // "Derived Show()"
        obj.Display(); // "Derived Display()"
    }
}

 

추상클래스에서 일반 가상함수를 사용할 수 있다

쓰던 그대로, virtual이 있으면 재정의할 필요는 없지만, override해서 재정의해도 된다

 

 


추상클래스는 abstract 키워드를 붙이며

부모 클래스에서 관리하는 건 상속과 같지만

부모 클래스에는 메서드를 선언만 하고

자식 클래스에서는 그 메서드를 "필수로" 구현해야한다

 

추상 클래스는 일부 동작의 구현을 가질 수 있다. 추상메서드를 포함하고 있기만 하면 된다. 다중상속 불가능

하지만 인터페이스는 구현을 가지지 않는다. 다중상속 가능

 

 

추상클래스와 인터페이스를 좀 더 알아보자

 

이번 팀 프로젝트때 아이템의 구조를 인터페이스의 상속을 받은 추상클래스의 상속을 받은 일반클래스로 만들었다

분석이 필요할 것 같다

using System.Diagnostics;
using System.Xml.Linq;

namespace SpartaDungeon
{
    public interface ItemData
    {
        string Name { get; }
        int ItemType { get; }   // 1: 무기 2: 갑옷, 3: 포션, 
        // string itemText { get {switch문을 작성} }
        // 기존의 switch문(확장 용이)
        string ItemText
        {
            get
            {
                switch (ItemType)
                {
                    case 1: return "무기";
                    case 2: return "갑옷";
                    case 3: return "포션";
                    default: return "알 수 없음";
                }
            }
        }
        int ClassType { get; }   // 1: 전사 2: 마법사 3: 궁수 4: ALL
        // switch 표현식 사용(간결한 방법)
        string classText => ClassType switch
        {
            1 => "전사",
            2 => "마법사",
            3 => "궁수",
            _ => "캐릭터 직업이 없습니다(다시 불러오기)"
        };

        float Attack { get; }
        float Defence { get; }
        float Hp { get; }
        float Mp { get; }
        string Description { get; }
        int Price { get; }
    }

    public abstract class Equipment : ItemData
    {
        public string Name { get; set; }
        public int ItemType { get; set; }
        public string ItemText { get; set; }
        public int ClassType { get; set; }
        public string ClassText { get; set; }
        public float Attack { get; set; }
        public float Defence { get; set; }
        public float Hp { get; set; }
        public float Mp { get; set; }
        public string Description { get; set; }
        public int Price { get; set; }
        public bool IsEquipable { get; set; }


    }
    public class Armor : Equipment // 정확하게 명시해서 작성할것
    {
        public Armor(string name, int itemtype, string itemtext,int classtype, string classtext,float attack, float defense, float hp, float mp, string description, int price)
        {
            Name = name;
            ItemType = itemtype;
            ItemText = itemtext;
            ClassType = classtype;
            ClassText = classtext;
            Attack = attack;
            Defence = defense;
            Hp = hp;
            Mp = mp;
            Description = description;
            Price = price;
        }
        public Armor()
        {
            Name = "";
            ItemType = 0;
            ItemText = "";
            ClassType = 0;
            ClassText = "";
            Attack = 0f;
            Defence = 0f;
            Hp = 0f;
            Mp = 0f;
            Description = "";
            Price = 0;
        }
    }

    public class Weapon : Equipment
    {
        public Weapon(string name, int itemtype, string itemtext,int classtype, string classtext,float attack, float defense, float hp, float mp, string description, int price)
        {
            Name = name;
            ItemType = itemtype;
            ItemText = itemtext;
            ClassType = classtype;
            ClassText = classtext;
            Attack = attack;
            Defence = defense;
            Hp = hp;
            Mp = mp;
            Description = description;
            Price = price;
        }
        public Weapon()
        {
            Name = "";
            ItemType = 0;
            ItemText = "";
            ClassType = 0;
            ClassText = "";
            Attack = 0f;
            Defence = 0f;
            Hp = 0f;
            Mp = 0f;
            Description = "";
            Price = 0;
        }
    }

    public class Potion : Equipment
    {
        public Potion(string name, int itemtype, string itemtext,int classtype, string classtext, float attack, float defense, float hp, float mp, string description, int price)
        {
            Name = name;
            ItemType = itemtype;
            ItemText = itemtext;
            ClassType = classtype;
            ClassText = classtext;
            Attack = attack;
            Defence = defense;
            Hp = hp;
            Mp = mp;
            Description = description;
            Price = price;
        }
        public Potion()
        {
            Name = "";
            ItemType = 0;
            ItemText = "";
            ClassType = 0;
            ClassText = "";
            Attack = 0f;
            Defence = 0f;
            Hp = 0f;
            Mp = 0f;
            Description = "";
            Price = 0;
        }
    }
}

 

 

인터페이스

1. 다중상속 가능

2. 상속받은 클래스가 인터페이스를 구현할 경우, 모든 인터페이스 멤버를 구현해야함

3. 인터페이스는 클래스가 아니며, 클래스에 대한 제약 조건

4. 상속의 개념(not Is A)이 아니라 기능을 탑재하는 개념(Has A)

5. 접근 제한자를 사용할 수 없어서 모든 필드는 public이 적용

 

추상클래스

1. 직접 인스턴스를 생성할 수 없는 클래스

2. 추상 메서드는 구현부가 없으며, 자식 클래스에서 반드시 구현해야 한다

3. 필드 혹은 메서드에 abstract를 사용하려면, 클래스도 abstract여야 한다(추상 클래스)

 

  Interface Abstract Class
접근 제한자 -함수에 대한 접근 지정자를 가질 수 없다
-기본적으로 public
함수에 대한 접근 지정자를 가질 수 있다
구현 구현이 아닌 서명만 가질 수 있다 구현을 제공할 수 있다
속도 느리다 빠르다
인스턴스화 X X
필드 X 상수와 필드를 정의할 수 있다
메소드 추상메소드만 추상메소드, 비추상메소드가 있을 수 있다
생성자 X O
다중상속 가능 불가능