[Java] 추상클래스와 인터페이스 _ 그래서, 둘의 차이가 뭔데?

Jake·2022년 3월 24일
0

Java

목록 보기
2/8
post-custom-banner

기술면접 단골질문인 추상 클래스와 인터페이스의 차이를 알아보고자 합니다.

1. 추상클래스

추상클래스의 특징

  • 추상클래스는 클래스 선언부에 abstract을 붙여서 정의한다.
  • 추상클래스는 추상메서드를 가질 수 있다
  • 추상클래스는 인스턴스를 생성할 수 없다.

Note. 추상메서드는 꼭 미완성이다?

  • 위와 같은 특성들 때문에 추상클래스는 미완성 설계도에 자주 비유되곤 합니다. 미완성 설계도로 완성된 제품을 생산할 수 없는 것 처럼, 추상클래스도 인스턴스를 생성할 수 없기 때문입니다.
  • 하지만 모든 추상클래스가 미완성 클래스인 것은 아닙니다. 완성된 클래스(추상메서드가 없는 클래스)라고 하더라도 클래스 선언부에 abstract만 붙어있다면 추상 클래스가 됩니다.
  • Q. 완성된 클래스에 왜 굳이 abstract를?
    A. 조상클래스의 인스턴스가 생성되는 것을 방지하기 위해서입니다. 협업 상황에서 개발자가 의도하지 않은 상황을 미연에 방지할 수 있다는 의의가 있습니다.

추상클래스의 완성

  • 추상클래스를 상속하는 자손클래스는, 추상클래스의 모든 추상메서드를 오버라이딩(Override) 하여 구현해야 합니다.
  • 이처럼 추상메서드는 자손클래스에서 특정 기능을 구현하도록 강제하는 기능을 합니다.

Note. 어차피 자손클래스에서 기능 구현을 강제할거면, 그냥 조상클래스에서 구현하면 되지 않나요?

  • 같은 이름의 함수라고 하여도, 자손클래스마다 실제 구현은 다르게 동작해야 할 수도 있기 때문입니다.
  • 또한 조상클래스에서 추상메서드를 선언하는 추상화를 통해, 강제성과 동시에 다형성에서 이점을 얻을 수 있습니다.

Note. 다형성

스타크래프트, 한 번씩은 해보셨을 것이라 생각합니다(아니라면 죄송합니다).
저는 테란을 주로 했는데, 마린만 선택해서 부대지정을 하면 스팀팩을 쓸 수 있었지만
마린과 메딕을 함께 지정해두면 스팀팩을 쓸 수 없었습니다.
Java 코드로 게임을 구현한다고 하면 다음과 같지 않을까요?

abstract class 유닛 {
	abstract void move();
    abstract void commandA(); //단축키 A가 마린에게는 공격, 메딕에게는 힐이었습니다.
}
class 마린 extends 유닛 {
	void move() {//이동 구현};
    void commandA() {//총으로 공격};
    void steamPack() {//스팀팩 사용}
}
class 메딕 extends 유닛 {
	void move(); {//이동 구현}
    void commandA() {//힐};
}

이렇게 유닛 클래스에 move()와 commandA() 추상메서드를 선언해두면 다음과 같은 기능을 사용할 수 있습니다.

유닛[] units = {마린1, 마린2, 메딕};
for(유닛 unit : units} {
	unit.move();
    unit.commandA();
    unit.steamPack();//Compile Error. Unit doesn't have method steamPack()
}

조상클래스인 유닛에 move()와 commandA()를 선언하지 않았다면, 마린과 메딕을 한 번에 선택해서 움직이거나 A키로 공격/힐을 지시할 수 없었겠죠. 스팀팩처럼요. 스팀팩은 마린 클래스에만 구현되어 있기 때문에, 마린만 묶었을 때 사용할 수 있습니다.

마린[] marines = {마린1, 마린2};
for(마린 marine : marines} {
	marine.move();
    marine.commandA();
    marine.steamPack();//OK
}

2. 인터페이스

인터페이스의 특징

  • 맴버변수를 가질 수 없다 (상수만을 가질 수 있다)
  • 다중상속이 가능하다 (그러나 인터페이스의 진정한 장점이 다중상속은 아니다)

인터페이스는 추상클래스와 달리 일반 메서드나 맴버변수를 일체 가지지 못했었는데, JDK 1.8 이후로 디폴트 메서드 기능이 추가되면서 일반 메서드, static 메서드는 가질 수 있게 되었습니다.

인터페이스의 장점

남궁 성님의 자바의 정석에서는 인터페이스의 장점을 다음과 같이 정의하고 있습니다
1. 개발시간을 단축시킬 수 있다
2. 표준화가 가능하다
3. 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다
4. 독립적인 프로그래밍이 가능하다

또한 인터페이스는 다형성도 지원합니다.

interface Rollable {
	void roll();
}

class Bike {
	@Override
	void roll() {}
}

위와 같은 예시에서, 다음과 같은 것들이 가능합니다.

public class Main {
	void ride(Rollable r) {
    	r.roll();
    }
    
    Rollable makeRollabe() {
    	return new Bike();
    }
}

Note. 즉, 인터페이스에서의 다형성은 다음과 같은 기능을 지원합니다 (아래 두 사항 모두 자바의 정석에서 매우 강조되고 있습니다)

  • 매개변수의 타입이 될 수 있다 (이 경우, 해당 인터페이스를 구현한 클래스의 인스턴스만 매개변수가 될 수 있다)
  • 매개변수의 리턴 타입이 될 수 있다 (이 경우, 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다)

3. 그래서 둘의 차이는 무엇인가?

  • 추상클래스는 다중상속이 불가능하고, 인터페이스는 가능하다는 말은 물론 옳은 말이지만, 뭔가 아쉬운 대답입니다.
  • 추상클래스는 일반 메서드를 가질 수 있고, 인터페이스는 불가능하다는 말은 JDK 1.8 이후로 옳지 않은 대답이 되었습니다.

Extend와 상속

Extends의 의미는 확장. 즉, 상속은 곧 확장입니다.
OCP 원칙에 따라 객체 지향 설계는 확장에는 열려있고 변경에는 닫혀있어야 합니다.
자손클래스는 조상클래스의 기능을 상속하고, 필요한 기능들을 추가로 구현합니다.

Implement와 구현

인터페이스의 구현은 상속관계의 영향을 받지 않습니다. 전혀 연관관계가 없는 클래스라고 하더라도, 동일한 인터페이스를 구현하도록 하여 같은 기능을 갖고 있음을 보장할 수 있습니다.

즉 인터페이스의 근본적 의의는, 인터페이스는 상속에서 지원하지 않는 다중상속을 지원함이 아니라
전혀 연관관계가 없는 클래스라 하더라도 동일한 기능을 구현함을 보장하는 것에 있다고 정리할 수 있겠습니다.

또한 인터페이스는 선언과 구현을 분리할 수 있도록 해줍니다.

또한 인터페이스를 통해 우리는 구체화에 의지하지 않고 추상화에 의지하여 프로그래밍을 할 수 있게 됩니다. 메서드를 호출하는 쪽은 메서드의 선언부만 알면 됩니다. 내용은 알 필요가 없는 것이지요. 이를 통해 우리는 객체 지향 설계를 이어나갈 수 있게 됩니다.
그리고 이는 객체 지향 설계 5대 원칙 중 하나인 DIP(의존관계 역전 원칙)를 지킬 수 있도록 해줍니다.

interface AnimalIntegerface {
	void sound();
}
public listenAnimalSound(AnimalInterface i) {
	i.sound(); 
}

listenAnimalSound() 메서드는 sound() 메서드의 구현을 알 필요가 없습니다. 이를 통해 listenAnimalSound() 메서드는 sound() 메서드의 변경에 전혀 영향을 받지 않게 됩니다. 이것이 선언과 구현을 분리함으로서 얻을 수 있는 장점입니다.
심지어는 AnimalInterface를 실제로 구현한 클래스가 아직 없어도 listenAnimalSound() 메서드를 코딩할 수 있습니다!!

둘의 공통점

추상클래스와 인터페이스 모두

  • 특정 기능을 강제하고
  • 다형성을 획득할 수 있도록 합니다.
profile
Java/Spring Back-End Developer
post-custom-banner

0개의 댓글