인터페이스는 코드로만 이해하려고 하면 모호하고 이상하게 머리에 잘 안 들어온다. 그러나 포기는 금물. 어떤 개념이라도 예시를 들면 비교적 쉽게 이해가 된다. 이 글에서는 내가 남은 일생동안(?) 더이상 헷갈리지 않기 위해 기억에 남을 만한 예제를 만들어 작성해보려고한다.
1. 인터페이스는 왜 필요한가?
나는 뮤지컬 공연장의 티켓판매를 맡고 있다.
관객이 예매내역을 보여주면 나는 티켓을 준다.
인터파크 예매내역이면 인터파크 티켓을 준다.
예스24 예매내역이면 예스24 티켓을 준다.
이러한 케이스를 코드로 담아보자. 일단 Ticket, Interpark, Yes24, Manager 클래스를 작성하자.
class Ticket {
String name;
void setName(String name) {
this.name = name;
}
}
class Interpark extends Ticket {
}
class Yes24 extends Ticket {
}
class Manager {
void ticketing(Interpark interpark) {
System.out.println("ticketing interpark");
}
void ticketing(Yes24 yes24) {
System.out.println("ticketing yes24");
}
}
public class Sample {
public static void main(String[] args) {
Manager manager = new Manager();
Interpark interpark = new Interpark();
Yes24 yes24 = new Yes24();
manager.ticketing(interpark);
manager.ticketing(yes24);
}
}
Manager 클래스의 ticketing 메서드처럼 입력값의 자료형 타입이 다를 경우(위에서는 Interpark, Yes24로 서로 다르다) 메서드 명을 동일하게(여기서는 메서드명이 ticketing으로 동일하다) 사용할 수 있다. 이런것을 메서드 오버로딩(Method overloading)이라고 한다.
티켓 예매처가 인터파크와 예스24뿐이라면 Manager 클래스는 더 이상 할일이 없겠지만 티켓링크, 멜론티켓 등이 계속 추가된다면 Manager는 예매처가 추가될 때마다 매번 ticketing 메서드를 추가해야 한다. 바로 이런 어려움을 극복하기 위해서 인터페이스의 도움이 필요하다.
2. 인터페이스 작성하기
다음과 같이 코드 상단에 티켓예매처(TicketOffice) 인터페이스를 추가하자.
인터페이스는 클래스와 마찬가지로 Sample.java와 같은 단독파일로 저장하는 것이 일반적인 방법이다. 여기서는 설명의 편의를 위해 Sample.java 파일 최상단에 인터페이스를 작성한다.
interface TicketOffice {
}
.
.
.
class Interpark extends Ticket implements TicketOffice{
}
class Yes24 extends Ticket implements TicketOffice{
}
Interpark, Yes24 클래스는 작성한 인터페이스를 구현(implements)하도록 수정한다.
이렇게 인터페이스를 구현하게 되면 Manager 클래스의 ticketing 메서드를 변경할 수 있다.
class Manager {
void ticketing(TicketOffice ticketoffice) {
System.out.println("ticketing interpark");
}
}
ticketing 메서드의 입력으로 Interpark, Yes24를 각각 필요로 했지만 이제 이것을 TicketOffice라는 인터페이스로 대체할 수 있게 되었다. interpark, yes24는 각각 Interpark, Yes24의 객체이기도 하지만 TicketOffice 인터페이스의 객체이기도 하기 때문에 위와같이 TicketOffice를 자료형의 타입으로 사용할 수 있는 것이다. 상속에서 공부했던 IS-A 관계가 인터페이스에도 마찬가지로 적용된다. "Interpark is a TicketOffice", "Yes24 is a TicketOffice"가 성립된다.
- interpark - Interpark 클래스의 객체, TicketOffice 인터페이스의 객체
- yes24 - Yes24 클래스의 객체, TicketOffice 인터페이스의 객체
이와같이 객체가 한 개 이상의 자료형 타입을 갖게되는 특성을 다형성이라고 한다.
이제 어떤 예매처가 추가되더라도 Manager는 ticketing 메서드를 추가할 필요가 없다. 다만 예매처가 추가 될 때마다 다음과 같이 TicketOffice 인터페이스를 구현한 클래스를 작성하기만 하면 되는 것이다.
구현체는 늘어날 수 있지만 인터페이스는 하나이기 때문에, 인터페이스를 기준으로 중요 클래스를 작성해야만 한다.
3. 인터페이스의 메서드
그런데 위의 Manager 클래스에 약간 문제가 발생했다. 어떤 내역서를 내밀던지 무조건 ticketing interpark를 출력하고 있다. yes24 내역서면 어떡하려고!!! 그래서 인터페이스를 또 활용해보자. TicketOffice 인터페이스에 다음과 같이 메서드를 추가한다.
interface TicketOffice {
String getTicket();
}
.
.
.
인터페이스의 메서드는 메서드의 이름과 입출력에 대한 정의만 있고 그 내용은 없다. 그 이유는 인터페이스는 규칙이기 때문이다. 위에서 설정한 getTicket라는 메서드는 인터페이스를 implements한 클래스들이 구현해야만 하는 것이다.
인터페이스에 위처럼 메서드를 추가하면 Interpark, Yes24 등의 TicketOffice 인터페이스를 구현한 클래스들에서 컴파일 오류가 발생할 것이다. 오류를 해결하려면 다음처럼 Interpark, Yes24클래스에 getTicket 메서드를 구현해야 한다.
인터페이스의 메서드는 항상 public으로 구현해야 한다.
class Interpark extends Ticket implements TicketOffice{
public String getTicket() {
return "interpark";
}
}
class Yes24 extends Ticket implements TicketOffice{
public String getTicket() {
return "yes24";
}
}
이제 Manager 클래스도 다음과 같이 변경이 가능하다.
class Manager {
void ticketing(TicketOffice ticketoffice) {
System.out.println("ticketing"+ticketoffice.getTicket());
}
}
ticketing 메서드가 "ticketing apple" 을 출력하던 것에서 "ticketing "+ticketoffice.getTicket()를 출력하도록 변경되었다. ticketoffice.getTicket()를 호출하면 Manager 인터페이스를 구현한 구현체(Interpark, Yes24)의 getTicket() 메서드가 호출된다.
4. 인터페이스의 핵심과 개념
이 글에서 가장 중요한 목표는 왜 인터페이스가 필요한지에 대해서 이해하는 것이다.
티켓예매처의 종류만큼 ticketing 메서드가 필요했던 Manager 클래스를 TicketOffice 인터페이스를 통해 구현했더니 단 한개의 ticketing 메서드로 구현이 가능해졌다. 메서드의 갯수가 줄어든 것보다 중요한 점은 Manager 클래스가 예매처 종류에 의존적인 클래스에서 예매처 종류와 상관 없는 독립적인 클래스가 되었다는 점이다. 바로 이 점이 인터페이스의 핵심이다.
이번에는 좀 더 개념적으로 인터페이스를 알아보자. 지금 눈 앞에 맥북이 있다. 맥북에는 C타입 포트가 있고 C타입 케이블로 다양한 것을 연결하여 사용할 수 있다. 이때 C타입 포트가 물리적 세계의 인터페이스라고 할 수 있다. C타입의 규격만 알면 어떤 기기도 만들 수 있다. 또 맥북은 C타입 포트만 제공하고 어떤 기기가 만들어지는 지 신경쓸 필요가 없다. 즉, 의존적이지 않다. 이 점이 인터페이스와 매우 비슷하다.
또한 인터페이스는 인터페이스의 메서드를 반드시 구현해야 하는 강제성을 갖는다는 점을 반드시 기억하자. (상속과 다른 점)
5. 디폴트 메서드
자바8 버전 이후부터는 디폴트 메서드(default method)를 사용할 수 있다. 인터페이스의 메서드는 몸통(구현체)을 가질 수 없지만 디폴트 메서드를 사용하면 실제 구현된 형태의 메서드를 가질 수 있다.
예를 들어 TicketOffice 인터페이스에 다음과 같은 디폴트 메서드를 추가할 수 있다.
interface TicketOffice {
String getTicket();
default void printTicket() {
System.out.printf("my Ticket is %s\n", getTicket());
}
}
.
.
.
디폴트 메서드는 메서드명 가장 앞에 "default" 라고 표기해야 한다. 이렇게 TicketOffice 인터페이스에 printTicket 디폴트 메서드를 구현하면 TicketOffice 인터페이스를 구현한 Interpark, Yes24 등의 실제 클래스는 printTicket 메서드를 구현하지 않아도 사용할 수 있다. 그리고 디폴트 메스드는 오버라이딩이 가능하다. 즉, printFood 메서드를 실제 클래스에서 다르게 구현하여 사용할수 있다.
6. 스태틱 메서드
자바8 버전 이후부터는 인터페이스에 스태틱 메서드(static method)를 사용할 수 있다. 인터페이스에 스태틱 메서드를 구현하면 인터페이스명.스태틱메서드명 과 같이 사용하여 일반 클래스의 스태틱 메서드를 사용하는 것과 동일하게 사용할 수 있다.
예를들어 TicketOffice 인터페이스에 다음과 같은 스태틱 메서드를 추가할 수 있다.
interface TicketOffice {
String getTicket();
default void printTicket() {
System.out.printf("my Ticket is %s\n", getTicket());
}
int SEAT_COUNT = 4; // 인터페이스 상수는 자동으로 public static final이 적용
static int discount() {
return SEAT_COUNT * 5;
}
}
.
.
.
이렇게 스태틱 메서드를 추가하면 다음과 같이 사용할수 있다.
TicketOffice.discount();
참고:
'💻dev > 🌱Java+Spring' 카테고리의 다른 글
JPA | 엔티티의 생명주기와 영속성 컨텍스트를 알아보자 (0) | 2023.04.27 |
---|---|
JPA | JPA란 무엇인가? (0) | 2023.04.27 |
Spring | 인프런 김영한님의 스프링 입문 강의 정리 - Test 편 (0) | 2023.04.07 |
Java+Spring | 자바 빈(Java Beans)과 스프링 빈(Spring Bean)에 대해 알아보기 (0) | 2023.04.06 |
Spring | @ResponseBody, @RequestBody 어노테이션 알아보기 (0) | 2023.04.06 |