수업을 들으며 노트에 정리한 것 (상속)

공재원
∙∙조회 6

개요

비슷한 클래스를 여러 개 만들다 보면 같은 코드가 반복된다. 상속은 이미 만든 클래스의 멤버를 물려받아 새 클래스를 만드는 문법이다. 물려받은 쪽은 차이 나는 부분만 적으면 된다.

객체 지향 언어의 필수 (상속)

유지 보수의 베이스


상속의 필요

  • 같은 코드가 반복 되는 자리

공통부를 따로 떼기

  • 공통 멤버를 담은 클래스 하나를 만들고, 나머지는 그것을 물려받으면 된다.

상속 문법

  • 클래스 이름 뒤에 콜론 (:) 과 물려받을 클래스 이름을 적는다
arduino
class 자식클래스 : 부모클래스
  • 물려주는 쪽과 물려받는 쪽은 부르는 이름이 여러가지로 문서마다 다른 말을 쓰니 모두 알아 둘 필요가 있다.
    (영어까지 외우기)
물려주는 쪽물려받는 쪽
부모 클래스 (parent class)자식 클래스 (child class)
기반 클래스 (base class)파생 클래스 (derived class)
상위 클래스 (super class)하위 클래스 (sub class)

물려받는 것과 물려받지 않는 것

멤버물려받는가
필드물려받음
프로퍼티물려받음
메서드물려받음
인덱서물려받음
생성자물려받지않음

물려받되 보이지 않는 것

  • private멤버도 자식 객체 안에 함께 만들어진다. 다만 자식 클래스에서는 닿을 수 없다.

protected

  • private 자신의 클래스 안에서만 사용 가능
  • public 아무에게나 열려있음
  • protected 자식에게만 열고 바깥에서는 불가능
  • private와 public사이에 있는 중간 단계가 protected다.
접근 모디파이어클래스 안자식 클래스바깥
private○✕✕
protected○○✕
public○○○

protected 필드는 자식이 직접 값을 바꿀 수 있다는 뜻이므로, 검사가 필요한 값이면 필드를

private으로 두고 protected 프로퍼티나 메서드로 통로를 내는 편이 안전함.

생성자와 상속

  • 생성자는 물려 받지 않는다.
  • 부모에 매개변수가 있는 생성자만 있으면 자식은 그것을 그냥 쓸 수 없다.
csharp
Student student = new Student();

class Person
{
    public string Name;

    public Person(string name)
    {
        Name = name;
    }
}

class Student : Person
{
}
  • 자식이 생성자를 따로 정의하고, 부모 몫의 초기화는 부모 생성자에게 넘겨야 한다.

base()로 부모 생성자 부르기

  • 자식 생성자의 매개변수 목록 뒤에 : (base(...)를 붙이면 부모 생성자가 먼저 실행된다.
csharp
using System;

Student student = new Student("홍길동", 3);
Console.WriteLine($"이름: {student.Name}, 학년: {student.Grade}");

class Person
{
    public string Name;

    public Person(string name)
    {
        Name = name;
        Console.WriteLine($"Person 생성자: {name}");
    }
}

class Student : Person
{
    public int Grade;

    public Student(string name, int grade) : base(name)
    {
        Grade = grade;
        Console.WriteLine($"Student 생성자: {grade}학년");
    }
}
makefile
Person 생성자: 홍길동
Student 생성자: 3학년
이름: 홍길동, 학년: 3

출력 순서를 보면 부모 생성자가 먼저 끝나고 자식 생성자 본문이 실행됨. 자식이 부모 필드를 만지기 전에 부모 쪽이 준비를 끝내야 하기 때문임.

base()를 생략하면

  • base(...) 나 this(...)를 적지 않은 인스턴스 생성자는 부모의 매개변수 없는 생성자를 암시적으로 호출한다.
    접근 가능한 해당 생성자가 없으면 인수를 받는 부모 생성자를 명시해야 한다.

부모에 매개변수 없는 생성자가 없으면 이 자동 호출이 실패하므로, 그때는 base()를 반드시 적어야 함.

여러 단계의 상속

  • 상속은 여러 단계로 이어질 수 있으며, 생성자는 가장 위쪽 부모부터 차례로 실행된다.
Mermaid
렌더링 중...

자식 쪽 필드 이니셜라이저가 먼저 돌고, 그 다음 부모의 필드와 생성자, 마지막에 자식 생성자 본문이 실행됨.

📝 메모: 물려받은 멤버는 base.없이 이름만으로 씀. base를 반드시 적어야 하는 대표적인 경우가 여기서 다룬 base()생성자 호출임

base()와 this()

  • 생성자 앞에 붙일 수 있는 것은 둘 중 하나다.
  • this()는 같은 클래스의 다른 생성자
  • base()는 부모 생성자

상속 계층 설계

  • is-a관계 / 상속은 "자식은 부모의 한 종류다" 가 성립할때 사용된다.
관계성립 여부
개는 동물이다성립 — class Dog : Animal
학생은 사람이다성립 — class Student : Person
정사각형은 도형이다성립 — class Square : Shape
자동차는 엔진이다성립하지 않음
주문은 상품이다성립하지 않음

말이 되지 않는데도 코드가 겹친다는 이유로 상속하면, 자식이 쓸 일 없는 멤버를 물려받게 되고 부모를 고칠 때마다 관계없는 자식이 깨짐.

상속 대신 조합

  • “은 이다”가 아니라 “**은 을 가진다”라면 상속이 아니라 필드로 가지는 것**이 맞다. 이것을 조합이라고 부른다.
판단문법
자식은 부모의 한 종류다 (is-a)상속 — class Dog : Animal
이것은 저것을 가진다 (has-a)조합 — private Engine engine;

C#에서 클래스는 부모를 하나만 가질 수 있다.

sealed클래스

  • 더 이상 물려줄 일이 없는 클래스에는 sealed를 붙여 상속을 막을 수 있다.

네임스페이스

  • 프로그램이 커지면 서로 다른 목적의 클래스가 같은 이름을 원하게 된다.
  • 네임스페이스는 타입을 묶어 이름의 범위를 나누는 이름표다.

계층 구조

  • 네임스페이스는 점으로 이어 계층을 만든다.
csharp
using System;

Game.Characters.Hero hero = new Game.Characters.Hero();
hero.Show();

Game.Items.Sword sword = new Game.Items.Sword();
sword.Show();

namespace Game.Characters
{
    class Hero
    {
        public void Show()
        {
            Console.WriteLine("Game.Characters.Hero");
        }
    }
}

namespace Game.Items
{
    class Sword
    {
        public void Show()
        {
            Console.WriteLine("Game.Items.Sword");
        }
    }
}

이름은 회사명.제품명.기능형태로 짓고 PascalCase를 씀.
폴더 구조와 네임스페이스를 맞춰 두면 파일을 찾기 쉬워짐.

arduino
MyGame/
├── Characters/
│   ├── Hero.cs        → namespace MyGame.Characters
│   └── Enemy.cs       → namespace MyGame.Characters
└── Items/
    └── Sword.cs       → namespace MyGame.Items

오늘은 C#언어에서 중요한 상속에 대하여 배웠다.
상속을 조금씩 이해하고, 과거에 했던 공부들을 복습하다보니 다시금 문제가 조금씩 풀려가기 시작했다.
지금의 내 모습이 아직 만족스럽지는 않지만 뿌듯한 감정은 든다.
이 모습 그대로 변하지 않고 꾸준히 성장했으면 좋겠다.