본문 바로가기

Programming/JAVA

인터페이스(4) - 인터페이스 구현 기본 원리

java logo image

 

 

 

인터페이스는 앞서 설명했듯이, 개발 코드에서 인터페이스 메서드를 호출하고 그 인터페이스가 객체의 메서드를 호출하는 구조입니다. 인터페이스는 중개자의 역할을 하기 때문에, 실제 구현 객체에서는 인터페이스에 정의된 추상 메서드와 동일한 이름과 파라미터와 리턴 타입을 갖는 실체 메서드를 가지고 있어야 하겠죠? 이는 추상 메서드에서도 배운 동일한 개념입니다.

 

여기서 인터페이스가 선언한 메서드의 실체를 갖고 있는 객체를 인터페이스 구현(implement) 객체라고 칭합니다. 당연히 이 구현 객체를 생성하는 클래스는 구현 클래스로 칭하게 되죠.

 

특정 인터페이스의 구현 클래스를 선언할 때는 implements 키워드를 추가하고, 인터페이스 명을 명시해야 합니다.

 

public class 구현클래스이름 implements 인터페이스이름 {
	// 인터페이스 추상 메서드의 실체 메서드 선언
}

 

 


 

 

그럼 지금부터는 간단한 인터페이스의 구현 형태에 대한 예제를 확인해 보도록 하겠습니다. 우선 인터페이스인 RemoteControl 부터 살펴보겠습니다.

 

public interface RemoteControl {
    public int MAX_VOLUME = 10;
    public int MIN_VOLUME = 0;
    
    public void turnOn();
    public void turnOff();
    public void setVolume(int volume);
    
    default void setMuete(boolean mute) {
        if(mute) {
            System.out.println("무음");
        } else {
            System.out.println("무음 해제");
        }
    }
    
    static void changeBattery() {
        System.out.println("배터리 교환");
    }
}

 

위 인터페이스에는 기본적으로 추상 메서드 세 개가 선언되어 있습니다. 이 세 개의 메서드는 구현 클래스에서 실체 메서드를 작성해 줘야 한다는 점을 다시 한번 기억합시다.

 

 

 

public class Television implements RemoteControl{
	
	// 필드
	private int volume;
	
	// turnOn() 추상 메서드의 실체 메서드
	public void turnOn() {
		System.out.println("스마트TV를 켭니다.");
	}
	
	// turnOff() 추상 메서드의 실체 메서드
	public void turnOff() {
		System.out.println("스마트TV를 끕니다.");
	}
	
	// setVolume() 추상 메서드의 실체 메서드
	public void setVolume(int volume) {
		if(volume > RemoteControl.MAX_VOLUME) {
			this.volume = RemoteControl.MAX_VOLUME;
		} else if(volume < RemoteControl.MIN_VOLUME) {
			this.volume = RemoteControl.MIN_VOLUME;
		} else {
			this.volume = volume;
		}
		
		System.out.println("현재 스마트TV 볼륨: " + volume);
	}
}

 

위 Television 클래스는 RemoteControl 인터페이스를 구현하는 구현 클래스입니다. 인터페이스에 선언된 세 개의 추상 메서드를 구현했습니다. 

 

 

public class Audio implements RemoteControl {
	// 필드
	private int volume;
	
	// turnOn() 추상 메서드의 실체 메서드
	public void turnOn() {
		System.out.println("스피커를 켭니다.");
	}
	
	// turnOff() 추상 메서드의 실체 메서드
	public void turnOff() {
		System.out.println("스피커를 끕니다.");
	}
	
	// setVolume() 추상 메서드의 실체 메서드
	public void setVolume(int volume) {
		if(volume > RemoteControl.MAX_VOLUME) {
			this.volume = RemoteControl.MAX_VOLUME;
		} else if(volume < RemoteControl.MIN_VOLUME) {
			this.volume = RemoteControl.MIN_VOLUME;
		} else {
			this.volume = volume;
		}
		
		System.out.println("현재 스피커 볼륨: " + volume);
	}
}

 

Audio 클래스 역시 동일한 방식으로 RemoteControl 인터페이스를 구현합니다. 

 

자, 여기서부터는 조금 복잡하고 헷갈리는 부분입니다. 이제 실행 클래스에서 RemoteControl 인터페이스를 구현한 클래스 - 즉, Television과 Audio 클래스를 사용해야 합니다. 흔히 생각하기로는 일반적인 방식으로 new를 사용해 각각의 클래스를 사용하면 될 것이라고 생각하고 계시죠?

 

하지만 아닙니다.

// 이러한 형태로 사용하지 않습니다.
Television tv = new Television();

 

 

인터페이스 구현 클래스를 사용할 때는, "인터페이스 타입"의 변수를 생성하고, 그 구현 인스턴스 객체를 대입하는 형태로 사용해야 합니다. 

 

public class ExampleMain {	
	public static void main(String[] args) {
		RemoteControl rc;
		
		rc = new Television();
		rc = new Audio();
	}
}

 

 

우리가 배웠던 상속 클래스의 개념을 여기에 좀 덧대어 설명해 볼까요? Television 클래스와 Audio 클래스가 RemoteControl 인터페이스를 상속받았다고 비유해 보겠습니다. 그럼, 여기서 구현 클래스 Television과 Audio는 반드시 ! 강제적으로 인터페이스에서 선언된 추상 메서드를 오버라이드 해서 사용하는 개념이 됩니다. 그런데, 위 예제에서 rc라는 변수는 Television과 Audio라는 구현(자식?)클래스 타입의 인스턴스가 대입되었습니다. 

 

클래스의 다형성에서 배웠던 내용을 떠올려봅시다. 그럼, 위 rc 변수(객체)를 통해서 사용할 수 있는 메서드는 인터페이스(부모?)에서 정의된 메서드만을 사용해야 하는 상황이 되지요. 물론, 각 클래스에서 오버라이드 한 메서드로 실행되는 것도 기억하셔야겠죠? 

 

결국 인터페이스를 구현하는 형태로 구현 클래스를 사용하게 되면, "강제로 인터페이스의 추상 메서드를 구현해야 하며" / "인터페이스의 메서드만을 사용하는 형태"가 되는 원칙이 자동으로 만들어지는 것입니다. 

 

물론 상세한 내용, 그리고 인터페이스의 다중 사용과 구현 클래스의 상속 시 여러가지 사용 원칙이 있겠지만 자세한 케이스는 다음 아티클에서 계속 다루어 보겠습니다. 일단 여기까지 읽어보셨다면 인터페이스를 사용함으로써 어떤 원칙과 강제성이 생기는지는 이해가 되셨을 것입니다. 이제 슬슬, 인터페이스라는 녀석을 왜 쓰는지 감이 잡히시죠?

 

 

[추가]

1. 만일 구현 클래스에서, 추상 메서드 세 개 중에서 일부만 구현한다면 - 일단은 컴파일 에러가 발생하고, 일부만 구현하는 상황이라면 해당 클래스는 자동으로 추상 클래스가 됩니다. 즉, 클래스에 abstract 키워드를 추가해줘야 합니다. 

 

2. 이클립스에서 Override/Implement Methods....] 메뉴를 사용하면 추상 메서드를 골라 자동으로 실체 메서드가 생성되며 @Override 키워드가 자동으로 붙습니다. 생략해도 좋지만, 안전성을 위해 붙이기도 합니다.