[로봇활용_10주차] C# 인터페이스(Interface)

최윤호·2025년 10월 11일
post-thumbnail

인터페이스: USB처럼 사용

여러분은 컴퓨터에 마우스, 키보드, 외장하드 등 다양한 장치를 연결할 때
어떤 포트를 사용하시나요? 아마 대부분 'USB 포트'를 떠올리실 겁니다.
기능이 전혀 달라도, 'USB 규격'만 맞으면 컴퓨터에 연결해서 사용할 수 있죠.
C# 프로그래밍 세계에도 이 USB 포트와 같은 역할을 하는 멋진 기능이 있습니다.
바로 인터페이스(Interface)입니다. 인터페이스는 서로 다른 클래스들이
함께 작동할 수 있도록 정해놓은 '규격' 또는 '약속'입니다.

1)인터페이스(Interface)란?

인터페이스는 메서드, 속성, 이벤트의 '설계도'입니다. 중요한 것은,
이 설계도에는 '무엇을 할 수 있는지(What)'에 대한 정의만 있을 뿐,
'어떻게 하는지(How)'에 대한 실제 구현 코드는 전혀 없다는 점입니다.

[비유]
USB(Interface)'데이터 전송', '전력 공급'이라는 기능적인 '약속'만 정의합니다.
마우스(Class)'클릭 정보'를 전송하고, 키보드(Class)'키 입력 정보'
전송하는 등 각자의 방식으로 기능을 '구현'하는 것과 같습니다.

2)인터페이스의 주요 특징

1. interface키워드를 사용하여 선언합니다.
2. 인터페이스 이름은 대문자 I로 시작하는 관례가 있습니다.
(예: ILogger, IComparable)
3. 내부의 모든 멤버는 구현부가 없는 '껍데기'만 가집니다.
(C# 8.0부터는 기본 구현도 가능하지만, 전통적인 개념은 이렇습니다.)
4. 인터페이스 자체는 객체로 만들 수 없습니다.
(예: new ILogger()불가능! )
5. 인터페이스는 여러 개를 동시에 구현(implement)할 수 있습니다.
(C#은 클래스의 다중 상속을 지원하지 않습니다.)

3)인터페이스 사용법

코드를 통해 다양한 방식의 로그(Log)를 남기는 기능을
인터페이스로 정의하고, 여러 클래스에서 구현해 보겠습니다.

1단계: 인터페이스 정의

로거(Logger)라면 "Log메시지를 남기는 기능이 있어야 한다"라는
약속을 정의하는 ILogger인터페이스로 만들어 봅시다.

[코드]

// "이 인터페이스를 구현하는 클래스는 반드시 Log 메서드를 가져야 한다"는 약속!
public interface ILogger
{
    void Log(string message);
}

2단계: 인터페이스 구현

이제 ILogger라는 약속을 지키는 두 개의 클래스를 직접 만들어 보겠습니다.
클래스 이름 뒤에 콜론(:)을 붙이고 구현할 인터페이스 이름을 적어주면 됩니다.

[코드]

// 1. 로그를 파일에 기록하는 클래스
public class FileLogger : ILogger
{
    // ILogger 인터페이스와의 약속을 지키기 위해 Log 메서드를 구현
    public void Log(string message)
    {
        // 실제 구현은 클래스 마음대로!
        File.WriteAllText("log.txt", message);
        Console.WriteLine($"[FileLogger] 파일에 로그 기록: {message}");
    }
}

// 2. 로그를 콘솔 창에 출력하는 클래스
public class ConsoleLogger : ILogger
{
    // ILogger 인터페이스와의 약속을 지키기 위해 Log 메서드를 구현
    public void Log(string message)
    {
        Console.WriteLine($"[ConsoleLogger] 콘솔에 로그 출력: {message}");
    }
}

3단계: 프로그램 실행

사용자 정보를 관리하는 UserManager클래스와 메인 로직을 만들고 실행합니다.

[전체 코드]

using System;
using System.IO;

// "이 인터페이스를 구현하는 클래스는 반드시 Log 메서드를 가져야 한다"는 약속!
public interface ILogger
{
    void Log(string message);
}

// 1. 로그를 파일에 기록하는 클래스
public class FileLogger : ILogger
{
    // ILogger 인터페이스와의 약속을 지키기 위해 Log 메서드를 구현
    public void Log(string message)
    {
        // 실제 구현은 클래스 마음대로!
        File.WriteAllText("log.txt", message);
        Console.WriteLine($"[파일에 로그 기록] {message}");
    }
}

// 2. 로그를 콘솔 창에 출력하는 클래스
public class ConsoleLogger : ILogger
{
    // ILogger 인터페이스와의 약속을 지키기 위해 Log 메서드를 구현
    public void Log(string message)
    {
        Console.WriteLine($"[콘솔에 로그 출력] {message}");
    }
}

public class UserManager
{
    // ILogger 인터페이스 타입을 사용 -> 느슨한 결합(Loose Coupling)!
    private readonly ILogger _logger;

    // 생성자를 통해 외부에서 어떤 로거를 사용할지 주입받음 (Dependency Injection)
    public UserManager(ILogger logger)
    {
        _logger = logger;
    }

    public void CreateUser(string name)
    {
        // ... 사용자 생성 로직 ...
        // _logger가 FileLogger인지 ConsoleLogger인지 전혀 신경 쓰지 않음!
        _logger.Log($"{name} 사용자가 생성되었습니다.");
    }
}

class Program
{
    static void Main()
    {
        // 파일에 로그를 남기고 싶을 때
        ILogger fileLogger = new FileLogger();
        UserManager userManager1 = new UserManager(fileLogger);
        userManager1.CreateUser("김철수");

        // 콘솔에 로그를 남기고 싶을 때
        ILogger consoleLogger = new ConsoleLogger();
        UserManager userManager2 = new UserManager(consoleLogger);
        userManager2.CreateUser("이영희");
    }
}

[실행 결과: 콘솔]

[파일에 로그 기록] 김철수 사용자가 생성되었습니다.
[콘솔에 로그 출력] 이영희 사용자가 생성되었습니다.

[실행 결과: log.txt]

김철수 사용자가 생성되었습니다.

외부에서 어떤 객체를 넣어주느냐에 따라 로깅 방식이 자유자재로 바뀝니다.
인터페이스는 코드의 의존성을 낮추고 유연성을 극대화하는 강력한 도구입니다.

4)인터페이스를 사용 안 하면?

public class UserManager
{
    // FileLogger 클래스 타입을 직접 사용 -> 강한 결합(Tight Coupling)!
    private readonly FileLogger _logger = new FileLogger();

    public void CreateUser(string name)
    {
        // ... 사용자 생성 로직 ...
        _logger.Log($"{name} 사용자가 생성되었습니다.");
    }
}

UserManager가 특정 FileLogger클래스에 직접 의존하고 있습니다.

문제점: 만약 로깅 정책이 바뀌어서 파일이 아닌 콘솔에 로그를 남겨야 한다면?
UserManager클래스 코드를 FileLogger에서 ConsoleLogger로 수정해야 합니다.
기능 하나 바꾸자고 다른 클래스 코드를 건드려야 하는 끔찍한 상황이죠.

느슨한 결합과 강한 결합?

클래스는 결합 방식에 따라 느슨한 결합과 강한 결합으로 나뉩니다.

구분느슨한 결합 (Loose Coupling)강한 결합 (Tight Coupling)
의존 대상추상화 (인터페이스, 추상 클래스)구체적인 클래스 (Concrete Class)
핵심 특징각 구성 요소가 독립적이며, 교체가 용이함한 구성 요소의 변경이 다른 구성 요소에 직접 영향을 줌
주요 장점높은 유연성, 확장성, 테스트 용이성초기 구현이 간단하고 코드 이해가 직관적일 수 있음
주요 단점초기 설계의 복잡도가 약간 증가할 수 있음낮은 재사용성, 유지보수의 어려움, 테스트가 힘듦

5)다중 상속 흉내

C#은 클래스는 오직 하나의 부모 클래스로부터만 상속받을 수 있죠.
하지만 인터페이스는 여러 개를 동시에 구현할 수 있습니다.

[코드]

using System;

// "공격 가능한" 약속(Interface)
public interface IAttackable
{
    // 속성(Property): 공격력을 알려줘야 해!
    int AttackDamage { get; }

    // 메서드(Method): 공격하는 기능이 있어야 해!
    void Attack(string target);
}

// '움직일 수 있는' 약속(Interface)
public interface IMovable
{
    // 메서드(Method): 움직이는 기능이 있어야 해!
    void Move(string target);
}

// 전사는 공격도 가능하고, 움직일 수도 있다! (여러 개의 인터페이스를 동시에 구현)
public class Warrior : IAttackable, IMovable
{
    // 약속 1: AttackDamage 속성을 구현해야 한다.
    public int AttackDamage { get; private set; } = 15;

    public Warrior()
    {
        // 생성자에서 초기화
    }

    // 약속 2: Attack 메서드를 구현해야 한다.
    public void Attack(string target)
    {
        Console.WriteLine($"{target}에게 검으로 {AttackDamage}의 피해를 입혔습니다!");
    }

    // 약속 3: Move 메서드를 구현해야 한다.
    public void Move(string target)
    {
        Console.WriteLine($"전사가 {target} 앞으로 걸어갑니다.");
    }
}

// 마법사는 공격도 가능하고, 움직일 수도 있다! (여러 개의 인터페이스를 동시에 구현)
public class Mage : IAttackable, IMovable
{
    // 약속 1: AttackDamage 속성을 구현해야 한다.
    public int AttackDamage { get; private set; } = 25;
    private int _mana = 100;

    public Mage()
    {
        // 생성자에서 초기화
    }

    // 약속 2: Attack 메서드를 구현해야 한다.
    public void Attack(string target)
    {
        if (_mana >= 10)
        {
            _mana -= 10;
            Console.WriteLine($"{target}에게 스파크로 {AttackDamage}의 피해를 입혔습니다!");
        }
        else
        {
            Console.WriteLine("마나가 부족하여 공격할 수 없습니다.");
        }
    }

    // 약속 3: Move 메서드를 구현해야 한다.
    public void Move(string target)
    {
        Console.WriteLine($"마법사가 {target} 앞으로 순간이동합니다.");
    }
}

// 인터페이스를 사용하면 '약속(인터페이스)'에 의존하는 코드를 작성할 수 있습니다.
public class Monster
{
    public string Name { get; set; } = "고블린";

    // 이 메서드는 '전사'나 '마법사'가 아닌 '공격 가능한(IAttackable)' 모든 것을 받습니다.
    public void GetAttackedBy(IAttackable attacker)
    {
        attacker.Attack(this.Name);
    }

    // 이 메서드는 '전사'나 '마법사'가 아닌 '움직일 수 있는(IMovable)' 모든 것을 받습니다.
    public void Chase(IMovable mover)
    {
        mover.Move(this.Name);
    }
}

class Program
{
    static void Main()
    {
        var warrior = new Warrior();
        var mage = new Mage();
        var monster = new Monster();

        // 전사 객체를 전달
        monster.Chase(warrior); // 전사가 몬스터를 향해 걸어감
        monster.GetAttackedBy(warrior); // 전사가 몬스터를 공격
        // 마법사 객체를 전달
        monster.Chase(mage); // 마법사가 몬스터를 향해 순간이동
        monster.GetAttackedBy(mage); // 마법사가 몬스터를 공격
    }
}

[실행 결과]

전사가 고블린 앞으로 걸어갑니다.
고블린에게 검으로 15의 피해를 입혔습니다!
마법사가 고블린 앞으로 순간이동합니다.
고블린에게 스파크로 25의 피해를 입혔습니다!

인터페이스를 활용하면 다중 상속의 효과를 낼 수 있습니다.

6)개념 정리

인터페이스는 처음에는 "왜 이렇게 번거롭게 하지?"라고 생각될 수 있지만,
규모가 커지고 유지보수가 중요해질수록 그 진가를 발휘하는 핵심적인 개념입니다.

개념설명
인터페이스 정의클래스가 반드시 구현해야 할 멤버들의 계약서
인터페이스 구현인터페이스의 약속을 지켜 클래스에서 실제 기능을 만드는 것
느슨한 결합구체적인 클래스가 아닌 추상화에 의존하여 유연성과 재사용성을 높이는 설계
강한 결합구체적인 클래스에 직접 의존하여 변경이 어렵고 종속성이 높은 설계
profile
🚀 미래의 엔지니어를 꿈꾸는 훈련생의 기록 📝

0개의 댓글