인터페이스 Interface

Gogi·2023년 9월 6일

C# 언어 기초 목록

목록 보기
13/29

약속, 계약, 명세, 규격 등등

'전에 만들었던 명령 패턴처럼 기능을 추가하기 쉬운 구조가 됨'

추상클래스와 인터페이스의 차이점

추상 클래스 (Abstract Class)

상태 정보: 추상 클래스는 멤버 변수를 가질 수 있으므로 상태 정보를 저장할 수 있습니다.

구현: 추상 클래스는 일부 메서드가 구현될 수 있습니다. 즉, '메서드에 대한 기본 구현을 제공'할 수 있습니다.

접근 지정자: 추상 클래스의 메서드는 public, protected, internal 등 다양한 접근 지정자를 가질 수 있습니다.

상속: '추상 클래스를 상속받는 클래스는 반드시 추상 클래스의 모든 추상 메서드를 구현'해야 합니다. 추상 클래스는 단일 상속만 지원합니다.

생성자: 추상 클래스에는 생성자가 있을 수 있으며, 이를 통해 상속받는 클래스에서 반드시 초기화해야 하는 로직을 정의할 수 있습니다.

인터페이스 (Interface)

상태 정보: 인터페이스는 '멤버 변수를 가질 수 없으'므로 상태 정보를 저장할 수 없습니다.

구현: 인터페이스의 모든 메서드는 추상 메서드입니다. 즉, '메서드에 대한 구현을 가질 수 없고' 오로지 '선언만 가능'합니다. // 재정의 강제

접근 지정자: 인터페이스의 모든 메서드는 암시적으로 public 입니다.

상속: 인터페이스를 구현하는 클래스 또는 인터페이스는 인터페이스의 모든 메서드를 구현해야 합니다. 인터페이스는 '다중 상속을 지원'합니다.

생성자: 인터페이스에는 '생성자가 없습니다.'

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Extending an Interface

  • System.Collections.IEnumerator
  • 모든 멤버는 암시적으로 public 권한

public interface IEnumerator
{
bool MoveNext();
object Current { get; } // 얘는 모양이 오토프로퍼티지만 오토프로퍼티가 아니라 선언만 써논 거임
void Reset();
}

internal class Countdown : IEnumerator
{
int count = 11;
public bool MoveNext() => count-- > 0;
public object Current => count;
public void Reset() { throw new NotSupportedException(); }
}

IEnumerator e = new Countdown();
while (e.MoveNext())
Console.Write (e.Current); // 1098765

인터페이스 상속받은 후 재구현 강제
서로 반드시 갖고 있어야할 메서드 정의(?)
'인터페이스는 override를 붙이지 않음'

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Explicit Interface Implementation

  • 인터페이스를 다중 상속 받는 경우 충돌 가능성 있다.
  • 재정의 하는 인터페이스를 명시하는 걸로 충돌을 피할수 있다.
  • 명시적으로 재정의하는 인터페이스 멤버를 호출하는 방법은 업캐스팅 이후에 호출하는 방법 밖에 없다.

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Implementing Interface Members Virtually // 이름 앞에 I를 붙일 것

public interface IUndoable { void Undo(); }

public class TextBox : IUndoable
{
public virtual void Undo() => Console.WriteLine ("TextBox.Undo");
}

public class RichTextBox : TextBox
{
public override void Undo() => Console.WriteLine ("RichTextBox.Undo");
}

RichTextBox r = new RichTextBox();
r.Undo(); // RichTextBox.Undo
((IUndoable)r).Undo(); // RichTextBox.Undo
((TextBox)r).Undo(); // RichTextBox.Undo

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

명시적 인터페이스 구현(Explicit Interface Implementation)

C#에서 인터페이스의 메서드를 구현할 때 발생할 수 있는 이름 충돌을 해결하는 메커니즘이다.
명시적으로 인터페이스를 구현하면 해당 메서드는 클래스 레벨에서는 직접 호출할 수 없으며, 인터페이스 타입을 통해서만 접근 가능하다.

충돌의 예

이름이 같은 메서드를 가진 두 개 이상의 인터페이스를 구현하려면 충돌이 발생할 수 있다.
예를 들어, 위의 I1I2 인터페이스는 모두 Foo()라는 메서드를 가지고 있다.
Widget 클래스가 이 두 인터페이스를 모두 구현하려면 이 충돌을 어떻게 해결할지 결정해야 한다.

명시적 인터페이스 구현을 사용한 해결

Widget 클래스에서 I1Foo()는 일반적으로 구현하고(public void Foo()),
I2Foo()는 명시적으로 구현(int I2.Foo())한다. 이렇게 하면, Widget 클래스의 인스턴스를 통해
Foo()를 호출하면 I1의 구현이 실행된다. I2Foo()를 호출하려면 I2로 형변환한 후에 호출해야 한다.

업캐스팅 이후 호출

명시적으로 구현된 인터페이스 멤버는 해당 인터페이스 타입으로 형 변환된 객체를 통해서만 접근할 수 있다.
예를 들어, ((I2)w).Foo()처럼 Widget 객체 wI2로 형변환해야 I2.Foo()를 호출할 수 있다.

요약

명시적 인터페이스 구현은 인터페이스 구현의 이름 충돌을 피하는 유용한 방법이다.
하지만 명시적으로 구현된 멤버는 해당 인터페이스로 형변환하여 접근해야 한다는 제약이 있다.

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

인터페이스 멤버 가상 구현 Implementing Interface Members Virtually

인터페이스 멤버를 가상 함수로 재정의 가능함. 기본값은 sealed

public interface IUndoable { void Undo(); }

public class TextBox : IUndoable
{
public virtual void Undo() => Console.WriteLine ("TextBox.Undo");
}

public class RichTextBox : TextBox
{
public override void Undo() => Console.WriteLine ("RichTextBox.Undo");
}

RichTextBox r = new RichTextBox();
r.Undo(); // RichTextBox.Undo
((IUndoable)r).Undo(); // RichTextBox.Undo
((TextBox)r).Undo(); // RichTextBox.Undo

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

하위 클래스에서 인터페이스 재구현 Reimplementing an Interface in a Subclass

  • 서브 클래스에서 베이스 클래스에 상속 받은 인터페이스를 다시 상속 받을 수 있음

public interface IUndoable { void Undo(); }

public class TextBox : IUndoable
{
void IUndoable.Undo() => Console.WriteLine ("TextBox.Undo"); // explicitly, virtual
}

public class RichTextBox : TextBox, IUndoable
{
public void Undo() => Console.WriteLine ("RichTextBox.Undo");
}

RichTextBox r = new RichTextBox();
r.Undo(); // RichTextBox.Undo Case 1
((IUndoable)r).Undo(); // RichTextBox.Undo Case 2

public class TextBox : IUndoable
{
public void Undo() => Console.WriteLine ("TextBox.Undo"); // implicitly, break
}

RichTextBox r = new RichTextBox();
r.Undo(); // RichTextBox.Undo Case 1
((IUndoable)r).Undo(); // RichTextBox.Undo Case 2
((TextBox)r).Undo(); // TextBox.Undo Case 3 // break

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

인터페이스 재구현을 위한 대안 Alternatives to interface reimplementation

public class TextBox : IUndoable
{
void IUndoable.Undo() => Undo(); // Calls method below
protected virtual void Undo() => Console.WriteLine ("TextBox.Undo");
}

public class RichTextBox : TextBox
{
protected override void Undo() => Console.WriteLine("RichTextBox.Undo");
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

인터페이스와 박싱 Interfaces and Boxing

interface I { void Foo(); }
struct S : I { public void Foo() {} } // 구조체는 인터페이스를 상속받을 수 있다

...
S s = new S();
s.Foo(); // 박싱 X

I i = s; // 인터페이스에 형 변환할 때 상자가 발생합니다.
i.Foo();

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

디폴트(기본) 인터페이스 멤버(C#8부터)

interface ILogger
{
void Log (string text) => Console.WriteLine (text);
}

class Logger : ILogger { }

(new Logger()).Log ("message"); // compile error
((ILogger)new Logger()).Log ("message");

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRITING A CLASS VERSUS AN INTERFACE

  • 공유하는 속성이 있는 것들은 상속해라.
  • 독립적인 기능은 인터페이스로 구현해라.

abstract class Animal {}
abstract class Bird : Animal {}
abstract class Insect : Animal {}
abstract class FlyingCreature : Animal {} // interface IFlyingCreature {}
abstract class Carnivore : Animal {} // interface ICarnivore {}

// Concrete classes:

class Ostrich : Bird {}
class Eagle : Bird, FlyingCreature, Carnivore {} // Illegal
class Bee : Insect, FlyingCreature {} // Illegal
class Flea : Insect, Carnivore {} // Illegal

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

profile
C, C++, C#, Unity

0개의 댓글