1. 소개
추상화 는 객체 지향 프로그래밍의 핵심 기능 중 하나입니다. 단순한 인터페이스를 통해 기능을 제공함으로써 구현 복잡성을 숨길 수 있습니다. Java에서는 인터페이스 또는 추상 클래스 를 사용하여 추상화를 달성 합니다.
이 기사에서는 애플리케이션을 설계하는 동안 인터페이스를 사용해야 하는 경우와 추상 클래스를 사용해야 하는 경우에 대해 설명합니다. 또한 이들 간의 주요 차이점과 우리가 달성하고자 하는 것에 따라 선택할 수 있는 것입니다.
2. 클래스 대 인터페이스
먼저 일반 구체 클래스와 인터페이스의 차이점을 살펴보겠습니다.
클래스는 객체 생성을 위한 청사진 역할을 하는 사용자 정의 유형입니다. 개체의 상태와 동작을 각각 나타내는 속성과 메서드를 가질 수 있습니다.
인터페이스는 또한 클래스와 구문적으로 유사한 사용자 정의 유형입니다. 클래스를 구현하는 인터페이스에 의해 재정의될 필드 상수 및 메서드 서명 컬렉션이 있을 수 있습니다.
이 외에도 Java 8의 새로운 기능은 이전 버전과의 호환성을 지원하기 위해 인터페이스에서 정적 및 기본 메서드 를 지원합니다. 인터페이스의 메서드는 정적 이거나 기본 이 아니고 모두 public 인 경우 암시적으로 추상 입니다.
그러나 Java 9부터 인터페이스 에 개인 메서드 를 추가할 수도 있습니다 .
3. 인터페이스 대 추상 클래스
추상 클래스는 abstract 키워드를 사용하여 선언된 클래스일 뿐입니다. 또한 추상 키워드(추상 메서드)를 사용하여 메서드 서명을 선언하고 해당 하위 클래스에서 선언된 모든 메서드를 구현하도록 합니다. 클래스에 추상 메서드가 있는 경우 클래스 자체가 추상이어야 한다고 가정합니다.
추상 클래스에는 필드 및 메서드 수정자에 대한 제한이 없지만 인터페이스에서는 기본적으로 모두 공개됩니다. 추상 클래스에는 인스턴스 및 정적 초기화 블록이 있을 수 있지만 인터페이스에는 절대 가질 수 없습니다. 추상 클래스에는 자식 개체의 인스턴스화 중에 실행되는 생성자가 있을 수도 있습니다.
Java 8은 선언된 추상 메소드가 하나만 있는 인터페이스인 기능적 인터페이스 를 도입했습니다. 정적 및 기본 메서드 이외의 단일 추상 메서드가 있는 인터페이스는 기능 인터페이스로 간주됩니다. 이 기능을 사용하여 선언할 추상 메서드의 수를 제한할 수 있습니다. 추상 클래스에서는 추상 메소드 선언의 수에 대해 이러한 제한을 가질 수 없습니다.
추상 클래스는 어떤 면에서 인터페이스와 유사합니다.
- 우리는 둘 중 하나를 인스턴스화할 수 없습니다. 즉, new TypeName() 문 을 직접 사용하여 개체를 인스턴스화할 수 없습니다. 앞서 언급한 명령문을 사용한 경우 익명 클래스 를 사용하여 모든 메서드를 재정의해야 합니다.
- 둘 다 구현 여부에 관계없이 선언 및 정의된 메서드 집합을 포함할 수 있습니다. 즉, 인터페이스의 정적 및 기본 메서드(정의), 추상 클래스의 인스턴스 메서드(정의), 둘 다의 추상 메서드(선언)
4. 인터페이스를 사용하는 경우
인터페이스를 사용해야 하는 몇 가지 시나리오를 살펴보겠습니다.
- 다중 상속을 사용하여 문제를 해결해야 하고 다른 클래스 계층으로 구성된 경우
- 관련되지 않은 클래스가 인터페이스를 구현하는 경우. 예를 들어, Comparable 은 두 객체를 비교하기 위해 재정의할 수 있는 compareTo() 메서드를 제공합니다.
- 애플리케이션 기능을 계약으로 정의해야 하지만 누가 동작을 구현하는지에 대해서는 관심이 없는 경우. 즉, 타사 공급업체는 이를 완전히 구현해야 합니다.
문제가 "A는 [이 일을] 할 수 있다"라고 말할 때 인터페이스를 사용하는 것을 고려하십시오 . 예를 들어 "Clonable은 개체를 복제할 수 있습니다", "Drawable은 모양을 그릴 수 있습니다" 등입니다.
인터페이스를 사용하는 예를 살펴보겠습니다.
public interface Sender {
void send(File fileToBeSent);
}
public class ImageSender implements Sender {
@Override
public void send(File fileToBeSent) {
// image sending implementation code.
}
}
여기에서 Sender 는 send() 메서드가 있는 인터페이스입니다 . 따라서 "Sender는 파일을 보낼 수 있습니다"를 인터페이스로 구현했습니다. ImageSender 는 대상에 이미지를 보내기 위한 인터페이스를 구현합니다. 위의 인터페이스를 사용하여 VideoSender , DocumentSender 를 구현하여 다양한 작업을 수행할 수 있습니다.
위의 인터페이스와 구현된 클래스를 사용하는 단위 테스트 케이스를 고려하십시오.
@Test
void givenImageUploaded_whenButtonClicked_thenSendImage() {
File imageFile = new File(IMAGE_FILE_PATH);
Sender sender = new ImageSender();
sender.send(imageFile);
}
5. 추상 클래스를 사용하는 경우
이제 추상 클래스를 사용해야 하는 몇 가지 시나리오를 살펴보겠습니다.
- 코드에서 상속 개념을 사용하려고 할 때(많은 관련 클래스 간에 코드 공유), 하위 클래스가 재정의하는 공통 기본 클래스 메서드를 제공하여
- 요구 사항을 지정하고 구현 세부 정보가 부분적으로만 지정된 경우
- 추상 클래스를 확장하는 클래스에는 몇 가지 공통 필드 또는 메서드(비공개 수정자가 필요함)가 있습니다.
- 객체의 상태를 수정하기 위해 non-final 또는 non-static 메소드를 갖고 싶다면
우리의 문제가 "A is B"라는 증거를 만들 때 추상 클래스와 상속을 사용하는 것을 고려하십시오. 예를 들어 "개는 동물이다", "람보르기니는 차다" 등이다.
추상 클래스를 사용하는 예를 살펴보겠습니다.
public abstract class Vehicle {
protected abstract void start();
protected abstract void stop();
protected abstract void drive();
protected abstract void changeGear();
protected abstract void reverse();
// standard getters and setters
}
public class Car extends Vehicle {
@Override
protected void start() {
// code implementation details on starting a car.
}
@Override
protected void stop() {
// code implementation details on stopping a car.
}
@Override
protected void drive() {
// code implementation details on start driving a car.
}
@Override
protected void changeGear() {
// code implementation details on changing the car gear.
}
@Override
protected void reverse() {
// code implementation details on reverse driving a car.
}
}
위의 코드에서 Vehicle 클래스는 다른 추상 메서드와 함께 추상으로 정의되었습니다. 모든 실제 차량의 일반적인 작업을 제공하며 몇 가지 공통 기능도 있습니다. Vehicle 클래스 를 확장하는 Car 클래스 는 자동차 의 구현 세부 정보를 제공하여 모든 메서드를 재정의합니다("Car is a Vehicle").
따라서 우리는 차량 클래스를 추상으로 정의하여 기능을 자동차 및 버스와 같은 개별 실제 차량에서 구현할 수 있습니다. 예를 들어, 현실 세계에서 자동차와 버스를 시작하는 것은 결코 같지 않을 것입니다(각각 다른 구현 세부 정보가 필요함).
이제 위의 코드를 사용하는 간단한 단위 테스트를 고려해 보겠습니다.
@Test
void givenVehicle_whenNeedToDrive_thenStart() {
Vehicle car = new Car("BMW");
car.start();
car.drive();
car.changeGear();
car.stop();
}
6. 결론
이 기사에서는 인터페이스와 추상 클래스의 개요와 이들 간의 주요 차이점에 대해 설명했습니다. 또한 유연하고 깨끗한 코드를 작성하기 위해 작업에서 각각을 언제 사용해야 하는지 조사했습니다.
이 기사에 제공된 예제의 전체 소스 코드는 GitHub에서 사용할 수 있습니다 .