Interface
Interface
C#의 인터페이스는 클래스가 구현해야 할 메서드, 속성 등의 계약을 정의하며, interface 키워드로 선언됩니다. 인터페이스는 다형성을 지원하고 코드 재사용성과 유연성을 높여주며, 다중 구현이 가능합니다. C# 8.0부터는 기본 구현 메서드를 제공하여 하위 호환성을 유지하면서 인터페이스를 확장할 수 있습니다. 추상 클래스는 인터페이스와 유사하지만, 상태(필드)를 가질 수 있고 일부 메서드는 구현을 포함할 수 있으며, 단일 상속만 가능하다는 차이점이 있습니다. 인터페이스와 추상 클래스는 “can-do”(인터페이스)와 “is-a”(추상 클래스) 관계로 구분하여 사용하는 것이 좋습니다.
인터페이스의 선언
인터페이스(interface)는 클래스가 구현해야 하는 메소드와 속성의 집합을 정의하는 역할을 합니다. 인터페이스는 interface 키워드로 선언하며, 메소드와 속성은 선언만 포함하고 구현을 가지지 않습니다.
인터페이스는 일종의 계약입니다. 클래스가 특정 인터페이스를 구현한다면, 그 인터페이스에 정의된 모든 멤버(메서드, 속성, 이벤트 등)를 반드시 구현해야 합니다. 이를 통해 서로 다른 클래스들이 동일한 인터페이스를 통해 상호 작용할 수 있도록 보장합니다. 인터페이스는 객체 지향 프로그래밍에서 다형성(polymorphism)을 구현하는 핵심적인 방법 중 하나입니다.
public interface ILogger
{
void Log(string message);
string LoggerName { get; set; }
}인터페이스는 약속
인터페이스를 구현한 클래스는 인터페이스의 모든 멤버를 반드시 구현해야 합니다. 이는 “약속”을 의미하며, 코드의 일관성과 명확성을 높이는 데 도움을 줍니다.
인터페이스를 사용하면, 구체적인 클래스 타입에 의존하지 않고도 코드를 작성할 수 있습니다. 예를 들어, ILogger 인터페이스를 사용하는 코드는 ConsoleLogger 뿐만 아니라, FileLogger, DatabaseLogger 등 ILogger를 구현하는 어떤 클래스와도 동작할 수 있습니다. 이는 코드의 유연성과 재사용성을 크게 향상시킵니다.
public class ConsoleLogger : ILogger
{
public string LoggerName { get; set; }
public void Log(string message)
{
Console.WriteLine($"[{LoggerName}] {message}");
}
}
ILogger logger = new ConsoleLogger { LoggerName = "Console" };
logger.Log("인터페이스 구현 예시");인터페이스를 상속하는 인터페이스
인터페이스는 다른 인터페이스를 상속받아 확장할 수 있습니다. 이를 통해 더욱 구체적인 계약을 정의할 수 있습니다.
인터페이스 상속은 인터페이스 간의 계층 구조를 만들 수 있게 해줍니다. IStorage 인터페이스는 IReadable과 IWritable의 기능을 모두 포함하면서, 추가적인 Clear 기능을 요구합니다. 이렇게 인터페이스를 조합함으로써, 더욱 세분화된 기능을 정의하고 재사용할 수 있습니다.
public interface IReadable
{
string Read();
}
public interface IWritable
{
void Write(string content);
}
// 두 인터페이스를 상속하는 인터페이스 정의
public interface IStorage : IReadable, IWritable
{
void Clear();
}여러 인터페이스, 한꺼번에 상속하기
C#은 여러 개의 인터페이스를 동시에 구현하는 것이 가능합니다.
C# 클래스는 단일 클래스만 상속할 수 있지만, 인터페이스는 여러 개를 동시에 구현할 수 있습니다. 이것이 인터페이스가 다중 상속의 문제를 해결하는 측면이 있습니다. FileStorage 클래스는 IStorage 인터페이스(이는 IReadable과 IWritable를 모두 상속)를 구현함으로써, 읽기, 쓰기, 초기화 기능을 모두 제공합니다.
public class FileStorage : IStorage
{
private string content;
public string Read()
{
return content;
}
public void Write(string content)
{
this.content = content;
}
public void Clear()
{
content = string.Empty;
}
}인터페이스의 기본 구현 메소드 (Default Interface Methods)
C# 8.0부터 인터페이스에 기본 구현(default implementation)을 제공할 수 있습니다. 이는 인터페이스의 확장성을 높이고, 기존 구현에 영향을 주지 않고도 새로운 메소드를 추가할 수 있게 합니다.
기본 구현 메서드는 인터페이스에 새로운 메서드를 추가할 때 유용합니다. 기존에 인터페이스를 구현하던 클래스들은 이 새로운 메서드를 반드시 구현할 필요가 없어, 하위 호환성을 유지할 수 있습니다. 하지만, 필요하다면 구현 클래스에서 기본 구현 메서드를 재정의(override)할 수도 있습니다.
public interface INotifier
{
void Notify(string message);
// 기본 구현 메소드
void NotifyInfo(string message)
{
Notify($"[INFO]: {message}");
}
}
public class EmailNotifier : INotifier
{
public void Notify(string message)
{
Console.WriteLine($"이메일 발송: {message}");
}
}
class Example
{
static void Main()
{
INotifier notifier = new EmailNotifier();
notifier.NotifyInfo("기본 메소드 사용 예시");
}
}추상 클래스: 인터페이스와 클래스 사이
추상 클래스(Abstract class)는 클래스와 인터페이스 사이의 개념으로, 일부 메소드만 구현하고 나머지는 자식 클래스에서 반드시 구현하도록 강제할 수 있습니다. 인터페이스와 달리 추상 클래스는 상태(필드)를 가질 수 있습니다.
추상 클래스는 abstract 키워드를 사용하여 정의합니다. 추상 메서드는 구현이 없고, 추상 클래스를 상속하는 자식 클래스에서 반드시 override 키워드를 사용하여 구현해야 합니다. 추상 클래스는 직접 인스턴스화할 수 없고(new Animal()은 불가능), 반드시 상속을 통해서만 사용됩니다. 추상 클래스는 공통적인 기능(일반 메소드)과 반드시 구현해야 할 기능(추상 메소드)을 함께 정의할 때 유용합니다.
public abstract class Animal
{
public abstract void MakeSound(); // 추상 메소드
public void Sleep() // 일반 메소드
{
Console.WriteLine("잠을 잡니다.");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("멍멍!");
}
}
Animal myDog = new Dog();
myDog.MakeSound(); // "멍멍!"
myDog.Sleep(); // "잠을 잡니다."인터페이스와 추상 클래스 비교
인터페이스와 추상 클래스는 모두 다형성을 지원하고, 코드의 결합도를 낮추는 데 기여합니다. 일반적으로, “is-a” 관계 (예: Dog is an Animal) 에는 추상 클래스를, “can-do” 관계 (예: Logger can do Log) 에는 인터페이스를 사용하는 것이 권장됩니다. 인터페이스는 클래스가 여러 기능을 수행할 수 있도록 유연성을 제공하는 반면, 추상 클래스는 클래스 계층 구조 내에서 공통적인 구현을 공유하는 데 더 적합합니다.
- 인터페이스
- 다중 구현 가능
- 구현체가 필수로 모든 메소드 구현
- 상태(필드) 보유 불가능 (기본 구현 제외)
- 추상 클래스
- 다중 상속 불가능 (단일 상속만 가능)
- 일부 메소드는 구현 가능
- 상태(필드)를 보유할 수 있음
두 기능을 적절히 활용하여 코드를 설계하면 코드의 유지보수성과 확장성을 크게 향상시킬 수 있습니다.