---
title: "무엇인가면 추상 클래스, 무엇을 할 수 있으면 인터페이스"
description: "하나는 공통 코드를 물려주면서 빈칸을 남기고, 하나는 지켜야 할 목록만 적습니다. 문장으로 읽어 보면 답이 나옵니다."
author: "유니얼"
pubDate: 2026-09-09
category: "programming/csharp/basics"
tags: ["C#", "기초"]
canonical: https://blog.unialgames.com/posts/csharp-abstract-interface/
---
앞 글의 `Character` 에 이상한 데가 있었습니다.

```csharp
class Character
{
    public virtual void Attack()
    {
        Console.WriteLine("공격");
    }
}
```

`Character` 의 기본 공격이 "공격" 을 출력합니다. 이게 뭘까요.
전사도 마법사도 아닌 그냥 캐릭터는 어떻게 공격하는지, 답이 없는 질문입니다.

두 가지 문제가 있습니다.

```csharp
Character c = new Character("아무개");    // 이게 만들어집니다
```

**직업 없는 캐릭터가 만들어집니다.** 그리고 새 직업을 만들면서
`Attack` 갈아 끼우는 것을 잊으면 조용히 "공격" 이 출력됩니다. 아무도 안 막아 줍니다.

> **채워야 하는 자리는 비워 두고, 안 채우면 컴파일이 안 되게 하면 됩니다.**

## 추상 클래스는 반쯤 만든 틀이다

```csharp
abstract class Character
{
    protected string name;
    protected int hp;

    public void TakeDamage(int amount) { ... }    // 공통, 구현해 둔다
    public bool IsAlive() { ... }

    public abstract void Attack();                // 자식마다 다르다, 비워 둔다
}
```

두 가지가 달라졌습니다.

`class` 앞에 `abstract` 를 붙였습니다. 이러면 **이 클래스로는 객체를 만들 수 없습니다.**

```csharp
Character c = new Character();      // 컴파일 에러
```

붕어빵 틀인데 반만 만들어진 틀입니다. 그대로는 못 찍습니다.

`Attack` 에도 `abstract` 를 붙이고 **중괄호 없이 세미콜론으로 끝냈습니다.**
"이런 메서드가 있어야 한다" 만 적고 내용은 안 적었습니다.

물려받은 자식은 반드시 채워야 합니다.

```csharp
class Warrior : Character
{
    public override void Attack()
    {
        Console.WriteLine("칼로 벤다");
    }
}
```

안 채우면 컴파일 에러입니다. 잊을 수가 없습니다.
`virtual` 은 "갈아 끼워도 된다" 였고, `abstract` 는 **"반드시 채워라"** 입니다.

| | `virtual` | `abstract` |
| --- | --- | --- |
| 부모에 내용이 | 있다 | 없다 |
| 자식이 | 갈아 끼워도 되고 안 해도 된다 | 반드시 채워야 한다 |
| 그 클래스로 객체를 | 만들 수 있다 | 만들 수 없다 |

## 인터페이스는 약속 목록이다

추상 클래스는 구현을 물려줍니다. 그런데 물려줄 것이 없고
**할 수 있어야 하는 것만** 정하고 싶을 때가 있습니다.

새가 날고 비행기도 납니다. 둘은 아무 관계가 없습니다.
공통 부모를 만들면 `FlyingThing` 같은 이상한 것이 생깁니다.

```csharp
interface IFlyable
{
    void Fly();
}

class Bird : Animal, IFlyable
{
    public void Fly() { Console.WriteLine("날개짓"); }
}

class Airplane : Vehicle, IFlyable
{
    public void Fly() { Console.WriteLine("엔진 추력"); }
}
```

인터페이스에는 **무엇을 할 수 있어야 하는지만** 적습니다. 어떻게 하는지는 안 적습니다.
필드도 없습니다. 상태를 안 가집니다.

이름 앞에 `I` 를 붙이는 것이 C# 의 관습입니다. `IFlyable`, `IComparable` 처럼요.

**부모는 하나뿐이지만 인터페이스는 여러 개 됩니다.**

```csharp
class Duck : Animal, IFlyable, ISwimmable
{
    public void Fly() { ... }
    public void Swim() { ... }
}
```

앞에서 부모를 하나로 제한한 이유가 물려받는 **내용**이 겹치기 때문이라고 했습니다.
인터페이스는 물려줄 내용이 없으니 겹칠 것도 없습니다. 그래서 여러 개가 됩니다.

## 인터페이스도 타입이다

인터페이스로도 여러 종류를 한 이름으로 부를 수 있습니다.

```csharp
List<IFlyable> things = new List<IFlyable>();
things.Add(new Bird());
things.Add(new Airplane());

for (int i = 0; i < things.Count; i++)
{
    things[i].Fly();
}
```

새와 비행기는 부모가 완전히 다른데 한 목록에 담겼습니다.
**"날 수 있다" 는 공통점 하나로 묶은 것**입니다.

상속으로는 이게 안 됩니다. 상속은 계보를 따라가야 하는데,
"날 수 있다" 는 계보와 상관없이 여기저기 흩어져 있습니다.

## 무엇으로 고르나

문장으로 읽어 보면 대체로 답이 나옵니다.

- 전사는 캐릭터**이다** → 추상 클래스
- 오리는 날 수 **있다** → 인터페이스

| | 추상 클래스 | 인터페이스 |
| --- | --- | --- |
| 관계 | 무엇인가 (is a) | 무엇을 할 수 있다 (can do) |
| 구현을 | 물려줄 수 있다 | 못 한다 |
| 필드를 | 가질 수 있다 | 못 가진다 |
| 몇 개나 | 하나만 | 여러 개 |

**공통 코드를 물려주려면 추상 클래스, 능력만 약속하려면 인터페이스**입니다.

둘을 같이 쓰는 것이 보통입니다.
`Character` 는 추상 클래스로 두고 공통을 물려주면서,
날 수 있는 직업만 `IFlyable` 을 붙이는 식입니다.

```csharp
class Dragoon : Character, IFlyable
{
    public override void Attack() { ... }
    public void Fly() { ... }
}
```

## 우리가 이미 쓰던 것들

`List` 를 만들 때 이렇게 쓸 수 있습니다.

```csharp
IList<int> nums = new List<int>();
```

`List` 는 `IList` 를 구현한 클래스입니다.
`IList` 로 받아 두면 나중에 다른 구현으로 바꿔도 쓰는 쪽이 안 깨집니다.

`foreach` 로 돌 수 있는 것들은 전부 `IEnumerable` 을 구현하고 있습니다.
배열도, `List` 도, `Dictionary` 도요. 그래서 같은 문법으로 돌 수 있습니다.
그 문법은 뒤에서 다룹니다.

**약속을 정해 두면 서로 모르는 것들이 같은 방식으로 다뤄집니다.**
인터페이스가 하는 일이 이것입니다.

## 여기까지 정리하면

객체지향이라고 부르는 것의 네 가지를 다 봤습니다.

| | 무엇 | 어디서 봤나 |
| --- | --- | --- |
| 캡슐화 | 안을 감추고 창구만 연다 | `private` 과 메서드 |
| 상속 | 겹치는 것을 물려받는다 | `Character` 와 자식들 |
| 다형성 | 같은 호출이 다르게 동작한다 | `virtual` 과 `override` |
| 추상화 | 공통점만 남기고 나머지를 지운다 | `abstract` 와 인터페이스 |

네 가지가 따로 있는 것이 아닙니다. **무엇이 같고 무엇이 다른가를 나누는 일**의 다른 이름들입니다.

같은 것은 부모에 두고, 다른 것은 자식에 두고, 밖에 안 보여도 되는 것은 감춥니다.
설계란 이 나누기를 하는 일이고, 문법은 그걸 적는 방법입니다.

처음부터 잘 나눌 수는 없습니다. **고치기 어려워졌다고 느낄 때** 다시 나누면 됩니다.
그때 꺼내 볼 기준이 하나 있다면, 한 클래스가 한 가지 일만 하게 두는 것입니다.
바꿔야 할 이유가 두 개 이상이면 나눌 때가 된 것입니다.

## 정리

- `abstract` 클래스는 그 자체로 객체를 만들 수 없습니다. 반쯤 만든 틀입니다.
- `abstract` 메서드는 내용 없이 선언만 합니다. 자식이 반드시 채워야 합니다.
- `virtual` 은 갈아 끼워도 된다는 허락, `abstract` 는 반드시 채우라는 요구입니다.
- 인터페이스는 무엇을 할 수 있어야 하는지만 적습니다. 구현도 필드도 없습니다.
- 부모는 하나지만 인터페이스는 여러 개 구현할 수 있습니다.
- 인터페이스도 타입입니다. 계보가 다른 것들을 능력 하나로 묶을 수 있습니다.
- "무엇인가" 면 추상 클래스, "무엇을 할 수 있다" 면 인터페이스입니다.
- 캡슐화, 상속, 다형성, 추상화는 전부 무엇이 같고 다른가를 나누는 일입니다.