본문 바로가기
프로그래밍/C#

C# 인터페이스와 추상클래스의 차이

by argentdarae 2025. 3. 3.
반응형

C#에서 인터페이스와 추상 클래스의 차이는 다음과 같다

 

개념적 차이

인터페이스

인터페이스는 주로 이 객체는 어떤 동작을 할 수 있다 라는 동작(행동)의 집합을 정의한다

 

예를 들어 '저장할 수 있다', '로그를 남길 수 있다' 같은 식으로 해당 인터페이스를 상속받은 클래스가 갖춰야 할 기능의 틀만 제공하는 것이다

using System;

// 1. 인터페이스 정의
public interface ILogger
{
    void Log(string message);
}

// 2. 인터페이스 구현 클래스
public class FileLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine($"파일 로그 기록: {message}");
    }
}

public class DatabaseLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine($"데이터베이스 로그 기록: {message}");
    }
}

// 3. LoggerManager 클래스
public class LoggerManager
{
    public void WriteLog(ILogger logger, string message)
    {
        logger.Log(message);
    }
}

// 4. 실제 사용하는 코드
class Program
{
    static void Main(string[] args)
    {
        var loggerManager = new LoggerManager();

        ILogger fileLogger = new FileLogger();
        ILogger dbLogger = new DatabaseLogger();

        loggerManager.WriteLog(fileLogger, "파일 로그 테스트 메시지");
        loggerManager.WriteLog(dbLogger, "DB 로그 테스트 메시지");
    }
}


================ 실행 결과 ================
파일 로그 기록: 파일 로그 테스트 메시지
데이터베이스 로그 기록: DB 로그 테스트 메시지

 

위 코드에서 ILogger는 Log라는 기능을 보장하는 계약 역할을 한다

FileLogger, DatabaseLogger는 이 계약을 각각 다른 방식으로 구현해, 파일과 데이터베이스에 로그를 남긴다

LoggerManager는 ILogger라는 추상적인 규약만 바라보고 동작하며, 구체적인 로그 방식은 주입받는 객체가 결정한다

 

이렇게 하면 새로운 로그 방식이 추가되어도 LoggerManager는 수정 없이 그대로 사용할 수 있는 것이다

 

추상 클래스

반면 추상 클래스는 이 객체는 무엇이다 라는 정체성을 부여하는 데 더 가깝다

 

예를 들어 '동물', '무기' 처럼 공통적인 속성이나 기본 동작은 동일하고 구체적으로는 자식 클래스에 구현을 위임하는 것이다

using System;

// 1. 추상 클래스 정의
public abstract class Animal
{
    // 모든 동물이 가지는 공통 속성
    public string Name { get; set; }

    // 모든 동물이 공유하는 공통 동작
    public void Breathe()
    {
        Console.WriteLine($"{Name}이(가) 숨을 쉽니다.");
    }

    // 동물마다 다른 소리를 내도록 강제
    public abstract void MakeSound();
}

// 2. 추상 클래스 구현 클래스

// 개 클래스 - 공통 동작 + 개 전용 행동
public class Dog : Animal
{
    public Dog(string name)
    {
        Name = name;
    }

    public override void MakeSound()
    {
        Console.WriteLine($"{Name}이(가) 멍멍 하고 짖습니다.");
    }

    // 개만 할 수 있는 행동
    public void WagTail()
    {
        Console.WriteLine($"{Name}이(가) 꼬리를 흔듭니다.");
    }
}

// 고양이 클래스 - 공통 동작 + 고양이 전용 행동
public class Cat : Animal
{
    public Cat(string name)
    {
        Name = name;
    }

    public override void MakeSound()
    {
        Console.WriteLine($"{Name}이(가) 야옹 하고 웁니다.");
    }

    // 고양이만 할 수 있는 행동
    public void ClimbTree()
    {
        Console.WriteLine($"{Name}이(가) 나무에 올라갑니다.");
    }
}

// 3. AnimalManager 클래스
public class AnimalManager
{
    public void LetAnimalAct(Animal animal)
    {
        animal.Breathe();    // 공통 동작 수행
        animal.MakeSound();  // 각 동물별 고유 동작 수행

        // 각 동물만의 특별한 행동은 캐스팅해서 호출해야 가능
        if (animal is Dog dog)
        {
            dog.WagTail();
        }
        else if (animal is Cat cat)
        {
            cat.ClimbTree();
        }
    }
}

// 4. 실제 사용하는 코드
class Program
{
    static void Main(string[] args)
    {
        var animalManager = new AnimalManager();

        Animal dog = new Dog("바둑이");
        Animal cat = new Cat("나비");

        animalManager.LetAnimalAct(dog);
        animalManager.LetAnimalAct(cat);
    }
}


================ 실행 결과 ================
바둑이이(가) 숨을 쉽니다.
바둑이이(가) 멍멍 하고 짖습니다.
바둑이이(가) 꼬리를 흔듭니다.
나비이(가) 숨을 쉽니다.
나비이(가) 야옹 하고 웁니다.
나비이(가) 나무에 올라갑니다.

 

위 코드에서 Animal은 모든 동물이 공유할 수 있는 속성(Name)과 기본 동작(Breathe)을 제공한다. 반면, MakeSound는 동물마다 달라야 하므로, 각 동물 클래스가 구체적으로 구현하게 강제한다

 

개는 WagTail(), 고양이는 ClimbTree()처럼 각 동물만의 고유한 행동도 추가할 수 있다

 

AnimalManager는 Animal 타입만 바라보면서, 구체적인 동물마다 다른 동작은 필요 시 타입 캐스팅을 통해 호출한다

 

예시를 통해 추상 클래스 사용시 이점인 공통 로직 재사용과 각 객체의 개별 특성 보장을 달성하는 것을 볼 수 있다

 

 

기능적 차이

상속

추상 클래스는 단일 상속만 가능하다. 즉, 한 클래스는 오직 하나의 추상 클래스만 상속받을 수 있다

 

인터페이스는 다중 구현이 가능하다. 즉, 한 클래스가 여러 인터페이스를 동시에 구현할 수 있다

 

멤버 구성 차이

추상 클래스는 필드, 속성, 메서드 등 구체적인 구현을 가진 멤버도 포함할 수 있고, 접근 제어자도 자유롭게 설정할 수 있다

 

인터페이스는 메서드나 속성의 시그니처만 정의하고 실제 구현은 갖지 않는다. 또한 필드를 가질 수 없고 상태를 저장할 수도 없으며 멤버들은 기본적으로 public이다

 

하지만 C# 8 이후부터 인터페이스도 구현은 가능해져서 필요하다면 override 해서 사용할 수 있다

public interface ILogger
{
    void Log(string message);

    // C# 8 이후, 디폴트 구현 제공 가능
    void LogWithTimestamp(string message)
    {
        Console.WriteLine($"[{DateTime.Now}] {message}");
    }
}

 

 

 

 

결론

인터페이스는 객체가 무엇을 해야하는지 요구사항을 정의하고, 추상 클래스는 무엇인지 정체성을 부여하며 어떤 일을 해야하는 지 정의한다

 

인터페이스는 다중 상속이 가능하고, 추상 클래스는 단일 상속만이 가능하다

 

인터페이스는 메서드나 속성의 시그니처만 정의하고 멤버들의 접근 한정자가 public이다. C# 8 이후부터 인터페이스도 구현은 가능하다. 하지만 개인적으로 설계 원칙상 인터페이스의 구현은 남용하지 않는 게 좋다고 생각한다

추상 클래스의 경우 구제적인 구현을 가진 멤버도 포함할 수 있고, 접근 제어자도 자유롭게 설정할 수 있다

 

 

'프로그래밍 > C#' 카테고리의 다른 글

C#의 프로퍼티(Property)란?  (0) 2025.03.10
C# 얕은 복사와 깊은 복사의 차이  (0) 2025.03.04
C# 값 타입과 참조 타입의 차이  (0) 2025.03.04
C# ContainsKey vs TryGetValue  (0) 2024.09.17