(객체part2 ) 10. 인터페이스(1)
인터페이스는 일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상메서드를 갖고 있지만 추상클래스보다 추상화 정도가 높아, 추상
클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다. 추상 클래스가 부분적으로만 완성된 '미완성 설계도' 라
한다면, 인터페이스는 밑그림만 그려져 있는 '기본 설계도'라 할 수 있다.
인터페이스의 제약 사항 :
- 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.
- 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다. (단 static 메서드와 디폴트 메서드는 예외(JDK 1.8부터))
인터페이스에 정의된 모든 멤버에 '예외없이' 적용되는 사항이기 때문에 제어자를 생략할 수 있는 것이며, 편의상 생략하는 경우가 많다.
생략된 제어자는 컴파일 시에 컴파일러가 자동적으로 추가해준다.
인터페이스의 특징
(1) 타입을 제외하고 항상 예외 없이 접근 제어자 생략이 가능하다.
interface PlayingCard {
public static final int SPADE = 4 ;
final int DIAMOND = 3; // public static final int DIAMOND
static int HEART = 2; // public static final int HEART
int CLOVER = 1; // public static final int CLOVER
(2) 인터페이스의 조상은 인터페이스만 가능 (Object가 최고 조상이 아니다.)
(3) 인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와는 달리 다중 상속이 가능하다. 즉 여러개의 인터페이스로부터 상속을
받는 것이 가능하다. (추상메서드랑 메서드의 선언부가 동일해서 충돌해도 문제가 없다. 어차피 구현부가 없으니까.. )
인터페이스의 구현
인터페이스도 추상클래스처럼 그 자체로는 인스턴스를 생성할 수 없으며, 추상클래스가 상속을 통해 추상메서드를 완성하는 것처럼,
인터페이스도 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야 하는데, 그 방법은 추상클래스가 자신을 상속받는 클래스
를 정의하는 것과 다르지 않다. 다만 extends 와 implements의 차이를 둘 뿐이다.
인터페이스에 정의된 추상 메서드를 구현하는 법
class 클래스이름 implements 인터페이스이름 {
// 인터페이스에 정의된 추상메서드를 모두 구현해야한다.
}
인터페이스를 이용한 다형성
자손클래스의 인스턴스를 조상타입의 참조변수로 참조하는 것이 가능하다는 것을 배운 것처럼 인터페이스 역시 이를 구현한 클래스의 조상
이라 할 수 있으므로, 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며 인터페이스 타입으로의
형변환 역시 가능하다.
인터페이스의 Fightable을 클래스 Fighter 가 구현했을 때, 다음과 같이 Fighter인스턴스를 Fightable 타입의 참조변수로 참조하는 것이
가능하다 .
리턴타입이 인터페이스지만, 메서드의 실제 리턴은 fighter 객체이다. 이것은 인터페이스의 다형성에 의해서 인터페이스를 구현한 자손클
래스가 리턴된 것이다. 위에서 작성했 듯, 인터페이스는 객체를 만드는 것이 불가능하기 때문에 리턴타입이 인터페이스라면 그 인터페이스
를 구현한 자손객체들만 리턴할 수 있다는 것을 알아두자. (실제로 Fighter f 객체가 반환됨. 참조변수 아님)
인터페이스의 장점
(1) 개발시간을 단축 시킬 수 있다.
일단 인터페이스가 작성되면 , 이를 사용패서 프로그램을 작성하는 것이 가능하다. 메서드를 호출하는 쪽에서는 메서드의 내용에 관계없이
선언부만 알면 되기 때문이다. 동시에 다른 한 쪽에서는 인터페이스를 구현하는 클래스를 작성하게하면 , 인터페이스를 구현하는 클래스가
작성될 때 까지 기다리지 않고도 양쪽에서 병렬로 동시에 개발을 진행할 수 있다.
(2) 표준화가 가능하다.
프로젝트에 사용되는 기본틀을 인터페이스로 짠 다음, 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 함으로써 일관되고
정형화된 프로그램이 가능하다 .
(3) 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.
서로 상속관계에 있지도 않고, 같은 조상클래스를 가지고 있지 않은 서로 아무런 관계도 없는 클래스들에게 하나의 인터페이스를 공통적으
로 구현하도록 함으로써 관계를 맺어줄 수 있다.
여기서 marine은 인간이고 , scv tank dropship은 기계이다. 위 처럼 기계를 수리하고 싶은 method를 만들고 싶을 때 ,
void repair(Tank t) {}
void repair(Dropship d) {}
일일이 유닛의 파라미터를 받아야 하니 다형성을 이용하려 한다. 하지만 다형성을 이용해서 scv와 tank클래스의 상위 클래스인
GroundUnit을 파라미터로 받자하니 Marine 클래스까지 상속받게 된다. 뿐만 아니라 dropshipd은 공중유닛이니 GroundUnit에 상속받
지 않아 난감하다. 이럴 경우처럼 아무 관계 없는 클래스들을 하나로 묶어주고 싶을 때 interface 를 사용하기 적절하다.
구현한 모습
tank, dropship 등은 고칠 수 있는 Repairable 인터페이스 한 곳에 모았다. 이제 scv클래스의 repair 메서드에서 Repairable을 구현한
클래스만 파라미터로 받을 수 있을 것이고, 이 유닛이 Unit2의 객체라면 Unit2로 형변환하여, hp를 회복하는 수순을 밟을 것이다.
(여기서 했던 실수.. tank 클래스에서 int hp를 한 번 더 선언하는 바람에 t1.hp를 출력하였을 때 Unit u2 리모콘으로 조정하였던 hp가 출
력되지 않고 t1객체의 hp 가 출력되어 애먹었다.... 실질적으로 출력해야하는 hp는 탱크라는 유닛의 hp이다. hp를 중복반복해서 생긴 문제..)
(4) 선언과 구현을 분리함으로써 , 유연한 캡슐을 만들 수 있다 .
예를 들어 A라는 클래스의 메서드가, B의 메서드를 직접 호출한다고 가정해보자. 그렇다면 우선 A의 객체를 생성한 후 b의 객체를 a의 메
서드에 집어넣어 b의 함수를 호출하는 순서가 될 것이다.
이런 관계를 A가 B에 의존한다고 말한다.
이 경우에 만약 의존 관계를 변경하고 싶을 때, class A의 method 파라미터 부분, 몸통 부분을 전부 수정해야한다. 예를 들어 c에 의존하고
싶을 때 , 파라미터도 C c , 메소드 호출도 c.methodC() 이런식으로 수정해야 할 것이다 .
하지만 이 경우에서 인터페이스라는 껍데기를 만들어 준다면
위와 달리 class A를 건들 필요 없이 메서드에서 interface를 구현한 자식 클래스의 객체만 생성해주면, B의 메서드 혹은 C의 메서드를
자유롭게 유동성 있게 호출해 줄 수 있다.