인터페이스 (Interface)
다중 상속
- 하나의 클래스가 여러 개의 클래스로부터 상속 받는 것
- C#에서는 하나의 클래스가 동시에 두 개 이상의 클래스에서 상속 받을 수 없다.
= 두 개 이상의 부모 클래스를 가질 수 없다.
→ 인터페이스를 통해 다중 상속할 수 있다.
📍 인터페이스
- 행동적인 특성만을 정의 (메서드, 속성, 인덱서, 이벤트)
- 자식 클래스에 구현되어야 하는 기능을 선언할 시, 일반적으로 이질적인 클래스들이 공통으로 제공해야 할 메소드들을 선언할 때 사용한다.
- 다중 상속을 구현하기 위해 사용한다.
class 키워드 대신 interface 키워드를 사용하여 선언한다.
interface IPrintable
{
void Print();
}
class Document : IPrintable
{
public void Print() { Console.WriteLine("Print document"); }
}
class Photo : IPrintable
{
public void Print() { Console.WriteLine("Print photo"); }
}
- 여기서
Document 클래스와 Photo 클래스가 서로 이질적인 관계이다.
📍 클래스 & 인터페이스 관계
class & class ⇒ 상속 관계
class & interface ⇒ 구현 관계
interface & interface ⇒ 상속 관계
🍀 클래스 & 인터페이스 사용 예시 1
interface IMyInterface: IBase1, IBase2
{
void MethodA();
void MethodB();
}
- 여러 개의 인터페이스를 상속 받은 인터페이스는 상위 인터페이스의 메서드를 모두 구현해야 한다.
class ClassA: IFace1, IFace2
{
// class members, implementing interface
}
class ClassB: BaseClass, IFace1, IFace2
{
// class members, implementing interface
}
ClassB : 클래스와 인터페이스를 함께 상속 받는 경우, 기본 클래스가 처음에 위치한다.
BaseClass ⇒ 클래스
IFace1 & IFace2 ⇒ 인터페이스
🍀 클래스 & 인터페이스 사용 예시 2
interface IScalable
{
void ScaleX(float factor);
void ScaleY(float factor);
}
public abstract class DrawObject
{
public DrawObject() {}
public abstract void Print();
}
public class TextObject: DrawObject, IScalable
{
private string text;
public TextObject(string text)
{
this.text = text;
}
public void ScaleX(float factor)
{
Console.WriteLine("ScaleX: {0} {1}", text, factor);
}
public void ScaleY(float factor)
{
Console.WriteLine("ScaleY: {0} {1}", text, factor);
}
public override void Print()
{
Console.WriteLine(“TextObject: {0}", text);
}
}
- 클래스와 인터페이스를 동시에 상속 받을 땐 클래스가 더 앞에 온다.
- 추상 클래스(부모)는
abstract 메서드로 선언되고, 반드시 자식 클래스에서 override 메서드로 구현한다.
- 인터페이스는 키워드 없이 반드시 자식에서 구현해야 한다.
📍 IComparable 인터페이스
- 객체 간 비교를 가능하게 해주는 인터페이스
- 정렬을 하기 위해 사용된다.
ComparableTo 메서드를 제공한다.
int CompareTo (Object obj)
obj 인수 : 인터페이스를 구현하는 클래스와 같은 형식
- 리턴 값은 현 객체가 obj보다 작으면 → 음수 / 같은면 → 0 / obj보다 크면 → 양수를 리턴한다.
public clas Age: IComparable
{
protected int m_value;
public Age(int value)
{
m_value = value;
}
public int CompareTo(object obj)
{
// 실제 int 비교
if(obj is Age temp)
return m_value.CompareTo(temp.m_value);
throw new ArgumentException("Object is not Age");
}
}
🍀 Sort() 활용 예시
List<Age> list = new List<Age>
{
new Age(25), new Age(20), new Age(30)
};
list.Sort();
list.Sort() : CompareTo 에 따라 정렬
List<T>.Sort() 메서드는 내부에서 CompareTo()를 호출하여 정렬한다.
📍 IEquatable 인터페이스
- 객체가 다른 객체와 같은지 비교할 수 있도록 해주는 인터페이스
bool Equals (Object obj)
obj 인수 : 인터페이스를 구현하는 클래스와 같은 형식
- 리턴값 ==
obj → true 를 리턴
리턴값 != obj → false 를 리턴
🍀 IEquatable 인터페이스 예시
Person 클래스가 같은 사람인지 비교
public class Person: IEquatable<Person>
{
public string name;
public int age;
public bool Equals (Person p)
{
if(p is Person)
return name.Equals(p.name && age.Equals(p.age);
throw new ArgumentException("Object is not Person");
}
}
사용 예시
Person p1 = new Person { name = "Suhyeon", age = 22 };
Person p2 = new Person { nae = "Suhyeon", age = 22 } ;
Conole.WriteLine(p1.Equals(p2)); // -> true
📍 IEnumerable 인터페이스
- 컬렉션을 반복할 수 있도록 해주는 인터페이스
- 반복을 위한 "열거자(IEnumerator)"를 반환한다.
IEnumerator GetEnumerator()
↪ foreach 루프가 동작하려면 이 메서드를 사용해야 한다.
🍀 IEnumerable foreach문 예시
foreach -> IEnumerable.GetEnumerator() -> IEnumerator 사용 (MoveNext + Current)
📍 IEnumerator 인터페이스
- 반복하면서 현재 위치를 추적할 수 있도록 해주는 인터페이스
- 아래와 같은 메서드로 구성되어 있다.
① object Current : 컬렉션의 현재 요소를 가져오는 속성
② bool MoveNext() : 컬렉션의 다음 요소로 이동하는 메서드
③ void Reset() : 컬렉션의 첫번째 요소 앞의 초기 위치로 설정하는 메서드
🍀 IEnumerable interface 예시
public class Tokens: IEumerable
{
private string[] elements;
public Tokens(string source, char[] delimiters)
{
elements = source.Split(delimiters);
}
// implementaion of GetEnumerator()
public IEnumerator GetEnumerator()
{
return new TokenEnumerator(this);
}
// implementation of TokenEnumerator
private class TokenEnumerator
private class TokenEnumerator: IEnumerator
{
private int positioin = -1;
private Tokens t;
public TokenEnumerator(Tokens t)
{
this.t = t;
}
public void Reset()
{
position = -1;
}
public bool MoveNext()
{
if(position < t.elements.Length-1)
{
position+++;
return true;
}
else
return false;
}
public object Current
{
get { return t.elements[position] };
}
}
}
Token 테스트
class EnumeratorTest
{
pubilc static void Main()
{
Tokens f = new Tokens("This is a sample test.", new char[] {' ', '-'});
foreach (string item in f)
{
Console.WriteLine(item);
}
}
}
💡 추상 클래스와 인터페이스의 공통점과 차이점
- 모두 추상의 의미이다.
new 키워드를 사용하여 객체를 생성하는 것이 불가능하다.
- 자식 클래스에서 모든 메서드를 구현하였을 경우에만 기능을 발휘할 수 있다.
- 클래스와 메서드가
abstract로 선언되어 있다면 인터페이스로 변환할 수 있다.
🍀 추상 클래스 → 인터페이스 변환 예시
추상 클래스
abstract public class StarPlayer
{
public abstract void GoodPlay();
public abstract void Handsome();
}
인터페이스
interface IStarPlayer
{
void GoodPlay();
void Handsome();
}
- 모든 추상 클래스가 인터페이스로 변환될 수 없다.
- 추상 메서드는
override 키워드를 사용한다.
- 추상 클래스는 다중 상속을 지원하지 않는다.
📍 is 연산자
- 데이터의 형 변환이 가능하면 true를 반환한다.
- 실패 시 결과 :
false
- 사용 용도 : 타입의 확인을 위해 사용한다. (확인 후 수동 변환)
Bird b;
if (a is Bird)
b = (Bird)a; // 안전한 형 변환
else
Console.WriteLine("Not a Bird.);
📍 as 연산자
- 객체 사이의 형 변환 연산자
- 오류 발생 시, exception 발생 없이 null을 반환한다.
- 실패 시 결과 :
null
- 사용 용도 : 타입을 바꾸고 싶을 때 사용한다. (안전하게 형 변환)
Bird b = a as Bird; // 형 변환
if (b == null)
Console.WriteLine("Not a Bird.")
📍 Boxing
- 값형식 → 참조형식으로 바꾸는 것
- 암시적 변환
int a = 127;
object o1 = a;
object o2 = o1;
📍 UnBoxing
- 참조형식 → 값형식으로 바꾸는 것
- 명시적 변환
int b = (int)o2;
📍 메서드 오버로딩
- 한 클래스 내에서 두 개 이상의 이름이 같은 메서드를 작성한다.
✔ 메서드 이름이 동일해야 한다.
✔ 매개 변수의 개수가 서로 다르거나, 타입이 서로 달라야 한다.
✔ 리턴 타입은 오버로딩과 관련 없다.
class Method overloading
📍 메서드 오버라이딩
- 부모 클래스의 메서드를 자식 클래스에서 재정의한다.
- 동적 바인딩이 발생한다.
✔ 자식 클래스에서 오버라이딩된 메서드가 무조건 실행되도록 동적 바인딩 된다.