10/10 상속과 다형성

정수현·2024년 10월 20일

C#

목록 보기
2/10
post-thumbnail

OOP

⑴ 캡슐화 ⑵ 인자 전달 ⑶ 상속 ⑷ 다형성

  • 이 글에서는 상속다형성에 대해 정리하였다.

상속

  • 다른 형식을 기반으로 파생된 형식을 정의할 수 있다.
  • C#에서 클래스는 부모가 될 수 있지만, 구조체는 부모가 될 수 없다.
  • 부모의 요소를 자식이 참조할 수 있다. (업캐스팅)
  • 부모에서 정의한 멤버 메서드를 자식이 재정의 할 수 있다.

다형성

  • 같은 메서드 이름을 사용하더라도 다양한 방식으로 구현할 수 있는 능력 ??
  • 부모의 멤버 메서드를 재정의하여 사용하는 변수 형식의 멤버 메서드가 아닌 실제 참조하고 있는 개체의 멤버 메서드가 수행되게 할 수 있다. (말 진짜 좆같게 못하네;;)



상속

  • 구조체는 부모 형식을 사용할 수 없지만 인터페이스를 부모로 사용하는 것은 가능하다.
  • 클래스는 부모 형식으로 클래스와 인터페이스 모두 사용 가능하다.
  • 자식(부모로부터 파생)을 정의할 때는 자식명 뒤에 콜론을 추가하고 부모명을 지정한다.
class Base //부모
{
}

clalss Derived: Base //Base를 부모로 하는 자식
{
}

자식은 부모의 개체 부분을 포함하여 생성한다.

  • 자식은 부모의 메서드를 사용할 수 있다.
class Man
{
    public void Walk()
    {
        Console.WriteLine("걷다.");
    }
}

class Student: Man
{
    public void Study()
    {
        Console.WriteLine("공부하다.");
    }
}

class Program
{
    static void Main(string [] args)
    {
        Student student = new Student();
        student.Walk(); //부모의 메서드를 사용할 수 있다.
        student.Study();
    }
}
  • 실행 결과 ✔
    걷다.
    공부하다.



접근 지정자 protected

부모 클래스의 private

  • 부모에서 private으로 접근 지정된 멤버는 보이지 않는다.
  • 자신에게 포함된 멤버이지만 접근할 수 없다.
class Man
{
    int hp = 100;
    public void Walk()
    {
        Consolle.WriteLine("걷다.");
        hp += 2; //private 접근
    }
}

cllass Student: Man
{
    public void Study()
    {
        Console.WriteLine("공부하다.");
        hp -= 2; //오류!! 접근 불가능하다.
    }
}

기반 클래스 protected

  • protected로 접근 지정을 하게 되면 외부에서는 접근하지 못하지만 자식은 부모에 접근할 수 있다.
  • 파생된 형식(자식)에서 접근할 수 있게 하려면 부모의 멤버 속성을 통해 접근할 수 있게 해야 한다.
class Man
{
    //int hp = 100; private //private 접근이라 필요 없음
    protected int Hp
    {
        get; set;
    } = 100;
    
    public void Walk()
    {
        Console.WriteLine("걷다.");
        Hp += 2;
        Console.WriteLine(Hp);
    }
}

class Student: Man
{
    public void Study()
    {
        Console.WriteLine("공부하다.");
        Hp -= 2;
        Console.WriteLine(Hp);
    }
}

internal class _13_부모protected
{
    static void Main(string[] args)
    {
        Student stu = new Student();
        stu.Walk();
        stu.Study(); 
    }
}
  • 실행 결과 ✔
    걷다.
    102
    공부하다.
    100



생성자

생성자와 소멸자의 순서

  • 최상위 부모가 가장 먼저 생성되고, 하위 자식이 가장 먼저 소멸한다.
class Man
{
    public Man()
    {
        Console.WriteLine("Man 생성자");
    }
}

class Student:Man
{
    public Student()
    {
        Console.WriteLine("Student 생성자");
    }
}

class Program
{
    static void Main(string [] args)
    {
        Student stu = new Student();
    }
}
  • 실행 결과 ✔
    Man 생성자
    Student 생성자

부모 클래스에 기본 생성자가 없을 때

  • 기본 생성자란 매개 변수가 없는 생성자를 말한다.
    클래스의 인스턴스를 생성할 때 필드를 기본값으로 초기화하는 역할을 한다.
  • 부모 클래스에 기본 생성자가 없고 매개 변수가 있는 생성자만 있을 땐,
    자식 클래스에서 base 키워드를 사용해 부모 클래스 생성에 필요한 인자를 초기화 할 수 있다.
class Man
{  
    string name;
    public Man(string name)
    {
        this.name = name;
    }
}

class Student: Man
{
    public Student(string name): base(name)
    {
    }
}

class Program
{
    static void Main(string [] args)
    {
        Student stu = new Student("홍길동");
    }
}
  • 부모 클래스에 기본 생성자가 없을 땐 자식 클래스에서 필드를 초기화 할 수 있다.
  • 위 코드는 name을 전달 받아 출력하는 문장이 없으므로 코드를 실행시켰을 때 아무것도 출력되지 않는다.



봉인 클래스 (sealed)

  • 다른 클래스가 부모 클래스를 부모 형식으로 사용할 수 없게 정의할 수 있다.
  • class 앞에 sealed 키워드를 명시한다.
sealed class Man
{
}
calss Student: Man //오류 발생!!
{
}



다형성

  • 업캐스팅이 가능하다.
    : 부모의 요소를 자식이 참조할 수 있다.

  • 재정의 할 수 있다.
    : 변수를 통해 메서드를 호출했을 때, 구체적인 동작이 다를 수 있다.

업캐스팅이란

  • 자식 객체를 부모 타입으로 변환하는 것을 말한다.
static void Main(string [] args)
{
    Man man = null;
    man = new Stu();
}



is 연산자와 as 연산자

  • 업캐스팅 : 자식 객체를 부모 타입으로 변환 (안전하게 가능)
    Parent p = new Child();
  • 다운캐스팅 : 부모 객체를 자식 타입으로 변환 (안전하지 X)
    Child ch = (Child)p;
Parent p = new Child1(); //업캐스팅
Child1 ch1 = (Child1)p; //다운캐스팅
Child2 ch2 = (Child2)p;
  • 다운캐스팅(부모 객체를 자식 타입으로 강제 형 변환) 시 버그가 발생될 수 있다.
    is 연산자와 as 연산자를 사용하여 해결할 수 있다.

as 연산자

  • 형을 변환한다. (객체를 특정 형식으로 변환, 다운캐스팅)
  • 좌항에 참조하려는 변수가 오고, 우항에 참조하려는 타입을 명시하여 사용한다.
    p as Child
  • 참조 형식에 사용된다.
Child ch = p as Child; //자식 개체 참조 (다운캐스팅)

if(ch != null)
    ch.Study();

else
    Console.WriteLine("Child 형식 개체가 아닙니다.");

is 연산자

  • 참, 거짓을 판단한다. (객체가 특정 형식인지 판단)
  • 좌항에 참, 거짓을 판단할 변수가 오고, 우항에 타입을 명시한다.
    p is Child
  • 값 형식에서 사용 가능하다.
Parent p = new Child(); //업캐스팅

if(p is Child)
{
    Child ch = (Child)p;
    ch .Study();
}

object obj = 3; //Boxing

if(obj is int)
{
    int i = (int)obj;
    Console.WriteLine(i.ToString()); //UnBoxing
}

💡 is, as 연산자 정리

  • is 연산자는 객체가 특정 타입인지 참, 거짓을 판단한다.
  • as 연산자는 객체를 형 변환한다. (다운캐스팅)



무효화된 멤버 사용하기

  • new 키워드
  • base 키워드

new 키워드를 이용한 무효화 멤버 사용하기

부모 클래스에서 정의된 멤버와 같은 이름의 멤버를 new 키워드를 사용해 재사용하면 부모 클래스의 멤버는 무효화된다. (새로 정의하여 사용할 수 있다.)

class Man
{
    public void Work()
    {
        Console.WriteLine("일을 하다.");
    }
}

class Student:Man
{
    public new void Work()
    {
        Console.WriteLine("공부를 하다.");
    }
}

class Program
{
    static void Main(string [] args)
    {
        Student stu = new Student();
        stu.Work();
        
        Man man = stu; //Student 객체를 Man 타입으로 변환(업캐스팅)
        man.Work();
    }
}
  • 실행 결과 ✔
    공부를 하다.
    일을 하다.

  • 부모 클래스의 참조로 접근하면 부모 클래스의 멤버가 사용된다.
    stu.Work(); "공부를 하다." 출력

  • 자식 클래스의 참조로 접근하면 자식 클래스의 멤버가 사용된다.
    man.Work(); "일을 하다." 출력

base 키워드를 이용한 무효화 멤버 사용하기

base 키워드를 통해 멤버를 호출하면 무효화된 부모 클래스의 멤버를 사용할 수 있다.

class Man
{
    public void Work()
    {
        
        Console.WriteLine("일을 하다.");
    }
}

class Student:Man
{
    public new void Work()
    {
        base.Work(); //무효화된 Man의 Work 메서드를 사용할 수 있다.
        Console.WriteLine("공부를 하다.");
    }
}

class Program
{
    static void Main(string [] args)
    {
        Student stu = new Student();
        stu.Work();
    }
}
  • 실행 결과 ✔
    일을 하다.
    공부를 하다.

💡 new, base 키워드 정리

new 키워드는 부모의 멤버를 무효화 시켜 자식에서 재설정 하여 사용한다.
base 키워드는 new를 통해 무효화 시킨 부모 멤버를 다시 유효화 시켜 사용한다.



가상화와 재정의

virtual

  • 가상화
    : 부모 클래스에서 virtual 키워드를 명시하여 멤버를 선언하면 가상 멤버가 도니다.

override

  • 재정의
    : 자식 클래스에서 부모의 가상 멤버를 재정의 할 때 사용한다.

부모 클래스에서 정의한 멤버를 자식 클래스에서 재정의 하면 값 형식이 아닌 참조 형식의 멤버가 동작하도록 한다.

class Man
{
    public virtual void Work()
    {
        Console.WriteLine("일을 하다.");
    }
}

class Student:Man
{
    public override void Work()
    {
        Console.WriteLine("공부를 하다.");
    }
}

class Program
{
    static void Main(string [] args)
    {
        Man man = new Student();
        man.Work();
    }
}
  • 실행 결과 ✔
    공부를 하다.

  • 자식의 Work메서드를 호출하고 싶어서 Man man = new Student();로 업캐스팅 하였다.



추상 클래스

  • abstract 키워드를 사용한다.
  • 상속을 통해서만 사용할 수 있는 부모 클래스를 정의할 때 쓰인다.
  • "추상" → 미완성 → 객체를 만들 수 없다.
abstract class Man
{
    public Man()
    {
    }
}

class Program
{
    static void Main()
    {
        Man man = new Man(); //오류 발생!!
    }
}
  • 추상 클래스는 객체를 생성할 수 없다.

추상 메서드 캡슐화

  • abstract 키워드를 명시한다.
  • 추상 메서드의 수행 코드는 정의하지 않는다.
  • 추상 메서드를 캡슐화하면 자식 클래스에서는 추상 멤버를 재정의해야 개체를 생성할 수 있다.
abstract class Man //추상 클래스
{
    public abstract void Work(); //추상 메서드
}

class Student:Man
{
    public override void Work()
    {
        Console.WriteLine("공부를 하다.");
    }
}

class Program
{
    static void Main(string [] args)
    {
        Man man = new Student();
        man.Work(); //자식의 Work() 메서드가 호출된다.
    }
}
  • 실행 결과 ✔
    공부를 하다.

0개의 댓글