나 개발자 진짜 되냐?

[ C# 기본 문법 16 ] 상속과 다형성 본문

C# 을 맛보았어요!/문법정리를 해보았어요!

[ C# 기본 문법 16 ] 상속과 다형성

Snow Rabbit 2024. 9. 23. 16:02

 

 

저번글에서는

C#은 객체지향적 언어고

다섯 가지 특징이 있다고 했었다.

 

캡슐화

다형성

상속

추상화

객체

 

그 중에서

상속 다형성에 대해 정리해보려고 한다.

 

상속 다형성

객체지향에서 굉장히 중요하다!!

 

함께 보자!


🌟 상속 🌟

우리가 부모한테 무언가를 물려받을 때 상속이라고 하듯

 

객체지향에서 상속은

부모 클래스 또는 상위 클래스를 확장하거나

재사용하여 새로운 클래스

자식 클래스 또는 하위 클래스를 생성하는 것이다.

 

자식 클래스 부모 클래스 멤버(필드, 메서드, 프로퍼티 등) 상속받아 사용

 

 < 상속 종류 >

 

단일 상속, 다중 상속, 인터페이스 상속

세 가지가 있는데

c#에서는 단일 상속만 지원해 주며

 

단일 상속은

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

 

다중 상속은

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

 

인터페이스 상속

하나의 클래스가 여러 개 인터페이스를 상속받는 것

 

다중 상속인데 인터페이스 상속은 c#에서 지원해 준다..!

 

 

 < 상속 특징 >

 

1.

자식 클래스 부모 클래스의 멤버에 접근이 가능하다.

 부모 클래스의 기능 재사용 가능

 

2.

자식 클래스 부모 클래스의 메서드를 재정의해서

입맛에 맞게 수정이 가능하다.

 

3.

상속은 계속 나무뿌리처럼 내려갈 수 있습니다.

부모 또한 자식을 하나만 두지 않고 여럿 가능하며

 

대신 우리도 촌수가 많아지면 헷갈리듯

적절하게 깊이를 유지하고 적절하게 사용하는 것이 중요

 

 

그렇다면 부모가 모든 것을 다 주고 싶지 않다면?!

 

접근 제한자로도 상속의 일부를 막을 수 있다.

private public protected

 

 

 부모 클래스의 멤버의 접근 제한자에 따라

자식 클래스에서 해당 멤버에 접근할 수 있는 범위가 결정된다.

 

이 덕분에 캡슐화와 정보은닉이 가능!

 

 

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Metrics;

namespace ConsoleApp1
{
    internal class Program
    {

        // 부모 클래스
        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 //  :부모클래스이름 적어주면 상속 완!
                                  //  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.Sleep();
            dog.Eat();
            dog.Bark();

            Cat cat = new Cat();
            cat.Name = " KKami ";
            cat.Age = 10;

            cat.Sleep(); //dog는 부모sleep가 나오고 cat는 자식sleep가 나온다.
            cat.Eat(); 
            cat.Meow();

        }
    }
}

 

연습 코드이다.

 

여기서 중요한 핵심은

 

상속은

자식 클래스 선언 : 부모 클래스 이름

 

그리고

sleep과 같이

이름이 같은 메서드가 있다면

더 가까운 곳에서 가져온다.

 

하지만 이것은 다형성은 아니다! 

왜인지는 밑에서 설명하겠다.

 


🌟 다형성 🌟

 

다형성은 같은 타입이지만 다양한 동작이 수행되는 능력이다.

 

쉽게 설명을 위해

종류별로 하나씩 설명해 보겠다!

 

 < 가상 메서드 >

기본적인 메서드

부모 클래스에서 정의되고 자식 클래스에서 재정의할 수 있는 메서드

 

vitrual이라는 키워드를 사용해서 선언하고

자식 클래스의 필요에 따라 재정의 될 수 있다.

 

아까 위에서

Sleep을 자식에게 해주면

자식 꺼가 나와서 다행인 거 같지만

 

문제는 고양이와 강아지가 엄청 많이 있을 때

일일이 구현하기가 힘들어서

부모 클래스에서 배열 및 리스트로 정렬해 주었을 때 문제가 발생한다.

 

List<Unit> list = new List<Unit>();
list.Add(new cat());
list.Add(new dog());

foreach (Unit unit in list)
{
    unit.sleep();
}

 

이렇게 출력하게 되면

아까 자식 클래스에서 바꿔주었던 식이 나오지 않고

부모 클래스 메서드가 실행된 모습이다.

 

그렇다면

 부모

자식들이 이 메서드를 재구현 할 수 있게 해 주겠어!

라고 생각하며 

선언해 주는 메서드가 바로!

 

virtual이다!

 

부모 클래스 내에 메서드에 적어주면 되고

접근제한자와 타입 사이에 넣어주면 된다.

 

public virtual void Sleep () 이렇게!

 

자식 클래스가 재구현을 했을 수 있을 테니 적어둔다!라는 느낌으로

 

자!부모 클래스에게 virtual을 써주었다.

 

그렇다면 자식 클래스 내에서도 가상을 쓴다는 것을 표시해주어야 한다!

 

자식은 virtual이 아니라 

override

라는 친구를 사용해야 한다.

 

public override void Sleep () 엄마! 저 재구현했어요!

 

자 우리는 부모 클래스를 가져온 자식 클래스이다.

우리는 부모것을 그대로 가져왔다고 표현하지만

메모리 상에서는

부모의 것 참조, 그리고 내 것 실제형태 이렇게 되어있다.

 

즉 unit는 참조

cat, dog는 실형태

 

그래서 코드들은 

foreach을 통해서 unit으로 가게 되면

unit 클래스 내에서는

virtual이 보이네?

 

조심해.. 재구현 했을 수도 있으니

실형태로 가서 바뀌어있는지 확인해 봐라!

 

그러면 이제 응 알겠어

하면서 내려가보니까

 

dog에는 재구현 흔적이 안 보이네

그럼 부모 출력!

 

 

어머어머 무슨 일이야

cat에는 override가 보이네?

정말 재구현이 됐잖아?

하며 재구현 된 자식 클래스 꺼 출력한다.

 

 < 추상 클래스 >

직접적으로 인스턴스를 생성할 수 없는 클래스

두리뭉실한 녀석

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

이것을 기점으로 만든다!

 

abstract

이라는 키워드를 사용하며 선언한다.

 

선언만 해놓고 구현부가 없는 메서드로

상속받은 자식 클래스에서 반드시 구현되어야 하는 강제성이 있다.

 

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Metrics;

namespace ConsoleApp1
{
    internal class Program
    {

        abstract class Shape // 추상 클래스
        {
            public abstract void Draw(); // 추상 메서드
        }

        class Circle : Shape
        {
            public override void Draw()
            {
                //아무것도 안써주면 에러가 난다. 왜냐면 추상친구 특징이
                //무조건 안에 무언가를 써줘야하기 때문!
                Console.WriteLine("Drawing a circle");
            }
        }

        class Square : Shape
        {
            public override void Draw()
            {
                Console.WriteLine("Drawing a square");
            }
        }

        class Triangle : Shape
        {
            public override void Draw()
            {
                Console.WriteLine("Drawing a triangle");
            }
        }

        static void Main(string[] args)
        {
            //Shape shape = new Shape();
            //오류가 난다. 왜냐면 추상적이라 객체화가 안되기 때문!
            List<Shape> list = new List<Shape>();
            list.Add(new Circle());
            list.Add(new Square());
            list.Add(new Triangle());

            foreach (Shape shape in list)
            {
                shape.Draw();
            }

        }
    }
}

 

가상친구와 다른 점은

 

가상은 재정의가 될 수도 있고 안될 수도 있어!

한번 내려가서 봐바 재정의 안되어있으면 부모 꺼 써!

 

추상은 무조건 재정의 되어있어! 내려가봐!

무조건 자식이 쓰고 있어!라는 뜻이다.

 


오버라이딩과 오버로딩의 차이

 

< 오버로딩 >

동일한 메서드 이름을 가지고 있지만,

개변수의 개수, 타입 또는 순서가 다른

여러 개의 메서드를 정의하는 것

 

오버로딩을 통해

동일한 이름을 가진 메서드를 다양한 매개변수 조합으로 호출할 수 있다.

 

public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }

    public int Add(int a, int b, int c)
    {
        return a + b + c;
    }
}

Calculator calc = new Calculator();
int result1 = calc.Add(2, 3);         // 5
int result2 = calc.Add(2, 3, 4);      // 9

< 오버라이딩 >

상속 관계에 있는 클래스 간에 발생하는 것으로

 

부모 클래스에서 이미 정의된 메서드를

자식 클래스에서 재정의하는 것을 의미

 

메서드의 이름, 매개변수 및 반환타입이 동일해야 하고

 

오버라이딩을 통해

자식 클래스 부모 클래스의 메서드를

재정의하여 자신에게 맞는 동작을 구현

 

위에서 우리가 Draw를 여러 개 구현했던 게 좋은 예시가 될 거 같다.

아예 똑같다!

안에 있는 내용만 다를 뿐..

 

둘의 명확한 차이는

오버로딩은 메서드 이름만 같고 안에 매개변수 개수 타입 순서 다 다르다면

오버라이딩은 메서드 이름도 같고 안에 매개변수 개수 타입도 같아야 한다.

 

오버로딩 : 함수 읽어오기

오버라이딩 : 함수 덮어쓰기

 

둘 다 메서드 안에 내용은 바뀌어도 되긴 한다!

오버라이딩의 경우 override를 써줘야 하는 거 잊지 말자!