Java

[JAVA] 추상 클래스 인터페이스 차이점 / abstract class interface 사용법

빡새 2022. 3. 19. 00:29


Java 에서 interface 와 abstract class 는 어떻게 다를까?
공통점과 차이점을 살펴보자.

- 1. 인터페이스와 추상 클래스 간의 공통점 -

먼저 공통점은 '강제성'이다.
무엇을 강제하는가?
'구현'을 강제한다.
인터페이스와 추상 클래스가 갖는 공통점은 해당 클래스를 상속 받은 서브 클래스에게 추상 메소드를 구현하도록 강제한다는 점이다.

예를 들어서, 먼저 인터페이스를 보자.
탈것 아래에 자동차가 있다.
interface Vehicle에서 move()를 만들어 두었다고 하자.
move()는 추상 메소드다.
그러면 그 Vehicle 인터페이스를 상속 받은 Car 클래스에서는 move() 메소드를 꼭 구현해야 한다.

추상 클래스도 마찬가지다.
동물 아래에 강아지가 있다.
abstract class Animal에서 eat()을 만들어 두었다고 하자.
마찬가지로 eat()은 추상 메소드다.
그러면 그 Animal 추상 클래스를 상속 받은 Dog 클래스에서는 eat() 메소드를 꼭 구현해야 한다.


- 2. 인터페이스와 추상 클래스 간의 차이점 -

그렇다면 차이점은 무엇일까?
목적이 다르다.
인터페이스를 쓰는 목적은 '통일성'이다.
추상 클래스를 쓰는 목적은 '확장성'이다.
인터페이스와 추상 클래스는 서브 클래스로 하여금 구현을 강제한다는 공통점이 있지만,
왜 그런 구현을 강요하는지 그 목적은 조금씩 다르다.

 

    - 1) 인터페이스

먼저 인터페이스를 보자.
인터페이스를 만드는 목적은 '통일성'이라고 했다.
아까 들었던 예시를 이어가자면,
탈것 아래에 자동차, 자전거, 비행기, 기차 등 이것저것 많다.
내가 당장 어떤 '탈것'을 이용하게 될지는 모르지겠만,
일단 '탈것'이라고 하면 나를 어딘가로 데려다준다는 기능을 수행한다는 공통점이 있다.
내가 서울에서 부산까지 가는 데에 자동차를 타든, 기차를 타든, 비행기를 타든,
이동()라는 기능을 수행한다는 보장을 받아야 한다.
이럴 때 쓰려고 만든 기능이 인터페이스다.

먼저 '탈것'이라는 interface를 만든다.
그 인터페이스 안에 이동()이라는 메소드를 만들어 둔다.
이렇게 만들어 둔 탈것 인터페이스를 
자동차 클래스, 기차 클래스, 비행기 클래스 등이 상속 받도록 만든다.
그러면 자동차 클래스도, 기차 클래스도, 비행기 클래스도,
이 이동() 메소드를 꼭 구현해야만 한다.
물론 이동하는 방법은 각자 다르므로 메소드를 구현하는 방식도 모두 다를 것이다.
하지만 이동()이라는 메소드를 실행할 수 있다는 공통점은 보장 받을 수 있다.

만약 main() 메소드 안에서 move(서울, 부산) 이라는 기능을 수행한다고 해보자.
서울에서 부산까지 가는 기능이다.
그런데 사용자에 따라서 자동차를 타고 갈지, 기차를 타고 갈지, 비행기를 타고 갈지 아직 모른다.
이때, main() 메소드 안에서는 자동차를 부르든, 기차를 부르든, 비행기를 부르든 기능상에 문제는 없다.
자동차.move(서울, 부산) / 기차.move(서울, 부산) / 비행기.move(서울, 부산)
모두 '탈것' 이라는 인터페이스를 상속 받았기 때문이다.
그때그때 여건에 따라서 바꿔가며 쓸 수 있다.
반대로 말하면, 어떤 교통 수단을 이용하게 될지 모르는 상황이더라도 main() 메소드를 구현하는 데에 크게 문제가 되지 않는다.
'탈것' 인터페이스를 상속 받은 클래스들 간에 '통일성'이 보장되는 덕분이다.

 

    - 2) 추상 클래스

다음은 추상 클래스를 보자.
추상 클래스를 만드는 목적은 '확장성'이라고 했다.
앞에서 '동물'과 '강아지' 예시를 들었다.
모든 '동물'이 갖는 공통점은 많지 않다.
먹는 것, 자는 것, 번식하는 것 등등.
'동물' 아래에 있는 '강아지'는 이 공통점들을 모두 가지고 있다.
동시에, 모든 '강아지'들이 갖는 특징도 있을 것이다.
짖는 것, 꼬리를 흔드는 것, 냄새를 맡는 것 등등.
'동물' 아래에 '강아지' 아래에 '푸들'을 보자.
'푸들'은 모든 '동물'이 갖는 공통점을 갖는 동시에,
모든 '강아지'가 갖는 공통점을 갖는다.
동시에, 모든 '푸들'이 갖는 특징도 있을 것이다.
털이 곱슬 거리는 것, 똑똑한 것, 슬개골이 약한 것 등등.
그러면 '동물' 아래에 '강아지' 아래에 '푸들' 아래에 '토이 푸들'을 보자.
아니다, 그냥 여기까지만 보자.
상위 개념에서 하위 개념으로 갈수록 공통점이 쌓인다.
이럴 때 쓰려고 만든 기능이 추상 클래스다.

먼저 '동물'이라는 abstract class를 만든다.
그 추상 클래스 안에 먹는다()는 메소드를 만들어 둔다.
이렇게 만들어 둔 동물 클래스를
강아지, 고양이, 새가 상속 받도록 만든다.
그러면 강아지 클래스도, 고양이 클래스도, 새 클래스도,
이 먹는다() 메소드를 구현해야만 한다.
당연히 먹는 방식이 모두 다르므로 구현 내용은 각자 다를 것이다.

'동물'이라는 개념을 상속 받아서, '강아지'나 '고양이', '새' 등의 개념으로 확장했다.
또 '동물'이라는 개념을 상속 받은 '강아지'라는 개념을 상속 받아 '푸들', '리트리버', '진돗개' 등의 개념으로 확장했다.
또 '동물'이라는 개념을 상속 받은 '강아지'라는 개념을 상속 받은 '푸들'이라는 개념을 상속 받아 '토이 푸들', '미디엄 푸들', '스탠다드 푸들' 등의 개념으로 확장했다.
기능의 '통일성'을 보장하는 목적으로 사용하던 인터페이스와는 조금 다르다.
강아지.eat() / 고양이.eat() / 새.eat() 
이들의 통일된 기능을 보장하기 위한 목적은 아니다.
'동물'이 갖는 특징은 기본적으로 가져가면서 그 위에 '강아지'라는 개념을 쌓는다.
'동물'이 갖는 특징과 '강아지'가 갖는 특징을 바탕으로 가져가면서, 그 위에 '푸들'이라는 개념을 쌓는다.
'동물', '강아지', '푸들'이 갖는 모든 특징을 기본적으로 가져가면서, 그 바탕 위에 '토이 푸들'이라는 개념을 쌓는다.
추상 클래스는 상위 개념을 하위 개념이 상속 받고, 거기에 살을 덧붙여서 개념을 확장하는 데에 목적이 있다.


interface와 abstract class 간의 공통점과 차이점을 살펴봤다.
아마 말장난 같고 이해하기 어려울 수도 있다.
정상이다.
왜냐하면 기능상에는 사실 상호보완적인 요소가 많기 때문이다.
뉘앙스, 느낌적인 느낌, 이런 걸로 설명하다 보니 역시 추상적이게 된다.
추상 클래스를 추상적으로 설명하니 더욱 추상스러운 추상 설명이 되어 버린다.
좋은 코드를 많이 보고, 많이 사용해 보면서 익히는 게 최고이지 싶다.


반응형