Search

인터페이스

class
구조
상태
완료
날짜
목차

인터페이스에 대한 개인적인 생각

제 생각에는 인터페이스와 추상클래스는 주로 유지 보수를 위한 구조라고 보입니다.
다른 역할도 있겠지만, 인터페이스를 사용하면 유지 보수가 왜 원활해지는지만 알면, 인터페이스 사용에 큰 문제는 없을 것입니다.
그래서 인터페이스에 대한 설명을 여기서는 가장 중요하다고 생각하는 순서대로 설명하겠습니다.

인터페이스

인터페이스는 클래스가 구현해야 하는 메서드의 시그니처만을 정의하며, 실제 구현 내용은 각 클래스에서 결정합니다.
이 접근 방식은 게임 개발의 복잡성을 관리하고, 유지 보수를 용이하게 하는 데 중요한 역할을 합니다.

인터페이스의 기본 구조

인터페이스는 interface 키워드를 사용해 정의됩니다.
// 인터페이스 사용 예시 public interface IAttackable { public void Attack(); } // 인터페이스를 활용한 클래스 public class Warrior : IAttackable { public void Attack() { // 전사의 공격 로직 구현 } } public class Archer : IAttackable { public void Attack() { // 궁수의 공격 로직 구현 } }
C#
복사
실제 구현은 각 클래스에서 이루어지지만, C# 8.0부터는 기본 메소드를 정의할 수 있습니다.
public interface ILogger { public void Log(string message); // 기본 구현을 가진 메서드 public void LogError(string message) { Log($"Error: {message}"); } } public class ConsoleLogger : ILogger { public void Log(string message) { Console.WriteLine(message); } // LogError 메서드는 기본 구현을 사용하므로, 별도로 구현할 필요가 없습니다. // 다른 곳에서 LogError 메소드를 기본 구현한 부분으로 사용할 수 있다는 의미입니다. }
C#
복사

인터페이스의 다중 상속

게임에서 여러 종류의 캐릭터(예: 플레이어, 적, NPC)가 있고, 이들이 다양한 행위(예: 공격, 이동, 대화)를 수행할 수 있다고 가정해 봅시다.
이 경우, 각 행위를 인터페이스로 정의하고, 각 캐릭터 클래스에서 필요한 인터페이스를 구현함으로써 다중 상속과 유사한 효과를 낼 수 있습니다.
C#에서는 클래스의 다중 상속을 지원하지 않습니다.
public interface IAttackable { void Attack(); } public interface IMovable { void Move(); } public interface ITalkable { void Talk(); } // 플레이어 클래스는 공격, 이동, 대화 모두 가능 public class Player : IAttackable, IMovable, ITalkable { public void Attack() { Console.WriteLine("Player attacks!"); } public void Move() { Console.WriteLine("Player moves!"); } public void Talk() { Console.WriteLine("Player talks!"); } } // 적 클래스는 공격과 이동만 가능 public class Enemy : IAttackable, IMovable { public void Attack() { Console.WriteLine("Enemy attacks!"); } public void Move() { Console.WriteLine("Enemy moves!"); } } // NPC 클래스는 대화만 가능 public class NPC : ITalkable { public void Talk() { Console.WriteLine("NPC talks!"); } }
C#
복사

인터페이스의 기본적인 장점

1.
유연성 : 각 캐릭터 클래스는 필요한 인터페이스를 선택적으로 구현할 수 있어, 다양한 행위의 조합을 유연하게 표현할 수 있습니다.
2.
확장성 : 새로운 행위(인터페이스)를 추가하거나 기존 행위를 수정할 때, 관련된 클래스만 변경하면 되므로 시스템 전체의 변경을 최소화할 수 있습니다.
3.
재사용성 : 인터페이스를 통해 정의된 행위는 여러 클래스에 걸쳐 재사용될 수 있어, 코드 중복을 줄일 수 있습니다.

인터페이스의 다형성

인터페이스를 사용하는 큰 이점 중 하나는 다형성을 통해 서로 다른 클래스의 인스턴스를 하나의 공통 인터페이스 타입으로 관리할 수 있다는 것입니다.
public interface IUpdatable { void Update(); } public class Player : IUpdatable { public void Update() { Console.WriteLine("Player is updated."); } } public class Enemy : IUpdatable { public void Update() { Console.WriteLine("Enemy is updated."); } } public class NPC : IUpdatable { public void Update() { Console.WriteLine("NPC is updated."); } } // 아래 코드는 단순한 예시입니다. List<IUpdatable> characterList = new List<IUpdatable>(); // 보통 아래 로직은 GameMain에서 사용합니다. foreach (var character in characterList ) { character.Update(); }
C#
복사

인터페이스 분리 원칙

인터페이스 분리 원칙은 클라이언트가 사용하지 않는 메서드에 의존하도록 강제해서는 안 된다는 원칙입니다. 즉, 매우 구체적이고 작은 인터페이스가 더 큰 인터페이스보다 낫다는 것을 의미합니다.
단순하게, 인터페이스를 상속받은 클래스에 굳이 사용하지 않을 메소드를 구현하면 안 된다는 의미입니다. 그렇기에 C#의 기본 클래스들을 보면 다양한 인터페이스들을 다중 상속하고 있죠.
public interface IAttacker { void Attack(); } public interface IMover { void Move(); } // 이런식으로 세분화해서 인터페이스를 상속 받습니다. public class Fighter : IAttacker, IMover { public void Attack() { Console.WriteLine("Fighter attacks!"); } public void Move() { Console.WriteLine("Fighter moves!"); } } public class Turret : IAttacker { public void Attack() { Console.WriteLine("Turret attacks!"); } // Turret은 Move를 구현할 필요가 없습니다. }
C#
복사

인터페이스 의존성 역전 원칙

이 부분부터는 상당히 어려워집니다. 따라서, 의존성에 대한 이해가 먼저 필요합니다.
저는 의존성을 강조하는 구조 디자인 패턴에 대해 말하고 싶습니다. 아래의 글을 먼저 읽어보시면, 의존성에 대한 이해를 돕게 될 것입니다.
의존성 역전 원칙은 고수준 모듈이 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다는 원칙입니다.
// 추상화 (인터페이스) public interface IDataAccess { void SaveData(string data); } // 저수준 모듈 (구체적인 구현) public class FileDataAccess : IDataAccess { public void SaveData(string data) { // 파일 시스템에 데이터 저장 } } public class DatabaseDataAccess : IDataAccess { public void SaveData(string data) { // 데이터베이스에 데이터 저장 } } // 고수준 모듈 public class DataManager { private IDataAccess _dataAccess; public DataManager(IDataAccess dataAccess) { _dataAccess = dataAccess; // 추상화에 의존 } public void Save(string data) { _dataAccess.SaveData(data); // 구체적인 구현에 대한 지식 없이 사용 } }
C#
복사

고수준 모듈과 저수준 모듈

고수준 모듈 : 핵심 로직을 담당하는 부분입니다. 전반적인 흐름을 제어하는 등의 역할을 합니다. 고수준 모듈은 "무엇을 할 것인가"에 초점을 맞춥니다.
저수준 모듈 : 고수준 모듈이 요구하는 기능을 구체적으로 수행하는 구현 부분입니다. 저수준 모듈은 "어떻게 할 것인가"에 대한 구현에 초점을 맞춥니다.

추상화에 의존한다는 의미

여기서 추상화는 인터페이스나 추상 클래스를 의미합니다.
구체적인 구현 대신 인터페이스에 의존함으로써, 구현이 변경되어도 인터페이스를 구현하는 새로운 클래스로 쉽게 교체할 수 있게 됩니다.(이게 가장 중요합니다.)
이렇게 함으로써, 고수준 모듈은 저수준 모듈의 구체적인 구현에 의존하지 않게 되므로, 결합도가 낮아지고 시스템의 유연성이 높아집니다.