인터페이스의 구현에는 두 가지가 있습니다. 이른바 암시적 구현과 명시적 구현이라고 하는데요, 일반적인 경우에는 주로 암시적 구현을 사용하지만 경우에 따라서는 명시적 구현을 사용해야 하는 경우도 있습니다.

먼저 암시적 구현 부터 살펴볼까요? 두 개의 인터페이스가 있다고 합시다.

01 public interface IGunner

02 {

03     void Shoot();<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

04 }

05

06 public interface ISoccerPlayer

07 {

08     void Shoot();

09 }

코드 1

IGunner는 총을 쏘는(shoot)을 메소드를 정의하고 있고, ISoccerPlayer는 공을 차는(shoot) 메서드를 정의하고 있습니다. 공교롭게 메서드의 이름이 같긴 하지만, 이 두 인터페이스는 서로 전혀 연관이 없습니다. 또한 3번과 8번 라인에서 아무런 접근 지정자가 붙어있지 않은 점도 유심히 보시기 바랍니다. 클래스의 경우 멤버의 접근 지정자가 붙어있지 않다면 private이 생략된 것으로 간주하지만, 인터페이스의 경우에는 public이 생략된 것으로 간주합니다. 아니 간주하는 것이 아니라, 인터페이스의 멤버는 그 성격상public외의 접근 지정자를 가질 수가 없습니다. 따라서 항상 public이기 때문에 인터페이스의 멤버에는 아무런 접근 지정자를 붙일 필요가 없습니다. 오히려 접근 지정자를 붙이면 문법 오류입니다.

이번에는 이들 인터페이스를 각각 구현하는 총잡이와 축구 선수 클래스를 생각해봅시다.

01 internal class Gunner : IGunner

02 {

03     public void Shoot()

04     {

05         Console.WriteLine("빵빵");

06     }

07 }

08

09 internal class SoccerPlayer : ISoccerPlayer

10 {

11     public void Shoot()

12     {

13         Console.WriteLine("");

14     }

15 }

코드 2

인터페이스에 정의된 메서드의 이름이 같기 때문에 이를 구현하는 두 클래스에서의 메서드 이름도 같을 수 밖에 없지만, 그 동작은 전혀 다릅니다. 총잡이는 총을 쏘고, 축구 선수는 공을 차지요. 그리고 3번 라인과 11번 라인에서는 public 접근 지정자가 붙어 있습니다. 인터페이스를 정의할 때는 붙이지 않았던 접근 지정자를 인터페이스를 구현하는 클래스에서는 이렇게 지정하여야 합니다. 인터페이스에서의 접근 지정자가 (생략된) public 이었기 때문에, 클래스에서도 public 이외의 다른 값을 가질 수는 없습니다. (곧 살펴 볼 명시적 인터페이스에는 적용되지 않는 이야기입니다..)

총잡이와 축구 선수의 인스턴스를 만들고 각각 Shoot 메서드를 호출해보도록 합시다.

1 private static void Main(string[] args)

2 {

3     Gunner gunner = new Gunner();

4     gunner.Shoot();

5

6     SoccerPlayer soccerPlayer = new SoccerPlayer();

7     soccerPlayer.Shoot();

8 }

코드 3

빵빵

계속하려면 아무 키나 누르십시오 . . .

당연히 의도한 대로의 결과가 나왔습니다.

여기까지가 인터페이스의 암시적 구현에 관한 이야기였습니다. 지금부터는 인터페이스의 명시적 구현에 관한 내용입니다.

명시적 구현을 설명하기 위해 새로운 클래스를 하나 등장 시켜야겠습니다. 군인 클래스인데요. 군인이니까 당연히 총을 쏩니다(shoot). 그리고 군인이니까 당연히(?) 공도 잘 찹니다(shoot). 그래서 병사 클래스는 IGunner ISoccerPlayer를 모두 상속 구현하도록 결정을 하겠습니다.

01 internal class Soldier : IGunner, ISoccerPlayer

02 {

03     public void Shoot()

04     {

05         Console.WriteLine("빵빵");

06     }

07

08     public void Shoot()

09     {

10         Console.WriteLine("");

11     }

12 }

코드 4

한 눈에 봐도 에러입니다. Shoot 메서드가 중복이 되었지요. 위와 아래의 메서드는 각각 Gunner SoccerPlayer Shoot인데, 이를 구별하지를 못합니다. 즉 시그니처(메서드의 형을 구별하는 기준, 쉽게 말하면 메서드의 이름과 매개 변수의 개수와 각 매개 변수의 형)가 동일한 메서드를 가진 두 인터페이스를 동시에 구현 상속하지 못하는 상황을 해결하기 위해 C#의 설계자는 별도의 문법을 고안하였습니다. 그것이 바로 인터페이스의 명시적 구현입니다.

인터페이스를 명시적으로 구현하는 문법은 간단합니다. 구현하는 메서드 이름 앞에 누구의 메서드인지만 표시해주면 됩니다. 다음 코드는 코드 4를 명시적 구현으로 바꾼 코드 입니다.

01 internal class Soldier : IGunner, ISoccerPlayer

02 {

03     void IGunner.Shoot()

04     {

05         Console.WriteLine("빵빵");

06     }

07

08     void ISoccerPlayer.Shoot()

09     {

10         Console.WriteLine("");

11     }

12 }

코드 5

3번 라인과 8번 라인을 보면 Shoot 메서드 앞에 각각 인터페이스의 이름을 붙이고 있습니다. 또한 접근 지정자가 사라졌습니다. 접근 지정자가 없기 때문에 Shootprivate 멤버입니다. 이 부분에 대해서는 잠시 후 다시 말씀 드리겠습니다.

이제 군인이 총을 쏘고 공을 차는 코드를 보겠습니다.

1 private static void Main(string[] args)

2 {

3     Soldier soldier = new Soldier();

4     soldier.Shoot(); // 쏘기

5     soldier.Shoot(); // 차기

6 }

코드 6

코드를 작성하긴 했는데 영 기분이 이상합니다. 아니나 다를까 문법 에러라서 컴파일도 되지 않습니다. 코드 5 Shoot 메서드는 Soldier private 메서드라서 접근할 수 없다고 합니다. 혹 접근이 된다고 하더라도, 4번 라인에서는 총을 쏘고 5번 라인에서는 공을 차야 하는데, 뭐가 무엇을 하라는 말인지 구별이 안 갑니다. 결론적으로 인터페이스가 명시적으로 구현이 되면 Soldier 변수를 통해서는 접근할 수가 없다는 사실을 알았습니다. 그렇다면 이 군인이 총을 쏘고 공을 차게 하는 방법은 무엇일까요?

답은 Soldier가 아닌 IGunner 혹은 ISoccerPlayer를 통해서 접근을 한다는 것입니다. 즉 총을 쏠 때는 Soldier 객체를 IGunner로 형변환 하고, 공을 찰 때는 ISoccerPlayer로 형변환 한다는  것입니다.

1 private static void Main(string[] args)

2 {

3     Soldier soldier = new Soldier();

4

5     ((IGunner)soldier).Shoot(); // 쏘기

6     ((ISoccerPlayer)soldier).Shoot(); // 차기

7 }

코드 7

형변환을 하지 않는다면 Soldier 객체를 만들어 IGunner 혹은 ISoccerPlayer 변수가 그를 참조하도록 하는 방법도 있습니다.

1 private static void Main(string[] args)

2 {

3     IGunner gunner = new Soldier();

4     gunner.Shoot(); // 쏘기

5

6     ISoccerPlayer soccerPlayer = new Soldier();

7     soccerPlayer.Shoot(); // 차기

8 }

코드 8

이 경우에는 예컨데 3번 라인과 같이 생성된 Soldier 객체를 IGunner 변수에 참조시키면 IGunner 변수를 통해서는 총만 쏘지 공을 찰 수는 없습니다.

이제 코드 5를 다시 보며 두 Shoot 메서드의 접근 지정자에 대해서 생각을 해 봅시다. 코드 6에서 확인하듯이 Soldier 클래스의 입장에서 두 Shoot 메서더는 private 입니다. 하지만 코드 7에서 보듯이 IGunner ISoccerPlayer의 입장에서는 각각의 Shoot 메서드는 public 입니다. 결국 명시적으로 구현된 인터페이스 메서드의 접근 지정자는 상황에 따라서 서로 다른 두 개의 접근 지정자를 가지게 되는 것입니다.

이제 인터페이스의 암시적, 명시적 구현을 모두 다루었으니, 응용으로 이 둘을 섞어 놓은 형태를 보도록 합시다. 다음 코드를 보시기 바랍니다.

01 internal class Soldier : IGunner, ISoccerPlayer

02 {

03     public void Shoot()

04     {

05         Console.WriteLine("빵빵");

06     }

07

08     void ISoccerPlayer.Shoot()

09     {

10         Console.WriteLine("");

11     }

12 }

코드 9

3번 라인은 암시적으로, 8번 라인은 명시적으로 인터페이스 멤버를 구현하고 있습니다. 8번 라인이야 ISoccerPlayer Shoot을 구현한다고 명확히 알 수 있겠는데, 3번 라인의 경우에는 명확히 알 수가 없습니다. 이 경우 컴파일러는 Soldier가 구현하여야 한는 Shoot는 두 개가 있는데 그 중 하나는 ISoccerPlayer Shoot이기 때문에(8번 라인) 나머지 하나는 IGunner Shoot로 인식을 합니다.(3번 라인)

이를 사용하는 코드는 다음과 같습니다.

01 private static void Main(string[] args)

02 {

03     Soldier soldier = new Soldier();

04    

05     ((IGunner)soldier).Shoot(); // 차기

06     ((ISoccerPlayer)soldier).Shoot(); // 차기

07

08     soldier.Shoot(); // 쏘기

09 }

코드 10

코드 7과 달라진 점은 8번 라인과 같이 Soldier 변수를 통해서도 Shoot을 호출할 수 있다는 것입니다. 이 때 Soldier Shoot IGunner Shoot를 구현하고 있기 때문에 결국 8번 라인은 5번 라인과 동일한 결과가 나타납니다.

빵빵

빵빵

계속하려면 아무 키나 누르십시오 . . .

 

인터페이스의 명시적 구현을 마무리 짓기 전에 가지 중요한 사실이 있습니다. 실컷 명시적 구현을 연습해 놓고 이런 하려니 허탈하긴 한데, 바로 인터페이스의 명시적 구현은 필요한 곳이 아니면 가급적 사용하지 말라 것입니다. 이유는 여태껏 이야기한 내용에 들어 있습니다. 예를 들어 Soldier 클래스를 사용하는 개발자는 Soldier 클래스만 봐서는 Shoot 메서드의 존재를 알기 어렵습니다. 또한 만일 Soldier 클래스가 아닌 구조체라면, Shoot메서드를 호출하기 위해 ((IGunner)soldier).Shoot 같이 형변환을 마다 추가적인 박싱이 일어나기도 합니다.




출처 : http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=18&MAEULNO=8&no=1470&page=7


Posted by Sting!

댓글을 달아 주세요