프로그래밍

객체지향에서 클래스(Class)와 인터페이스(Interface)의 차이점과 사용방법을 알아보자.

maxboy 2025. 4. 13. 13:37

  "클래스(class)"와 "인터페이스(interface)"는 객체지향 프로그래밍(OOP)에서 핵심적인 개념이다. 각자의 역할이 분명하며, 특히 다형성과 구조적인 설계에 중요한 역할을 한다. 아래에 차이점과 각각의 역할을 명확하게 정리해 본다.

 

1. 클래스

클래스는 객체의 설계도라 할수 있다. 객체(인스턴스)는 클래스 기반으로 만들어지며, 속성(필드)과 동작(메서드)을 가지고 있다. 아래 예시코드를 보도록 하자.

public class Animal
{
    public string Name;

    public void Speak()
    {
        Console.WriteLine("Some sound");
    }
}

 

Animal 클래스는 이름 속성과 말하는 메서드를 가지고 있다. 클래스는 객체화(인스턴스화)해야 실제로 사용가능 하기 때문에 아래와 같이 객체화를 하고 Name속성과 메서드를 사용 할 수 있다.

Animal dog = new Animal();
dog.Name = "Buddy";
dog.Speak();

 

2. 인터페이스

인터페이스는 행동의 약속(Contract)만 정의한다. 다시말해서 메서드만 정의한다. 구현은 하지 않으며, 인터페이스를 상속하는 하위 클래스가 반드시 메서드를 구현해야하는 추상 메서드 방식을 가지고 있다.

public interface IDriveable
{
    void Drive();
}

 

이 인터페이스를 상속하는 클래스는 반드시 Drive() 메서드를 구현해야 한다.

public class Car : IDriveable
{
    public void Drive()
    {
        Console.WriteLine("The car is driving");
    }
}

 

클래스와 인터페이스의 특징을 표로 정리하면 아래와 같다.

항목 클래스(Class) 인터페이스(Interface)
목적 객체 설계도 행동 명세(계약)
구성요소 필드(속성) + 메서드(구현 포함) 메서드 시그니처만 정의
상속 단일 상속만 가능 다중 구현 가능
인스턴스 생성 가능 불가능
예시 new car() IDriveable은 직접 생성 불가

 

3. 언제 클래스와 인터페이스를 사용할까?

정리를 하자면 클래스를 사용하는 목적은 객체의 상태와 동작을 함께 정의하고 실제 구현까지 포함하는 것이라면, 인터페이스는 구현은 숨기고 약속(행위)만 정의하고자 할때, 여러 객체를 같은 방식으로 제어하고 싶을 때  사용한다라고 정리를 할수 있다.


4. 예시로 보는 클래스와 인터페이스 (포켓몬 게임)

우리가 흔히 접하는 게임에는 다양한 능력들이 존재한다. 이 능력들을 상황에 따라 쉽게 교체하거나 적용하기 위해서 사용하는 것이 인터페이스이다. 인터페이스를 통해 서로 다른 능력들을 같은 방식으로 다룰 수 있어, 유연하고 확장 가능한 구조로 관리 할 수 가 있다. 아래는 포켓몬 게임에서 캐릭터 마다 여러가지 능력들이 있지만, 때에 따라 능력을 유연하게 바꿔줄수 있는 예시 코드이다.

 

즉, 푸린은 원래는 노래하기와 회피를 사용하지만, 게임 도중 백만볼드와 방어로 능력을 변경해 상황에 따라 유연하게 능력(행동)을 바꿔주는 구조를 인터페이스로 구현한 예시 코드이다.

using System;

public class Program
{
    public static void Main(string[] args)
    {
        Pokemon pikachu = new Pikachu();
        pikachu.Introduce();

        Pokemon pairi = new Pairi();
        pairi.Introduce();

        Pokemon purin = new Purin();
        purin.Introduce();

        purin.SetAttack(new MillionVolt());
        purin.SetPassive(new Defensibility());

        purin.Introduce();
    }
}

 

위 코드는 포켓몬 객체화했으면 푸린의 경우 인터페이스를 이용하여 다른 능력(백만볼트 및 방어) 으로 교체를 하는 코드이다.

public abstract class Pokemon
{
    public IAttack attack;
    public IPassive passive;

    public Pokemon() { }

    public void Introduce()
    {
        Name();
        attack.Motion();
        passive.Detail();
    }

    public abstract void Name();

    public void SetAttack(IAttack attack)
    {
        this.attack = attack;
    }

    public void SetPassive(IPassive passive)
    {
        this.passive = passive;
    }
}

 

포켓몬 클래스는 추상 클래스로 구현 했으며, 상속받는 하위 클래스에서 정의 하도록 하였다. 포켓몬 클래스에는 두개의 메서드가 존재하는데 인터페이스 타입을 파라미터로 가지고 있다.

public class Purin : Pokemon
{
    public Purin()
    {
        attack = new Sing();
        passive = new Avoidability();
    }

    public override void Name()
    {
        Console.WriteLine("이름: 푸린, 속성: 노멀");
    }
}
public class Pikachu : Pokemon
{
    public Pikachu()
    {
        attack = new MillionVolt();
        passive = new Speedability();
    }

    public override void Name()
    {
        Console.WriteLine("이름: 피카츄, 속성: 번개");
    }
}
public class Pairi : Pokemon
{
    public Pairi()
    {
        attack = new Flame();
        passive = new Defensibility();
    }

    public override void Name()
    {
        Console.WriteLine("이름: 파이리, 속성: 불");
    }
}

 

총 3종류의 포켓몬을 정의했다.

public interface IAttack
{
    void Motion();
}
public class Sing : IAttack
{
    public void Motion()
    {
        Console.WriteLine("공격 스킬 - 노래하기: 노래를 불러 상대를 잠재움");
    }
}
public class Flame : IAttack
{
    public void Motion()
    {
        Console.WriteLine("공격 스킬 - 불꽃: 뜨거운 불꽃을 쏘아 공격");
    }
}
public class MillionVolt : IAttack
{
    public void Motion()
    {
        Console.WriteLine("공격 스킬 - 백만 볼트: 백만 볼트의 강력한 전기 공격");
    }
}

 

공격 능력에 대한 인터페이스를 구현하고 상속받는 하위 클래스에서 메서드를 구현했다.

public interface IPassive
{
    void Detail();
}
public class Defensibility : IPassive
{
    public void Detail()
    {
        Console.WriteLine("패시브 스킬 - 방어: 받는 피해 40% 감소시킴\n");
    }
}
public class Speedability : IPassive
{
    public void Detail()
    {
        Console.WriteLine("패시브 스킬 - 스피드: 한 번에 두 번 공격\n");
    }
}
public class Avoidability : IPassive
{
    public void Detail()
    {
        Console.WriteLine("패시브 스킬 - 회피: 30% 확률로 공격 회피\n");
    }
}

 

패시브 능력에 대한 인터페이스를 구현하고 상속받는 하위 클래스에서 메서드를 구현했다.

 

다시한번 설명하자면...

public void SetAttack(IAttack attack)
    {
        this.attack = attack;
    }

public void SetPassive(IPassive passive)
    {
        this.passive = passive;
    }

 

SetAttack(IAttack attack)의 파라미터는 클래스가 아니라 인터페이스 타입이다. 그렇기 때문에 이 메서드는 반드시 인터페이스를 구현한 클래스를 받을 수 있다. 

Pokemon purin = new Purin();

purin.SetAttack(new MillionVolt());
purin.SetPassive(new Defensibility());

 

위 코드 처럼 인터페이스를 구현한 클래스만 파라미터로 사용할 수 있다.

 

5. 마지막으로

인터페이스를 사용하는 핵심적인 이유는 구현체(Sing, millionvolt 등)을 몰라도 그 인터페이스만 알고 있으면 유연하게 사용할 수 있다라는 것이다. 즉, 필효한 기술들이 추가 된다면, 인터페이스에 추가된 클래스만 넣어주면 바로 교체를 할 수 있다. 이러한 구조는 디자인 패턴중 전략 패턴(Strategy Pattern)을 정확하게 구현한 예시 코드 이기도 하다. 전략 패턴이란 행위를 인터페이스로 분리하고, 런타임에 객체의 행동을 바꿀 수 있게 만드는 패턴을 의미한다. 클래스의 행위를 바꾸고 싶을 때 상속 대신 구성(Composition)을 사용하는 방식이다.