삽질의 현장/- .NET

#004_닷넷(.NET)_.Net Framework 기본 - 인터페이스

shovelman 2015. 10. 22. 01:13


안녕하세요 삽잡이입니다.


이전 시간에 CTS에 대해서 알아보며 Type에 대해서 언급한적이 있습니다.

이번 시간에는 CTS의 다섯개의 타입 중 왕따인... 

인터페이스에 대해서 알아보고자 합니다.


인터페이스는 독립적입니다. 객체 지향에서 매우 중요하지요...

interface는 명사로 '중개자', 동사로 '중개하다, 소통하다'의 뜻을 가지고 있습니다.

왜 명사, 동사를 나눠서 말씀을 드렸을까요...

이유는 그 자체를 뜻하기도, 동작을 뜻하기도 하기 때문입니다.


그렇다면 객체지향에서 말하는 객체지향이란 뭘까요?



예를 들어서... 학생이라는 클래스가 있다고 해봅시다.

이 학생이라는 클래스에는 공대생, 미대생, 인문계생 등이 있다고 해보겠습니다.

이 모든 학생들은 기본적으로 공부를 합니다...

그런데 말입니다... 각 학생들은 

즉, 공대생, 미대생, 인문계생들 모두 공부하는 방법에 차이가 있을 것 아니겠습니까?


객체 측면에서 보면 공대생 객체가 만들어졌을 때

이 공대생 객체는 공부라는 메소드를 호출할 수 있습니다.

왜냐하면 학생이라는 클래스는 기본적으로 공부라는 메소드를 가지고 있는데

이를 상속받았기 때문이지요...


이때, 이 공부라는 메소드는 공대생 객체가 

객체의 공부하기를 호출한 놈과 이야기를 하는 것입니다.

따라서, 소통한다는 뜻의 Interface라고 불리는 것이죠..


C#에 대한 인터페이스...

닷넷에 대한 인터페이스에 대해서 알아볼까요?



A1 클래스에는 a()라는 메소드가 있습니다.

그렇다면 a()는 A1 클래스의 인터페이스입니다.

왜냐하면, A1 클래스와 이야기를 하려면 항상 a() 메서드를 통해서 얘기를 해야하기 때문입니다.


그런데, A3 클래스의 인터페이스는 a()이자 c()입니다.

상속을 받았기 때문입니다.

A3 클래스는 그렇다면 a(), c() 인터페이스를 가지고 있는 것입니다.


다시 한번 말씀드려보겠습니다...

왜 interface라고 부르는 것입니까?

바로... A3 클래스의 내부와 c() 메서드를 통해서 이야기하기 때문입니다.

A3.c() 이렇게 호출하지 않습니까...


그런데 이런 상속 구조 말고,

a(), c() 라는 메서드의 기능을 무수히 많이 사용해야한다면,

a(), c() 메서드만 따로 모을 수 있습니다.


그런데, 이 따로 모은 곳에서는 내용물은 없고 오직 인터페이스만 있어야됩니다.

그 인터페이스는 a(), c() 이고, 행동 즉, 내용물이 없어야 된다는것이지요...


a() 메서드가 만약 공부하기()라는 인터페이스라고 생각해보겠습니다.

만약, 학생 객체에 공부하기() 인터페이스에 내용물이 차있게되면,

이 학생 객체를 상속 받은 자식 객체들은 각 객체에 맞는 인터페이스의 내용물을 가질 수 없게 됩니다.

이건 마치 미대생에게 코딩을 하라고 하는 것과 같아지는 것이지요...



즉, 핵심적인 내용물을 각각의 객체에 맞도록 해줘야합니다.

정리하자면, 각 객체에 맞는 인터페이스의 내용물을 가지게 해야한다는 것입니다.

이것이 바로 메소드의 정의라고 부릅니다.


공대생에게 공부하라고 하면 코딩 공부를 하도록 공부하기() 메서드를 정의하고,

미대생에게 공부하라고 하면 뭐 미술에 관련된 공부를 하도록 공부하기() 메서드를 정의해야겠지요...

다시 말씀드리지만 이를 '메서드 정의'라고 부릅니다.


따라서 인터페이스라는 것은

공부하기() 하면 각 객체에 맞도록 공부하도록 정의가 되어있게 되는 것이지요...


공부하기()와 같은 명령은 외부에서 날라옵니다.

공부하기()의 동작은 내부에 정의가 되어있을 것이구요...

외부에서는 내부 정의를 모르겠지요...

하지만, 공대생이던, 미대생이던 모두 '공부하기()'라는 기능을 가지고 있다는 것은 알고 있습니다.


공부하기() 메서드를 외부에서 호출했는데, 

외부에서는 내부에 정의되어있는 사실을 모르기 때문에

외부와 내부와 이야기를 하기 위해서 

공부하기()라는 메소드의 시그니처를 통해 이야기하게 됩니다.

이를 인터페이스라고 하지요... 

즉, 메서드의 시그니처를 인터페이스라고 하는 것이고,

인터페이스란 객체의 내부와 외부 사이에 이야기하는 소통의 단위라고 할 수 있습니다.


정리를 하자면...

객체의 내,외부를 구분하고 그 내,외부가 통신할 수 있도록 하는 수단이 

바로 인터페이스이자 메소드 시그니처라고 하는 것이지요...


학생이라는 클래스는 공부하기() 메소드에 대한 정의를 할 수 없습니다.

그런데, 모든 학생객체들은 공부하기()라는 인터페이스를 가져야한다고 해봅시다.

따라서 학생 클래스에 순수 가상 메서드를 만들게 되는 것입니다.

즉, 모든 학생 객체들은 공부하기()라는 인터페이스를 가져야한다 이겁니다.


 


자... 학교 선생님들도 공부를 한다고 해보겠습니다.

그런데 이 학교 선생님도 클래스를 상속받고 상속 하고 있을 것입니다...

선생님이라는 클래스에도 공부하기() 메서드가 있습니다.

그리고 해당 메서드에 대한 정의가 있겠지요...


그러면 봐봅시다...

학생 클래스에도 공부하기(), 선생님 클래스에도 공부하기() 메서드가 있습니다.

그렇다면 선생님 클래스들도 모두 하나로 다룰 수 있습니다.


그런데 학생 클래스와 선생님 클래스를 같이 다룰 수는 없습니다.

왜냐하면, 각 타입이 다르기 때문입니다... 또한 상속 구조가 다르지요...

하지만, 모든 객체들이 공부하기() 메서드를 수행할 수 있다는 것은 가지고 있습니다.


객체지향에서는 추상적으로 다루는 것이 편합니다.

왜냐하면, 유연성을 가지고 확장성을 가지게 되기 때문입니다.

추상적으로 동시에 객체들을 다룰 수 있을 때,

공부하기()라는 메서드를 공통적으로 가지고 있으니까

어느 객체든지 해당 메서드를 수행하라고 요청을 하기만 하면 됩니다...


만약 이처럼 인터페이스 설계를 하지 않는다면,

객체가 추가 될 때마다 즉, 확장성에 문제가 생깁니다.

각각 객체마다 공부하기() 메서드를 다뤄줘야하기 때문입니다.

그런데 다형성을 기반으로 한 인터페이스 설계를 잘 할 시

각 객체에 맞도록 메서드를 호출할 수 있기 때문에 확장성이 용이합니다.


모든 객체들을 각각 추상적으로 다루고 싶은데,

만약 모두 동일한 메서드를 가지고 있다면 메소드 시그니처를 통해 다룰 수 있다는 것입니다.


그런데 우리는 이 인터페이스에 의해서

각기 다른 타입과 상속 구조일지라도 모두 한꺼번에 다룰 수 있습니다.

즉, 공부하기()라는 메소드를 보유한 놈들만 담아서 해당 메소드를 호출시키면 된다는 것입니다.


그런데 여기서 오해를 하시면 안되는 것이

객체지향의 개념적으로 계층 구조가 다르기 때문에

즉, 개념 구조가 다르기 때문에 각각 객체를 선생님이던지, 학생이던지 넣을 수 없습니다.

개념간의 계층 구조가 다르기 때문에 말입니다...


선생님과 학생 클래스에는 공통점이 있지 않습니까?

공부하기()라는 시그니처... 그 행동(정의)는 다르지만 시그니처는 같지 않습니까...

따라서 '공부하기()가 가능하다'는 메서드 시그니처만 구현을 해놓는 것입니다...

(여담으로, 인터페이스는 ~가능한... ~able이 붙습니다.)


공부하기()라는 기능을 최상위 클래스에 만들어 놓는 것이 아닌

'공부하기() 가능한'이라는 인터페이스를 구현한다는 것이지요...

인터페이스인 메서드 시그니처만 만들어뒀다면

각 클래스들이 상속을 받아서 구현을 하도록 하면 된다 이겁니다...


따라서, 모든 클래스들은 모두 공부하기() 메서드를 가지지 않고

implementation 해서 해당 메소드 시그니처를 가져와서 각자 구현을 하는 것이지요...


'공부하기() 가능한' 이라는 하나의 타입으로 보자면

각각 객체들을 하나의 타입으로 볼 수 있지 않겠습니까...


이게 바로 핵심인 것입니다...

누구든, 어떤 계층에 있던, 어떤 객체던 관심이 있는 것이 아닙니다.

객체의 기능이 수행되는데 공통적인 기능을 가지고 있다면

인터페이스를 하나 만들고 한꺼번에 다룰 수 있도록 하자는 것이지요...

이렇게 된다면, 한 타입으로 각 객체들을 다룰 수 있게 된다 이겁니다.


그런데 각 객체들을 정확하게 말하자면 인터페이스를 갖는다고 할 수 있지만, 

타입을 상속받는 다고 하기에는 좀 그렇습니다... 오직 인터페이스만 가지고 있기 때문이지요...


그런데 이 인터페이스는 하나의 메서드만 해당하는 것이 아닙니다.

즉, 하나의 시그니처만 있을 수 있다는 것이 아닙니다. 여러개가 가능하다 이거지요...

인터페이스는 정의가 없기 때문에 여러가지일 수 있다는 사실을 기억하시길 바랍니다.


아무튼... 

서로 다른 계층 구조내에 객체들을 

하나의 타입처럼 사용가능하도록 할 수 있다는 사실을 알게 됬습니다.

그래서 추상적으로....

즉, 객체지향에서 인터페이스 구조로 프로그램을 만들게 되면...

보다 유연하게 구현할 수 있다는 것입니다.


결론적으로 닷넷 인터페이스는

'공개 메소드 시그니처 집합'에 이름을 붙인 것이라고 부를 수 있습니다.

인터페이스니까 서로 공개가 되있어야겠지요...



자... 그럼 좀 더 나아가서 상속에 대해서 알아가보도록 하겠습니다...

상속에는 크게 두 가지가 있습니다.

Shape이라는 클래스에 Draw()라는 메서드가 있다고 해봅시다.

추가적으로 이 안에는 Rect, Ellipse 클래스가 파생되어 있는데 

Draw() 메서드를 상속한다고 해보겠습니다.


위에서 주구장창 배웠으니... 왜라고 하시진 않겠지만...

Shape 클래스의 Draw() 메서드에는 기능이 없을 것입니다.

하지만 상속받은 Rect와 Ellipse 클래스에는 기능에 대한 내용물이 있겠지요...


그래서 보통 Shape과 같은 최상위 클래스에는 기능이 있을 수 없기 때문에

순수 가상으로 만들게 됩니다.


Rect, Ellipse는 Shape을 상속하는데

Draw() 메서드를 각 클래스들이 물려받는 것은 인터페이스뿐입니다.

즉, 인터페이스만 물려받고 내용물은 물려받지 않는 것입니다.

(물려 받는 것 자체를 상속이라고 하지 않고, 인터페이스 구현이라고 할 수 있습니다.)


그러면 Ellipse나 Rect나 모두 Draw() 메서드를 가지고 있다는 것은 보장이 됩니다.

즉, Shape이라는 부모를 선택한 자식 클래스들은 Draw()를 물려받았다는 것이지요...

이를 인터페이스 상속이라고 부릅니다.



그리고 두번째에는 '구현 상속'이 있습니다.

객체를 만들게 되면 자신만의 Uniqe한 ID를 가지고 싶다고 해보겠습니다.

그러면 우리가 예시로 든 Shape 클래스에 GetID()라는 메소드를 만들었다고 하지요...

자... Shape는 Uniq한 ID를 만들어내는 객체가 됬습니다.

그런데 이 GetID() 메소드는 동일한 기능을 가지고 있을 터이니

최상위 클래스인 Shape이 내용물을 가지고 있을 것입니다.


객체가 만들어질 때마다 생성 방식이 동일하니 Shape에 만들어지는 것이지요...

그런데 Rect나 Ellipse 클래스는 부모의 것을 물려받게 되었지만

Draw() 메서드와는 다릅니다.

GetID()라는 메서드는 내용물까지 상속을 받았는데

Draw() 메서드는 인터페이스만 상속받은 것이니 말입니다...


Draw() 메서드와 GetID() 메서드의 차이점은

전자는 구현은 자식에서 하고 인터페이스만 가져온 것이고,

후자는 인터페이스, 내용물 둘다 물려받았다는 것입니다.


따라서 Ellipse 객체를 최상위 부모인 Shape 형태로 만들었다고 해봅시다...

Shape s = Ellipse;

s.Draw();

s.GetID();


Draw() 메서드의 경우 인터페이스는 Shape 것이지만, Ellipse 것을 쓸 것입니다.

GetID() 메서드의 경우에는 인터페이스도 Shape이지만 구현도 Shape 것을 쓸 것이구요...

물려받았기 때문에 말입니다...


그래서 인터페이스 상속과 인터페이스 구현이라는 상속 두 가지가 있다고 말하는 것입니다.


인터페이스를 만들어 놓으면

인터페이스는 클래스가 아니기 때문에 구현을 해놓지 않습니다.

인터페이스만 가지고 있으니 해당 인터페이스를 물려받는 클래스에서 구현을 해줘야합니다.

그래서 물려받는 일 자체를 상속이라고 부르지 않고

인터페이스 구현이라고 하는 것입니다...



이번 시간은 여기까지 하도록 하겠습니다.

길고 지루한 글이 된 것 같군요...


이상 삽잡이였습니다!