CodeGym /행동 /C# SELF /클래스 계층 구조 만들기

클래스 계층 구조 만들기

C# SELF
레벨 20 , 레슨 4
사용 가능

1. 소개

계층 구조가 없는 세상을 상상해봐: Person, Animal, Vehicle 같은 수천 개의 클래스가 전부 따로따로 있는 거지. 그런 세상에서 개발자들은 점심시간도 못 버티고 완전 헷갈려서 멘붕 올 거야! 실제 프로젝트에서는 여러 객체가 공통적으로 할 수 있는 게 필요할 때가 많아 (예를 들어, 모든 동물은 움직일 수 있음). 근데 각자만의 특징도 있지 (물고기는 수영, 새는 날기).

바로 이런 계층 구조 덕분에 엔티티들 사이의 관계를 표현할 수 있고, 프로그래밍이 창의적인 일이 되지, 복붙 전쟁이 아니게 돼.

예시를 보자. base 클래스 Animal이 있다고 해보자. 모든 동물은 소리를 낼 수 있어. 근데 고양이는 야옹, 개는 멍멍, 앵무새는 심지어 농담도 할 수 있지. 이걸 코드로 계층적으로 표현하고 싶어.

Base 클래스


public class Animal
{
    public string Name { get; set; }

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

    // 기본 메서드: 자식에서 override 가능
    public virtual void Speak()
    {
        Console.WriteLine("동물이 어떤 소리를 내고 있어...");
    }
}

여기서 virtual 키워드를 Speak() 메서드에 붙였어. 이건 "야, 자식 클래스들아, 필요하면 이 메서드 override 해도 돼"라는 표시야.

계층 구조 만들기: 파생 클래스

이제 Cat 클래스를 만들어서 Animal을 상속받아보자:


public class Cat : Animal
{
    public Cat(string name) : base(name) { }

    // Speak를 override — 고양이는 그냥 으르렁거릴 수 없지!
    public override void Speak()
    {
        Console.WriteLine($"{Name} 말한다: 야옹!");
    }
}

그리고 Dog 클래스:

public class Dog : Animal
{
    public Dog(string name) : base(name) { }

    public override void Speak()
    {
        Console.WriteLine($"{Name} 말한다: 멍멍!");
    }
}

만약 말 못하는 평범한 동물이 있다면? 그럼 base 클래스를 그대로 쓰면 돼, override 안 해도 됨.

시각화 — 계층 트리


.               Animal
                /   \
             Cat    Dog
  • Animal — base 클래스
  • Cat, Dog — 자식(파생) 클래스

2. 이런 계층 구조를 사용하는 코드 써보자

우리 콘솔 앱 작업 계속해보자.

동물 컬렉션이 있고, 각각이 자기만의 소리를 내게 하고 싶다고 해보자:

Animal[] zoo = new Animal[]
{
    new Cat("Барсик"),
    new Dog("Рекс"),
    new Animal("수수께끼 생명체")
};

foreach (Animal animal in zoo)
{
    animal.Speak();
}

예상 출력:

Барсик 말한다: 야옹!
Рекс 말한다: 멍멍!
동물이 어떤 소리를 내고 있어...

이렇게 계층 구조랑 polymorphism 덕분에 (곧 자세히 볼 거지만, 핵심은 — 객체의 실제 타입에 따라 올바른 메서드가 호출됨), 네 앱이 유연하고 확장 가능해지는 거야.

3. 새로운 메서드와 필드 추가하기

"소리내기"는 이제 끝. 근데 모든 동물이 똑같으면 너무 심심하지. 예를 들어, 고양이는 9개의 생명을 가질 수 있고, 개는 막대기 가져오기 가능.

고유 동작 추가

자식 클래스에서 자기만의 메서드랑 필드 추가할 수 있어:


public class Cat : Animal
{
    public int Lives { get; private set; } = 9;

    public Cat(string name) : base(name) { }

    public override void Speak()
    {
        Console.WriteLine($"{Name} 말한다: 야옹! 내 생명 {Lives}개야.");
    }

    public void LoseLife()
    {
        if (Lives > 0)
        {
            Lives--;
            Console.WriteLine($"{Name} 생명 하나 잃었어. 남은 생명: {Lives}");
        }
        else
        {
            Console.WriteLine($"{Name} 이미 모든 생명 다 썼어!");
        }
    }
}

코드에서 써보기:

var barsik = new Cat("Барсик");
barsik.Speak();      // Барсик 말한다: 야옹! 내 생명 9개야.
barsik.LoseLife();   // Барсик 생명 하나 잃었어. 남은 생명: 8

새 클래스 추가: "동물원" 확장하기

파생 클래스 만드는 법은 이미 알지. 예를 들어, 앵무새 추가해보자:

public class Parrot : Animal
{
    public Parrot(string name) : base(name) { }

    public override void Speak()
    {
        Console.WriteLine($"{Name} 말한다: 안녕, 인간!");
    }

    public void Repeat(string phrase)
    {
        Console.WriteLine($"{Name} 따라한다: {phrase}");
    }
}

이제 기존 코드는 건드릴 필요 없이 시스템을 쉽게 확장할 수 있어:

var keshka = new Parrot("Кеша");
keshka.Speak();                 // Кеша 말한다: 안녕, 인간!
keshka.Repeat("공부해, 학생!"); // Кеша 따라한다: 공부해, 학생!

4. 동물 행동 비교

타입 메서드 Speak() 고유 필드 추가 동작
Animal 있음 (virtual) Name
Cat 있음 (override) Lives LoseLife()
Dog 있음 (override)
Parrot 있음 (override) Repeat(string)

메모리에서 클래스 계층 구조는 이렇게 생겼어 (블록 다이어그램)

Animal (Name)
 ├── Cat (Lives)
 ├── Dog
 └── Parrot (Repeat)

5. 우리 앱에서 실전 연습

계층 구조 아이디어를 앱에 연결해보자 — 예를 들어, 여러 타입의 할 일이 있다고 해보자:

  1. Task (base 클래스): 모든 할 일 — 제목이랑 완료 상태가 있음.
  2. WorkTask (업무용): 추가로 deadline이 있음.
  3. HomeTask (집안일): 우선순위("매우 중요", "그냥 그래") 가질 수 있음.

base 클래스부터 시작:

public class Task
{
    public string Title { get; set; }
    public bool IsCompleted { get; private set; }

    public Task(string title)
    {
        Title = title;
    }

    public virtual void Complete()
    {
        IsCompleted = true;
        Console.WriteLine($"할 일 \"{Title}\" 완료!");
    }
}

이제 업무용 할 일 추가:

public class WorkTask : Task
{
    public DateTime Deadline { get; set; }

    public WorkTask(string title, DateTime deadline)
        : base(title)
    {
        Deadline = deadline;
    }

    public override void Complete()
    {
        base.Complete();
        Console.WriteLine($"마감일: {Deadline:d}");
    }
}

그리고 집안일 할 일:

public class HomeTask : Task
{
    public string Priority { get; set; }

    public HomeTask(string title, string priority)
        : base(title)
    {
        Priority = priority;
    }

    // base 클래스의 Complete로 충분하면 override 안 해도 됨
}

프로그램에서 할 일 리스트 만들기:

List<Task> tasks = new List<Task>
{
    new WorkTask("보고서 보내기", DateTime.Today.AddDays(2)),
    new HomeTask("설거지하기", "매우 중요"),
    new Task("상속 강의 읽기")
};

foreach (Task task in tasks)
{
    Console.WriteLine($"할 일: {task.Title}");
    task.Complete();
}

예상 출력:

할 일: 보고서 보내기
할 일 "보고서 보내기" 완료!
마감일: 2025.07.13
할 일: 설거지하기
할 일 "설거지하기" 완료!
할 일: 상속 강의 읽기
할 일 "상속 강의 읽기" 완료!

봐봐, 얼마나 편한지: 모든 할 일을 한데 모아서 똑같이 처리하고, 필요한 부분에서만 특성이 드러나.

6. 상속 쓸 때 흔한 실수들

실수 1: virtual로 선언 안 된 메서드를 override 하려는 경우.
base 클래스에서 메서드에 virtual 안 붙이면, 파생 클래스에서 override 못 해. 그러면 polymorphism의 유연함이 사라지고, 계층 구조가 무쓸모가 돼.

실수 2: 논리적으로 연결 안 된 엔티티끼리 상속.
객체들이 의미상 연결 안 돼 있으면 상속 쓰지 마. 예를 들어, 은 진짜 도형이 맞지만, 탈것인 건 좀 억지야. 단, 특정 맥락(예: 중세 게임)에서는 괜찮을 수도 있음.

실수 3: 너무 깊은 계층 구조.
클래스 구조가 5~6단계 이상 깊어지면, 코드 읽기/유지/테스트가 힘들어져. 이럴 땐 composition을 상속 대신 고려해봐.

실수 4: base 생성자 호출 깜빡함.
파생 클래스에 새 속성 추가할 때 base(...) 호출을 빼먹기 쉬워. 그러면 base 부분이 제대로 초기화 안 돼서, 찾기 힘든 버그가 생길 수 있음.

1
설문조사/퀴즈
상속의 개념, 레벨 20, 레슨 4
사용 불가능
상속의 개념
상속과 클래스 계층 구조
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION