목차
인터페이스에 대한 개인적인 생각
제 생각에는 인터페이스와 추상클래스는 주로 유지 보수를 위한 구조라고 보입니다.
다른 역할도 있겠지만, 인터페이스를 사용하면 유지 보수가 왜 원활해지는지만 알면, 인터페이스 사용에 큰 문제는 없을 것입니다.
그래서 인터페이스에 대한 설명을 여기서는 가장 중요하다고 생각하는 순서대로 설명하겠습니다.
인터페이스
인터페이스는 클래스가 구현해야 하는 메서드의 시그니처만을 정의하며, 실제 구현 내용은 각 클래스에서 결정합니다.
이 접근 방식은 게임 개발의 복잡성을 관리하고, 유지 보수를 용이하게 하는 데 중요한 역할을 합니다.
인터페이스의 기본 구조
인터페이스는 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#
복사
고수준 모듈과 저수준 모듈
•
고수준 모듈 : 핵심 로직을 담당하는 부분입니다. 전반적인 흐름을 제어하는 등의 역할을 합니다. 고수준 모듈은 "무엇을 할 것인가"에 초점을 맞춥니다.
•
저수준 모듈 : 고수준 모듈이 요구하는 기능을 구체적으로 수행하는 구현 부분입니다. 저수준 모듈은 "어떻게 할 것인가"에 대한 구현에 초점을 맞춥니다.
추상화에 의존한다는 의미
여기서 추상화는 인터페이스나 추상 클래스를 의미합니다.
구체적인 구현 대신 인터페이스에 의존함으로써, 구현이 변경되어도 인터페이스를 구현하는 새로운 클래스로 쉽게 교체할 수 있게 됩니다.(이게 가장 중요합니다.)
이렇게 함으로써, 고수준 모듈은 저수준 모듈의 구체적인 구현에 의존하지 않게 되므로, 결합도가 낮아지고 시스템의 유연성이 높아집니다.