CodeGym /행동 /C# SELF /추상 클래스와 메서드

추상 클래스와 메서드

C# SELF
레벨 22 , 레슨 1
사용 가능

1. 소개

추상 클래스new로 바로 인스턴스 만들 수 없는 클래스야. 이건 다른, 더 구체적인 클래스들이 이걸 기반으로 만들어지게 하려고 있는 거지. 일종의 템플릿이나 "설계도" 같은 거라서, 파생 클래스들한테 공통 구조랑 동작을 정해주지만, 구체적인 구현은 걔네가 알아서 하게 남겨두는 거야.

이해하기 쉽게 비유해볼게: "교통수단"이라는 개념이 있다고 치자. 현실에서 아무도 추상적인 "교통수단"을 타고 다니진 않잖아(상상 속에서나 가능하겠지). 누군가는 자동차를 타고, 누군가는 배를 타고, 누군가는 비행기를 타지. 추상 클래스는 네 설계도 "교통수단"이야. 이건 모든 교통수단이 움직일 수 있어야 한다(Move)는 걸 정해주지만, 어떻게 움직이는지는 자식 클래스에서 정해야 해.


// 추상 클래스 예시
public abstract class Transport
{
    public string Model { get; set; }

    // 추상 메서드 - 구현 없음
    public abstract void Move();
}

Transport 타입의 객체를 만들려고 하면 컴파일 에러 나! 한 번 해봐 — 컴파일러가 바로 "추상 클래스 인스턴스 만들 수 없다"고 뭐라 할 거야.

언제, 왜 추상 클래스가 필요할까?

  • 비슷하지만 다른 객체들의 공통 특성과 동작을 한 군데에 모으고 싶을 때.
  • "공통 규칙"을 정하고 싶을 때: 예를 들어 "모든 자식은 움직일 수 있어야 한다".
  • 몇몇 메서드는 공통 구현이 있지만, 일부 로직은 파생 클래스에서 구체적으로 정해야 할 때.

추상 클래스는 큰 시스템 아키텍처 설계할 때, 모두에게 공통인 "기능"은 여기서 정하고, 세부사항은 각 상속받는 클래스가 구현하게 할 때 딱 좋은 도구야.

2. 추상 클래스와 일반 클래스의 차이

여기서 헷갈리는 경우 많으니까, 확실히 정리해보자!

추상 클래스 vs 일반 클래스

  • 일반 클래스new로 인스턴스 만들 수 있고, 모든 메서드가 구현돼 있을 수 있어.
  • 추상 클래스:
    • 바로 인스턴스 못 만듦(abstract로 클래스 선언)
    • 구현된 메서드도 있고, 추상 메서드(몸체 없는)도 있을 수 있음
    • 필드, 프로퍼티, 생성자, static 메서드도 가질 수 있음

public abstract class Animal
{
    public string Name { get; set; }
    public void Sleep()
    {
        Console.WriteLine("동물이 자고 있어!");
    }

    public abstract void MakeSound();
}

3. 추상 메서드와 프로퍼티

추상 메서드

추상 메서드는 추상 클래스에서 선언만 하고, 몸체(구현)는 없어. 파생 클래스가 자기 방식대로 꼭 구현해야 해.


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

    // 추상 메서드
    public abstract void MakeSound();
}

추상 프로퍼티

추상 프로퍼티도 선언할 수 있어서, 모든 자식이 꼭 구현하게 만들 수 있어:


public abstract class Shape
{
    // 추상 프로퍼티
    public abstract double Area { get; }
}

4. 추상 클래스 상속

추상 클래스는 추상 메서드도, 구현된 메서드도 가질 수 있어. 파생 클래스는 모든 추상 메서드랑 프로퍼티를 꼭 구현해야 하고, 안 그러면 그 클래스도 추상 클래스가 돼.

이제 우리 "동물 앱"을 좀 더 발전시켜보자:


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

    public void Sleep()
    {
        Console.WriteLine($"{Name} 달콤하게 자고 있어!");
    }

    public abstract void MakeSound();
}

이제 일반(추상 아님) 클래스를 만들어보자:


public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine($"{Name}가 말해: 멍멍!");
    }
}

MakeSound() 구현 안 하면 컴파일 에러 볼 수 있어.

다이어그램: 추상 클래스가 있는 클래스 계층 구조


+---------------------+
|   Animal (abstract) |
+---------------------+
| Name: string        |
| + Sleep()           |
| + MakeSound()*      |
+---------------------+
         ^
         |
  +------+-------+
  |              |
 Dog            Cat

* MakeSound() — 추상 메서드

5. 다형성 실전: 추상화가 실제로 어떻게 동작하는지

이제 이걸 우리 미니 앱에 적용해보자. 동물 리스트가 있다고 치자 — 강아지랑 고양이로 해보자.


List<Animal> animals = new List<Animal>
{
    new Dog { Name = "샤릭" },
    new Cat { Name = "바르식" }
};

foreach (var animal in animals)
{
    animal.MakeSound(); // 다형성 호출: 각자 자기 방식으로 동작함
    animal.Sleep();     // 기본 클래스에 구현된 메서드 호출
}

Cat 클래스도 비슷하게 구현해보자:


public class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine($"{Name}가 말해: 야옹!");
    }
}

실제 프로젝트에서의 실용성

추상 클래스는 대형 앱 아키텍처에서 엄청 많이 써. 예를 들어, 그래픽 에디터에는 보통 Shape라는 추상 클래스가 있고, Draw() 같은 메서드랑 Area 같은 프로퍼티가 있어. 구체적인 도형(사각형, 원 등)은 이 추상 메서드를 자기 공식으로 구현하지. 이런 방식 덕분에 프로그램 로직을 다 새로 안 짜고도 다양한 도형을 만들 수 있어.

6. 추상 클래스의 특징과 제한

인스턴스 생성 불가

컴파일러가 이런 건 절대 못 하게 막아:


Animal a = new Animal(); // 에러!

파생(구체적인) 클래스만 인스턴스화 가능해.

추상 클래스에 들어갈 수 있는 것들:

  • 추상 메서드 선언 (구현 없음)
  • 구현된 메서드 (몸체 있음)
  • 프로퍼티와 필드 (추상 또는 구현됨)
  • 생성자 — 맞아, 추상 클래스도 생성자 가질 수 있는데, 이건 오직 상속받는 클래스에서만 호출할 수 있고, 직접 인스턴스 만들 때는 못 써.

추상 클래스는 상속 지원

추상화 계층을 여러 단계로 쌓을 수도 있어:


public abstract class Creature
{
    public abstract void Live();
}

public abstract class Animal : Creature
{
    public abstract override void Live();
    public abstract void MakeSound();
}

상속 체인에서 모든 추상 메서드를 구현 안 하면, 그 클래스도 추상으로 선언해야 해!

7. 추상 클래스와 생성자

추상 클래스도 생성자를 가질 수 있고, 이건 파생 클래스 생성자에서 호출돼.
중요한 점: 추상 클래스 생성자는 보통 protected로 선언하는데, 이건 오직 상속받는 쪽에서만 쓰라고 명확히 해주는 거야. 직접 인스턴스 만들 때는 못 쓰게 하려고.


public abstract class Animal
{
    public string Name { get; set; }
    
    // 생성자는 protected로 선언
    protected Animal(string name)
    {
        Name = name;
        Console.WriteLine($"{name}라는 동물 생성됨");
    }
    
    public abstract void MakeSound();
}

public class Dog : Animal
{
    public Dog(string name) : base(name)
    {
        // base(name)으로 Animal 생성자 호출
        Console.WriteLine("강아지 생성됨");
    }

    public override void MakeSound()
    {
        Console.WriteLine($"{Name}가 말해: 멍멍!");
    }
}

protected 생성자를 쓸까?

  • 의도 명확히: protected는 이 생성자가 상속받는 쪽만 쓰라는 걸 확실히 보여줌
  • 실수 방지: 컴파일러가 어차피 new로 인스턴스 못 만들게 막지만, protected 생성자는 API 레벨에서 의도를 더 명확히 해줘서 오해의 여지도 없앰
  • 좋은 습관: 캡슐화 원칙, 명확한 API 설계에 부합

추상 클래스에서 public 생성자도 쓸 수는 있지만, protected가 더 의도에 맞아.

8. 추상 메서드와 virtual 메서드 비교

추상 메서드는 사실 virtual 메서드의 한 종류야(항상 virtual처럼 동작하지만, 그 단어는 안 붙임). 근데 추상 메서드는 구현을 쓸 수 없어. 만약 어떤 메서드를 자식이 선택적으로 오버라이드하게 하고 싶으면 virtual을 써. 무조건 오버라이드해야 한다면 abstract만 써야 해.


public abstract class Worker
{
    // 무조건 구현해야 함
    public abstract void Work();

    // 오버라이드할 수도 있고, 안 해도 됨
    public virtual void Rest()
    {
        Console.WriteLine("기본 휴식!");
    }
}
추상 메서드 virtual 메서드
기본 클래스에 구현 있음? 아니, 시그니처만 있음(몸체 없음) 응, 기본 구현 있음
어디서 선언 가능? 추상 클래스에서만 sealed 아닌 모든 클래스에서, 추상 클래스 포함
자식이 오버라이드 필수? 첫 번째 추상 아님 자식에서 무조건 해야 함 필수 아님. 오버라이드해도 되고, 기본 구현 써도 됨
바로 호출 가능? 아니(몸체 없음) 응(기본 버전 실행됨)
주요 목적 자식이 자기만의 구현을 꼭 하게 강제. "계약" 정의 필요하면 동작 바꾸라고 자식한테 기회 줌

9. 내 프로젝트에서 추상 클래스 활용

우리 예제 앱을 더 발전시켜보자. 이번엔 사용자 입력을 처리하는 추상 클래스를 만들어보자(예: 여러 타입의 폼).


public abstract class InputForm
{
    public string Title { get; set; }

    // 입력 처리 프로세스 구현할 추상 메서드
    public abstract void ShowAndHandleInput();
}

public class LoginForm : InputForm
{
    public override void ShowAndHandleInput()
    {
        Console.WriteLine($"=== {Title} ===");
        Console.Write("로그인 입력: ");
        string login = Console.ReadLine();
        Console.Write("비밀번호 입력: ");
        string password = Console.ReadLine();
        Console.WriteLine("인증 완료!");
    }
}

public class RegistrationForm : InputForm
{
    public override void ShowAndHandleInput()
    {
        Console.WriteLine($"=== {Title} ===");
        Console.Write("로그인 만들기: ");
        string login = Console.ReadLine();
        Console.Write("비밀번호 만들기: ");
        string password = Console.ReadLine();
        Console.WriteLine("회원가입 완료!");
    }
}

사용 예시:


InputForm[] forms = new InputForm[]
{
    new LoginForm { Title = "로그인" },
    new RegistrationForm { Title = "회원가입" }
};

foreach (var form in forms)
{
    form.ShowAndHandleInput();
}

이런 방식은 프로그램이 커질수록 점점 더 강력해져. 추상 클래스는 확장성 좋은 시스템을 만들 때 완전 좋은 기반이야. DRY, SOLID(중복 피하기, 모듈화, 확장성) 원칙 지키는 데도 딱이야.

10. 추상 클래스/메서드 쓸 때 흔한 실수

실수 1: 추상 메서드 구현을 깜빡함.
파생 클래스가 모든 추상 메서드/프로퍼티를 구현 안 하면 컴파일러가 에러 내. 특히 남이 짠 코드나 계층 구조가 복잡할 때 자주 나오는 실수야. 중요한 거 빠뜨린 거 없는지 꼭 확인해.

실수 2: abstractstatic을 동시에 쓰려고 함.
이건 안 돼. 추상 메서드는 인스턴스에서 구현해야 하고, static은 인스턴스 필요 없음. 둘은 완전 반대라서 컴파일러가 절대 허락 안 해.

실수 3: "귀찮아서" 추상 멤버 구현을 다 안 함.
모든 추상 메서드 구현하기 싫으면, 그 클래스도 abstract로 만들어야 해. 이건 에러는 아니지만, 구조가 꼬였다는 신호일 수도 있어. 중간 추상 클래스를 만들어서 일부 구현을 미리 해두면 자식 클래스가 편해질 수도 있어.

2
과제
C# SELF, 레벨 22, 레슨 1
잠금
추상 클래스와 파생 클래스 만들기
추상 클래스와 파생 클래스 만들기
2
과제
C# SELF, 레벨 22, 레슨 1
잠금
추상 클래스와 다형성
추상 클래스와 다형성
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION