인터페이스

by 조쉬 posted Sep 13, 2016
?

단축키

Prev이전 문서

Next다음 문서

ESC닫기

크게 작게 위로 아래로 댓글로 가기 인쇄

인터페이스


1. 인터페이스란?

인터페이스는 일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다.

오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 허용하지 않는다.

추상클래스를 부분적으로만 완성된 '미완성 설계도'라고 한다면, 인터페이스는 구현된 것은 아무 것도 없고 밑그림만 그려져 있는 '기본 설계도'라 할 수 있다. 인터페이스도 추상클래스처럼 완성되지 않은 불완전한 것이기 때문에 그 자체만으로 사용되기 보다는 다른 클래스를 작성하는데 도움 줄 목적으로 작성된다.


cp.) 인터페이스란 무엇인가

사전적 의미는 어떤 객체와 객체의 중간에 놓이는 것(객체와 객체의 중간에 놓이는 통신 채널)


1) 인터페이스는 뭐 할 때 쓰는 건가?

둘이서 서로 다른 클래스를 만들 때 서로 ‘이렇게 만들자, 이렇게 만들어 드릴게요’라면서 약속할 때 쓰이는 것이 인터페이스이다. 즉 클래스를 만들기 전에 앞으로 이런 기능을 만들겠다. 이런 기능이 있었으면 좋겠다고 먼저 합의해놓은 것이 인터페이스이다.


2) 인터페이스는 어떻게 쓰는 것인가?

먼저 필요한 약속을 먼저 잡고, 한쪽에서 약속대로 호출하고, 한쪽에서는 약속대로 구현대로 사용한다. 인터페이스를 구현하는 입장에서는 약속한 대로 기능(메소드)을 만들어주고, 사용하는 입장에서는 실제 객체가 어떻든 간에 약속을 이행할 수 있는 객체이기만 한다면 마음대로 약속된 기능을 호출하는 방식이다.


3) 인터페이스는 새로운 기능인가?

프로그램을 설계하고 조금 더 유연한 프로그램을 만드는 기법이다. 인터페이스는 상속과 더불어서 다형성이라는 객체지향 프로그래밍의 특징을 구현하는 방식이다.


2. 인터페이스를 사용하는 상황

(1) 당신과 친구가 서로 약속을 한다면: 객체의 스펙

A가 사용할 객체를 만들어준다면 아마도 B는 A가 프로그래밍이 다 완료될 때까지 기다리고 있어야만 한다. 처음부터 A가 어떤 메소드를 만들 것인지를 먼저 정하는 것이다. 어떤 메소드에는 어떤 파라미터를 받게 할 것이고, 어떤 메소드에는 어떤 리턴값을 이용할 것이다 등 이런 논의를 먼저하게 되면 B는 적어도 A가 만든 클래스와 객체가 어떤 메소드를 가졌는지 알게 되고, 바로 당장은 아니더라도 프로그램을 개발하는데 도움이 될 것이다.

우리가 해야 하는 일은 정확하게 구분하고, 상대방에 해야 할 일을 명시해주는 작업을 해야 한다면 인터페이스를 이용해야 한다고 생각하면 된다. 이러한 의미에서 인터페이스는 일종의 객체의 계약서(스펙)라는 의미가 있다.


(2) 오늘 점심 뭐 먹지?: 스펙을 만족하는 객체

여러 객체 중에서 여러분이 원하는 기능을 가진 객체의 스펙을 만족하게 한다는 객체를 만들어주고자 하는 것이 인터페이스이다. 즉 어떤 객체가 여러분이 선택한 기준의 기능들을 다 구현하고 있다고 생각하면 된다.

따라서 인터페이스를 구현한다는 의미는 어떤 객체가 어떤 기준을 만족하게 한다는 의미로 해석할 수 있다.

(입사 기준에 만족하는 사람은 여러 명이 있을 수 있다. 실행활에서 어떤 기준을 만족하듯이 객체들이 어떤 기준을 만족하게 하는 장치가 바로 인터페이스의 용도 중의 하나이다.)


(3) 꿩 대신 닭: 현재 객체의 대체물

프로그래밍에서는 어떤 기준에 만족한 객체를 이용해서 프로그래밍을 만들다가 새로운 버전이나 다른 객체로 변경하는 일도 자주 일어난다. 이럴 때 기존의 메소드를 전면 수정하게 되면 결국은 모든 코드의 내용을 수정해야 하는 일이 발생한다.

인퍼페이스를 이용한다는 것은 인터페이스를 구현한 객체들을 하나의 부속품처럼 마음대로 변경해서 사용하는 것이 가능하다는 것이다.

자동차의 순정부품이 있긴 하지만, 때로는 다른 부품을 결합하기도 하는 것처럼, 인터페이스는 시스템이 확장되면서 발생하는 잦은 변경의 문제를 해결하는 데 사용한다.

--> 인터페이스를 코드에 적용하게 되면 실제 객체의 클래스를 몰라도 프로그램을 코딩할 수 있다. 따라서 더 유연한 프로그램을 설계할 수 있다.


(4) 호텔 떡볶이와 길거리 떡볶이: 전혀 다른 객체의 같은 기능

실제로 시내의 모 호텔에 갔더니 떡볶이를 파는 상황을 그려보자.

떡볶이는 길거리 음식이라고 생각했는데 버젓이 호텔의 메뉴판에 있는 것이다. 호텔 요리사와 노점에서 장사하는 사람, 이 두사람은 서로 다른 객체이지만 같은 기능을 할 수 있는 존재들이다. 즉 필요하다면 위에서 말한 하나의 부속품처럼 두 객체를 시스템에서 마음대로 사용할 수 있어야만 한다.

인터페이스는 하나의 기능에 대한 약속이기 때문에 중요한 점은 어떤 객체이든 간에 그 약속을 지키기만 한다면 필요한 곳에서 사용할 수 있게 한다는 것을 의미한다.


3. 문법으로 알아보는 인터페이스

(1) 인터페이스는 실제 객체를 의미하지 않는다.

인터페이스는 그 자체가 객체를 의미하지 않는다. 인터페이스라는 것은 결국은 어떤 객체가 할 수 있는 기능 자체를 의미하고, 그 기능을 하나의 스펙으로 모은 것에 불과하다. 따라서 인터페이스가 실제 기능을 가지는 것이 아니다. 즉 실제로 구현된 코드를 가지지 않았다는 것이다. 인터페이스는 실제로 구현된 코드 대신에 오로지 추상 메소드와 상수만을 가지고 있게 된다.


(2) 인터페이스의 상수는 private으로 만들 수 없다.

인터페이스는 실제 객체는 아니지만 서로 간의 약속으로 사용된다. 정해진 약속을 한 쪽에서 일방적으로 수정하게 되면 문제가 발생할 수 있다. 인터페이스는 객체와 객체의 중간에 놓이기 때문에 인터페이스에 선언하는 변수는 자동으로 'public static final'이 된다. 즉 완벽한 상수로 정의된다. 반면에 private는 객체를 만들 때 외부 클래스에서 접근할 수 없게 하려고 사용하기 때문에 외부로 공개되기 위해서 사용하는 인터페이스에는 맞지 않는다. 그래서 실제로 인터페이스에 private으로 시작하는 변수는 선언할 수 없다.


(3) 인터페이스에는 추상 메소드만 존재한다.

인터페이스는 실제 객체로 만들어지지 않는다. 즉 우리가 원하는 어떤 기능들을 모아서 하나의 인터페이스로 선언하는 것이 가장 일반적으로 사용되는 용도이기 때문에 인터페이스는 ‘기능의 묶음’아고 해석하는 것이 편리하다. 상속을 영어로 해석할 때 ‘is a relation'이라고 한다면, 인터페이스는 ’has a relation'으로 해석된다. 즉 어떤 객체가 어떤 인터페이스를 구현했다는 것은 인터페이스에서 정의한 기능들을 그 객체가 모두 구현해두었다는 것을 의미한다. 따라서 실제 객체는 모든 메소드를 구현했겠지만, 인터페이스 자체에는 메소드의 선언만 들어 있게 된다.


(4) 인터페이스는 객체의 타입으로만 사용된다.

인터페이스는 실제 객체로 사용되지는 않지만, 객체의 타입으로는 사용된다. 이 말은 상속에서와 같이 변수 선언 시에 객체의 타입으로 인터페이스를 사용할 수 있다는 것을 의미한다.

--> 인터페이스 a = new 인터페이스 구현 객체( );

이 경우에 컴파일러는 실제로 변수를 사용할 때 변수의 타입만을 보기 때문에 a라는 변수를 이용해서 객체의 메소드를 호출하는 작업을 실행하면 컴파일러는 인터페이스에 호출하려는 메소드가 있는지만을 따지게 된다.

상속에서 타입이 부모 클래스일 때 컴파일러가 부모 클래스에 선언된 메소드만 따지는 것과 같은 방식이라고 생각하자.

인터페이스 역시 객체의 타입으로 선언될 수 있기 때문에 컴파일러는 변수의 타입에 메소드가 존재하는지만 따지게 되고, 실제로 호출되는 것은 실행되고 있는 객체의 메소드가 호출된다.

cf.) implements은 어떤 기준을 구현한다고 보자


4. 인터페이스의 작성

인터페이스를 작성하는 것은 클래스를 작성하는 것과 같다. 다만 키워드로 class대신 interface를 사용한다는 것만 다르다. 그리고 interface에도 클래스와 같이 접근제어자로 public 또는 default를 사용할 수 있다.

interface 인터페이스 이름{ // 인터페이스의 선언

public static final 타입 상수이름 = 값; // 상수 선언부

public abstract 메서드 이름(매개변수 목록); // 추상 메소드 선언부

}


일반적인 클래스의 멤버들과 달리 인터페이스의 멤버들은 다음과 같은 제약사항을 가지고 있다.

- 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.

- 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.

cf.) 인터페이스에 정의된 모든 멤버에 예외없이 적용되는 사항이기 때문에 제어자를 생략할 수 있는 것이며, 편의상 생략하는 경우가 많다. 생략된 제어자는 컴파일 시에 컴파일러가 자동적으로 추가해준다.


interface PlayingCard{

public static final int SPADE =4;

final int DIAMOND = 3; // public static final int DIAMOND = 3;

static int HEART =2; // public static int HEART =2;

int CLOVER =1; // public static int CLOVER =1;

public abstract String getCardNumber();

String getCardKind(); // public abstract String getCardKind();

}


5. 인터페이스의 상속

인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와는 달리 다중상속, 즉 여러 개의 인터페이스로부터 상속을 받는 것이 가능하다.

cf.) 인터페이스는 클래스와는 달리 Object클래스와 같은 최고 조상은 없다.

interface Movable{

void move(int x, int y);// 지정된 위치(x ,y)로 이동하는 기능의 메서드

}

inferface Attackable{

void attack(Unit u);// 지정된 대상(u)을 공격하는 기능의 메서드

}

interface Fightable extends Movable, Attackable{   }

클래스의 상속과 마찬가지로 자손 인터페이스는 조상 인터페이스에 정의된 멤버를 모두 상속받는다. 그래서 Fightable 자체에는 정의된 멤버가 하나도 없지만 조상 인터페이스로 부터 상속받은 두 개의 추상메서드 move(int x, int y)와 attack(Unit u)을 멤버로 갖게된다.


6. 인터페이스의 구현

인터페이스도 추상클래스처럼 그 자체로는 인스턴스를 생성할 수 없으며, 추상클래스가 상속을 통해 추상메서드를 완성하는 것처럼, 인터페이스도 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야하는데, 그 방법은 추상클래스가 자신을 상속받는 클래스를 정의하는 것과 다르지 않다. 다만 클래스는 확장한다는 의미의 키워드 'extends'를 사용하지만 인터페이스는 구현한다는 의미의 키워드 implements'를 사용할 뿐이다.

cf.) 만일 구현하는 인터페이스의 메서드 중 일부만 구현한다면, 추상클래스로 선언되어야 한다.


class FighterTest {

public static void main(String[] args) {

Fighter f = new Fighter();


if (f instanceof Unit) {

System.out.println("f는 Unit클래스의 자손입니다.");

}


if (f instanceof Fightable) {

System.out.println("f는 Fightable인터페이스를 구현했습니다.");

}


if (f instanceof Movable) {

System.out.println("f는 Movable인터페이스를 구현했습니다.");

}


if (f instanceof Attackable) {

System.out.println("f는 Attackable인터페이스를 구현했습니다.");

}


if (f instanceof Object) {

System.out.println("f는 Object클래스의 자손입니다.");

}

}

}


class Fighter extends Unit implements Fightable {

public void move(int x, int y) { /* 내용 생략 */ }

public void attack(Unit u) { /* 내용 생략 */ }

}


class Unit {

int currentHP; // 유닛의 체력

int x; // 유닛의 위치(x좌표)

int y; // 유닛의 위치(y좌표)

}


interface Fightable extends Movable, Attackable { }

interface Movable { void move(int x, int y); }

interface Attackable { void attack(Unit u); }

실행결과)

f는 Unit클래스의 자손입니다.

f는 Fightable인터페이스를 구현했습니다.

f는 Movable인터페이스를 구현했습니다.

f는 Attackable인터페이스를 구현했습니다.

f는 Object클래스의 자손입니다.

인터페이스는 상속 대신 구현이라는 용어를 사용하지만, 인터페이스로부터 상속받은 추상메서드를 구현하는 것이기 때문에 인터페이스도 조금은 다른 의미의 조상이라고 할 수  있다.여기서 주의 깊게 봐두어야 할 것은 movable인터페이스에 정의된 void moveful(int x, int y)를 Fighter클래스에서 구현할 때 접근 제어자를 public으로 했다는 것이다.


7. 인터페이스 관련 주제


(1) 다중 구현은 다중 타입으로 선언할 수 있다.

클래스의 선언에 implements 인터페이스 구문이 나온다는 얘기는 해당 클래스가 인터페이스의 메소드들을 실제로 구현하고 있다는 선언이다.

클래스가 여러 개의 인터페이스를 구현한다는 것은 또 다른 의미로는 하나의 객체를 여러 가지의 타입으로 선언하는 것이 가능하다는 것을 의미한다.

즉 인터페이스도 타입으로 사용된다. ‘MyMp3 mp3 = new TonyMp3( );'에서처럼 어떤 클래스가 인터페이스를 구현하게 되면 변수 선언 시에 인터페이스를 타입으로 사용할 수 있다는 얘기이다. 클래스가 여러 개의 인터페이스를 구현하게 되면 결과적으로 변수의 타입으로도 다양하게 쓰일 수 있다는 것을 의미하게 된다.

ex.) Phone3G phone = new Phone3G( );

      VisualCall v1 = new VisualCall( );

      VoiceCall v2= new VoiceCall( );

--> 변수의 타입으로 보면 실제로 만들어지는 객체는 마찬가지로 Phone3G 이지만 VoiceCall 타입으로도, VisualCall 타입으로도 사용되는 것을 보실 수 있다.


(2) 상속과 인터페이스 차이(다중상속 관련)

상속은 구체적으로 구현된 메소드를 물려주기 때문에 다중 상속을 하게 되면 문제가 생긴다. 하지만, 인터페이스는 스펙(추상 메소드)만을 물려주기 때문에 여러 개의 인터페이스의 같은 메소드를 물려받아도 구현은 실제 구현 클래스 한 곳에서만 한다.

상속과 달리 인터페이스는 하나의 기준이면서 ‘~를 할 수 있는 기능을 가진 객체’의 의미로 해석될 수 있다. 인터페이스 자체가 하나의 클래스가 아니고, 그저 객체를 판단하는 기준이기 때문에 하나의 객체가 여러 가지 기능을 가질 때에는 클래스의 선언 뒤에 여러 개의 인터페이스를 ‘,’를 이용해서 사용할 수 있다.


(3) 인터페이스가 타입으로 쓰일 수 있기에 가능한 일들

인터페이스는 하나의 타입으로 선언되기 때문에 변수의 선언으로 가능한 모든 작업이 고스란히 가능해진다. 우리가 일반적으로 변수를 쓰는 경우는 다음과 같다.

1) 객체를 나중에 다시 사용하기 위해서 변수로 선언하는 경우

cf.) 인터페이스를 변수로 선언하는 경우

인터페이스로 변수를 선언하게 되면 사용하는 입장에서는 보이는 메소드는 인터페이스의 메소드들만 보이게 된다.

인터페이스 변수를 선언하게 되면 뒤에 오는 모든 객체는 간단히 인터페이스만 구현한 객체이면되기 때문에 실제로 new 뒤에 올 수 있는 클래스에 대한 제한이 없게 된다. 따라서 좀 더 시스템이 유연해지는 계기를 마련하게 된다.


2) 메소드의 파라미터나 메소드의 리턴 타입으로 사용하는 경우

메소드의 파라미터나 메소드의 리턴 타입으로 사용되는 경우에는 추상 클래스나 상속을 이용하는 것과 같아진다. 역시 이런 경우에는 상속과는 달리 전혀 무관하지만, 인터페이스에 선언된 기능을 가진 객체이면 되기 때문에 좀 더 확장성 있는 구조가 된다.


3) 배열이나 자료구조를 선언하기 위해서 사용하는 경우

배열이나 자료구조에서 인터페이스 타입으로 선언되는 경우에는 상속의 단점을 보완하는 방식의 설계가 가능해진다. 

--> 인터페이스를 이용하면 이런 작업들이 모두 가능해진다.

- 인터페이스는 전혀 다른 객체를 하나의 타입으로 인식할 수 있게 한다.


cr.)

1) 인터페이스가 실제 객체를 의미하는 것이 아니라 원하는 객체의 스펙을 의미하는 것이다.

2) 어떤 객체가 인터페이스를 구현함에 따라 여러 가지 타입으로 선언될 수 있다는 점이다.

--> 인터페이스 자체가 의미하는 것은 어떤 기능만을 의미하기 때문에 우리가 만든 여러 가지의 클래스에 원하는 인터페이스를 붙여주면 전혀 다른 객체이지만 같은 타입으로 인식할 수 있게 된다.


cp.) 개발하다 예상치 못한 새로운 기능이 추가되는 경우가 있다. 이렇게 전혀 다른 객체들의 공통적인 문제를 해결하는 데 있어서 인터페이스가 도움을 줄 수 있다.

-->  인터페이스는 하나의 타입으로 이전에 전혀 관계가 없는 클래스를 하나의 타입으로 볼 수 있게 한다. 따라서 전혀 엉뚱한 데이터를 가진 객체들을 하나의 자료구조와 같은 타입으로 묶어줄 수 있다.

1) 공통적인 기능만을 인터페이스로 정의해버린다.

가장 먼저 해야 할 일은 공통적인 기능을 인터페이스로 정의하는 것이다. 예를 들어 4가지의 클래스가 결국 매달 지급해야 하는 돈을 계산해주는 기능이 필요하다고 판단된다. 따라서 Ipayable

이라는 인터페이스를 정의하도록 한다.(4가지 클래스가 공통적인 기능을 갖는 인터페이스)

2) 공통 기능을 필요한 클래스가 정의하도록 클래스의 선언을 수정한다.

이제 필요한 클래스가 공통 기능을 할 수 있도록 수정해주어야 한다. 즉 전혀 관계없는 객체들을 공통된 기능을 구현한 객체로 볼 수 있게 한다는 것이다.

3) 인터페이스 타입으로 여러 종류의 객체를 처리할 수 있다.

4가지의 클래스가 동일한 인터페이스를 구현 했다면 전혀 관계가 없는 4가지 클래스의 객체들을 하나의 타입으로 볼 수 있도록 하는 중간 적인 역할이 필요할 때 사용된다.

ex.) IPayable[] arr = new Ipayable( );

    arr[0] = new RegularWorker( );

    arr[1] = new ContractWorker( );

    arr[2] = new PartTimer( );

    arr[3] = new RentalPay( );


(4) 인터페이스는 일종의 접근 제한 역할도 할 수 있다.

예를 들어 실제 MP3가 가진 기능은 playMovie( ), viewImage( ) 기능이지만, 외부에 인터페이스만 호출하게 되면 호출하는 쪽에서는 이 객체를 어떤 타입으로 보는지에 따라서 사용할 수 있는 메소드가 제한되나. 즉, PlayMovie m = new MP3(); 로 보면 실제로 MP3 객체가 가진 기능은 이미지를 보는 기능 viewImage( ) 와 동영상을 보는 기능인 viewImage( )이지만 변수의 선언에 의해서 사용할 수 있는 메소드는 playMovie( ) 만 보이게 된다. 이런 이유 때문에 클래스에 여러 가지 메소드를 만들어 둔 다음 인터페이스로 분리하는 작업을 진행하는 경우가 가끔 있다.

-->  A,B,C라는 인터페이스를 구현한 클래스를 반환할 때 A타입으로 변환하게 되면 외부에서는 A

인터페이스의 메소드만 보이게 된다. 따라서 별도의 접근 제한을 이용하지 않고도 접근 제한과 마찬가지 효과를 보게 하는 방법이다.


- 인터페이스는 다중 상속 문법도 된다.

인터페이스는 다중 구현을 통해서 하나의 객체가 여러 가지의 인터페이스를 구현할 수 있도록 하고 있다.

-->  인터페이스의 다중 상속은 여러 개의 스펙을 모아서 하나의 스펙을 만들어내는 일종의 조합방식이다.

-->  인터페이스의 다중 상속은 ‘스펙+스펙=새로운 스펙’으로 이해하자

만약 각각 한 가지씩의 기능을 가진 인터페이스를 하나의 제품의 스펙으로 규정하길 원할 때 다중상속을 사용한다.


ex.) interface PerfectPhone extends Camera, Mp3, DMB, Widget 

-->  이렇듯 새로운 규격을 만들어 주었다면 이제 PerfectPhone 하나만 물려받으면 끝난다.( 굳이 4가지의 인터페이스를 다 extends할 필요 없다.)


cf.) 인터페이스는 하나의 타입이나 규격일 뿐이지 그 자체가 하나의 객체가 되는 것이 아니다. 따라서 인터페이스의 상속은 클래스의 상속처럼 부모의 속성과 동작을 물려받는 것이 아니다. 인터페이스의 상속은 규격이나 스펙 자체 혹은 기능 자체의 선언을 물려받은 것이다. 규격이나 스펙을 물려받아서 새로운 스펙을 만든다면 기존 여러 개의 스펙을 조합해서 하나로 묶거나 기존의 스펙을 고스란히 물려받은 후에 다시 추가적인 기능을 가지게 하는 것이다.(extends는 상속이 아니다.)


cf.) 인터페이스에서 가장 중요한 사실은 인터페이스를 통해서 객체와 객체 간의 메시지를 주고받는다는 것이다.


(5) 인터페이스는 하나의 클래스에 여러 개를 붙여줄 수 있다.

인터페이스는 객테와 관련된 것이 아니라 객체가 할 수 있는 기능에 대한 정의이기 때문에 상속과는 달리 하나의 객체가 여러 가지의 기능의 스펙을 구현하는 것도 이상한 일이 아니다.

즉 java에서는 단일 상속만을 지원 했던 것과는 달리 하나의 객체는 여러 가지 종류의 기능 스펙인 인터페이스를 구현 할 수 있다.

ex.) 복합기가 프린트도 되고, 팩스도 되고, 복사도 되는 모습(하나의 객체가 여러 개의 기능 스펙을 구현)

복사기 a = new A기계( );

팩스 a = new A기계( );

프린트 a = new A기계( );

-->  하나의 객체를 여러 타입(모습)으로 보는 것이 다형성의 핵심이다.

-->  A 기계(객체)가 여러 가지의 인터페이스를 구현하는 형태이다.

-->  인터페이스 위주로 프로그램을 설계한다는 것은 만들어야 하는 기능에 대한 기준을 미리 잡는다는 것과 유사어 정도라고 생각된다.