TIL

2주차(3)-C# 문법 강의 3주차 내용-2

WMG1 2023. 8. 16. 22:23
반응형

상속의 다형성

 

 

1.상속
객체지향에서 아주 중요한 개념

1)상속이란?

기존의 클래스(부모 혹은 상위 클래스)를 확장하거나 재사용하여
새로운 클래스(자식 혹은 하위 클래스)를 생성하는 것이다.

자식 클래스는 부모 클래스의 멤버(필드, 메서드, 프로퍼티 등) 을
상속받아 사용할 수 있다.

상속을 통해 부모 클래스의 기능을 확장하거나 새로운 클래스를 정의할 수 있다.

2)상속의 장점

코드의 재사용성 : 기존 클래스의 데이터를 사용할 수 있으므로 
반복적인 코드 작성을 줄일 수 있다. 

계층 구조의 표현 : 코드 구조가 명확해진다

유지 보수성의 향상 : 위와같은 이유로 유지 보수성이 향상됨
부모 클래스 수정/버그픽스 를 진행하면
자식 클래스들도 한번에 고쳐지니 더 편해짐

3)상속의 종류

단일 상속 : 하나의 자식 클래스가 하나의 부모 클래스만 상속받는 것

C#은 단일 상속을 지원

다중 상속 : 하나의 자식 클래스가 여러 개의 부모 클래스를 동시에 상속받는 것

C#은 다중 상속을 지원하지 않음

인터페이스 상속 : 클래스가 인터페이스를 상속받는 것
인터페이스는 다중 상속을 지원하며, 클래스는 하나의 클래스와
여러개의 인터페이스를 동시에 상속받을 수 있다.


4)상속의 특징

부모 클래스의 멤버에 접근, 사용 가능(변수, 함수)
재정의 하여 자신에게 맞게 수정하거나 확장 가능(다형성)

상속의 깊이 : 단일 상속을 계속 이어서 하는것도 가능
부모 -> 자식 -> 자식 -> 자식

하지만 이렇게하면 클래스간의 관계가 복잡해져 알아보기 힘들 수 있으니 주의

5)접근 제한자의 상속
필요에 따라 조절하면 캡슐화나 정보 은닉을 구현할 수 있다.



2.자식 객체에 부모를 상속하는법

자식객체의 class에 : 부모클래스 만 해주면 된다

예시
public class Dog : Animal // Dog이라는 자식 클래스가 Animal이라는 부모 클래스를 상속받았다.

우리가 스크립트를 생성할 때 항상 보던 
public class 스크립트 명 : MonoBehavior이

우리의 스크립트는 기본적으로
MonoBehavior를 상속받는다는 뜻이였다!

이러면 자식 클래스는 자신이 가진 필드/메서드에 더해서 
부모 클래스의 필드/메서드까지 사용이 가능하다!

        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!");
            }
        }


        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();
        }
    }


일단 Dog는 그대로 사용하였고
이 때 자식 클래스 Dog의 메모리 구조를 보자면

앞부분에 Animal, 뒷부분에 Dog가 붙은 형태로 볼 수 있다.


Cat 클래스의 경우는 살짝 변형을 하였는데,
Cat에서 다시 작성된 Sleep는 Cat에 복사되어 오는 Sleep을 덮어씌운다.
호출 시 더 관계성이 있는 클래스에서 메서드를 가져온다.
(여기선 Cat class)

하지만 Cat의 경우는 다형성을 살린게 아니라 단순히 숨기고 보여준 것 뿐이므로

문제가 발생할 수 있다. 그게 뭐냐

Unit(부모 클래스)에서 메서드를 찾는 경우

자식 클래스에서 메서드를 재구현 했더라도
결국 부모클래스의 메서드를 사용하므로

숨기는 건 결국 좋지 않은 방향이다.





3.상속의 다형성 살리기

위의 숨기기를 대체하는 해결책이 이것이다.

1)가상(Virtual) 메서드
가상 메서드는 기본적으로 부모 클래스에서 정의되고
자식 클래스에서 재정의 할 수 있는 메서드이다.

가상 메서드는 virtual 키워드를 통해 선언되며, 자식 클래스에서
필요에 따라(재정의 할 필요 없음) 재정의 될 수 있음

이를 통해 자식 클래스에서 부모 클래스의 메서드를 변경하거나 확장함 

예시)


    internal class Program
    {

        public class Unit
        {
            public virtual void 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)
        {
            Marine marine = new Marine(); 
            marine.Move();
            marine.Attack();

            Zergling zergling = new Zergling();
            zergling.Move();
            zergling.Attack();


            //현재 유닛으로 참조를 하고있고
            List<Unit> list = new List<Unit>();
            list.Add(new Marine());
            list.Add(new Zergling());
            //실행 형태는 저글링과 마린이다.

            foreach(Unit unit in list) //유닛 안에 리스트의 정보들을 넣어준다.
            {
                unit.Move();
            }
        }

        

    }

부모 클래스에서 재정의가 필요한 함수를 virtual 로 가상 메서드로 만들어 주고
public virtual void Move() // 자식이 재정의를 했을 수 있다.

자식 클래스에서 상속받은 동일한 함수에  override를 붙이면 된다.
public override void Move() // 재정의 하겠다고 표시함

virtual 함수는 
부모 객체를 참조하는경우
내가 부모객체가 맞지만 virtual이 달린 함수는 실제 형태가 다를 수 있다.
그러니 실행 함수로 가서 재정의 되었는지 확인하여 봐라 라는 뜻.

이것은 virtual메서드를 부를 때 마다 확인한다.
다르다면 재정의 된 메서드를 사용한다.


요약
부모 클래스 참조할 때, 자식 클래스가 상속받은 함수를 변경하여 사용하고 있다면
똑같은 메서드 이름을 사용해 숨기지 말고
virtual & override로 재정의 확인&재정의 해주는게 좋다.
===>
override로 재정의 확인한다면 재정의 된 메서드로 실행됨

덮어 쓰기는 각자 실행하는 경우 문제x
부모 클래스 참조로 실행하는 경우 문제o


4.추상(Abstract) 클래스와 메서드
직접적으로 인스턴스를 생성할 수 없는 클래스(두루뭉실함)

주로 상속을 위한 베이스 클래스로 사용된다.

추상 클래스는 abstarct를 키워드로 선언한다. 또한 추상 메서드를 포함할 수 있다.

추상 클래스는 선언만 하고 구현은 안한다.

구현은 누가함? ==> 상속받은 메서드가 '무조건' 구현해야함
abstract는 override(재정의)에 강제성이 더해짐 

virtual과 비슷하지만 
virtual은 자식 클래스의 재정의가 있기도, 없기도 하지만

abstract 무조건 override가 존재한다는 점이 다르다.


5.오버라이딩 과 오버로딩
오버라이딩(Overriding) : 부모클래스에 이미 정의된 메소드를 자식이 재정의 한다
이때, 오버라이딩 하는 클래스들은 상속 관계에 있어야 하며, 메서드 이름,
매개변수 및 반환타입이 동일해야 한다
덮어 쓸 때 씀

오버로딩(OverLoading) : 같은 이름의 메서드가 여러개 있는것
매개변수의 타입, 개수, 순서가 달라지는만큼 생긴다.
읽어 올 때 씀

반응형