다음과 같이 gpt chat에게 물어봤다
추상클래스와 인터페이스 따로 쓰는 이유가 뭐야? 추상클래스가 인터페이스의 기능을 모두 할 수 있을거 같은데?
답변은 아래와 같다.
추상 클래스와 인터페이스는 모두 추상화된 개념입니다. 하지만 두 개념은 사용하는 목적이나 특징이 다르기 때문에 따로 쓰이는 경우가 있습니다.
추상 클래스는 일부 메서드를 구현하고 나머지는 하위 클래스에서 구현하도록 하는 추상 메서드를 가지고 있습니다. 또한 일반적인 멤버 변수와 메서드를 가질 수 있습니다. 이러한 특징으로 인해 추상 클래스는 상속 관계에서 하위 클래스에게 공통된 속성과 메서드를 물려주는 역할을 하면서도 상속받은 클래스에서 반드시 구현해야 할 메서드가 무엇인지 알려주는 역할을 합니다.
반면에 인터페이스는 추상 메서드와 상수만을 가질 수 있습니다. 따라서 인터페이스는 클래스가 반드시 구현해야 할 메서드 목록을 정의하는 용도로 주로 사용됩니다. 인터페이스는 클래스가 아닌 다른 인터페이스를 구현할 수 있기 때문에, 다중 상속을 구현하는 데 사용될 수도 있습니다.
따라서, 추상 클래스는 공통된 구현 코드를 공유하는 클래스들 사이에서 공통된 속성과 메서드를 물려주는 데 사용되고, 인터페이스는 클래스가 구현해야 할 메서드를 정의하는 데 사용됩니다. 추상 클래스와 인터페이스는 둘 다 추상화된 개념이지만, 사용하는 목적과 특징이 다르기 때문에 따로 쓰이는 경우가 있습니다.
결론은,
추상 클래스는 공통된 구현 코드를 공유하는 클래스들 사이에서 공통된 속성과 메서드를 물려주는 데 사용되고,
인터페이스는 클래스가 구현해야 할 메서드를 정의하는 데 사용된다.
뭐, 틀린 말은 아니고 정론이긴 하다.
하지만 아직 확실히 용도가 와닿진 않는다.
추상클래스도 구현해야할 메서드를 정의하는데 사용되는데??
너무 광범위한 설명 아닌가?
그래서 더 찾아보고 공부한 결과,
내가 낸 결론은,
사용의도에서 차이점이 있다고 본다.
즉, 항상 그렇다는건 아니지만, 보통
추상클래스는 IS - A 관계일때 주로 사용된다.
=> 사과는 과일이다. 즉, 사과는 과일의 한 종류이다. 이런 관계를 표현할 때는 추상 클래스를 사용한다.
인터페이스는 HAS - A 관계일때 주로 사용한다.
=> 자동차는 엔진을 가지고 있다. 즉, 자동차는 엔진이라는 요소(기능)을 가지고 있다. 이런 관계를 표현할 때는 인터페이스를 사용한다.
다음과 같은 예시를 보자
위에서 Creature, Human, Animal 같은 추상클래스는 구체화된 클래스의 상위 클래스로서, 하위 클래스들의 기본적으로(다른말로, 공통적으로) 가져야 되는 속성과 기능을 정의했다.
즉 추상클래스가 하위클래스의 근본(is-a, ~는 ~이다)이 된다는 의미를 가지고있다.
=> IS-A 관계에 대입하면 적절한 문장이 된다는 것이다.
ex)
Kevin은 Human이다(Kevin is Human) => O (케빈의 근본은 사람)
Turtle 은 Animal이다(Turtle is Animal) => O (거북이의 근본은 동물)
Pigeon은 Animal이다(Pigeon is Animal) => O (비둘기의 근본은 동물)
예를들어
public abstract class Creature {
public abstract void eat();
}
}
public class Kevin extends Creature {
@Override
public void eat() {
System.out.println("Kevin eats Kimchi!");
}
}
public class Turtle extends Creature {
@Override
public void walk() {
System.out.println("Turtle eats fish!");
}
}
public class Pigeon extends Creature {
@Override
public void walk() {
System.out.println("Pigeon eats bug!");
}
}
위처럼 추상클래스인 Creature에는 생명체의 기본이 되는 eat()메서드만 선언해놓고, 하위 클래스들은 기본이되는 해당 메서드를 재정의 해서 각 클래스마다 고유한 기능을 하도록 하였다.
Talkable, Flyable, Swimable 같은 인터페이스들은, 어떤 클래스들의 조상 클래스가 되기에는 적절지 않지만,
"말할 수 있음","날 수 있음","수영할 수 있음" 등의 어떠한 기능에 대한 가이드(규칙) 제공하고, Kevin이나 Turtle, Pigeon같은 클래스들이 그런 기능들을 가질 수 있게(HAS - A 관계)해준다.
예를 들어
public interface Flyable {
void fly();
}
public interface Swimable {
void swim();
}
public interface Talkable {
void talk();
}
이렇게 만들어 놓고, 어떠한 클래스가 위 기능들이 필요하다면(have, 가지고 싶다면) implemnets로 구현해서 바로 기능을 제공해주면 된다.
이제 모두 활용한 예를 들면
abstract class Animal {
public abstract void walk();
public abstract void talk();
}
interface AblityToEat {
public void eat(Creature creature);
}
interface ToBeEaten {
public void toBeEatenBy(Creature creature);
}
class Turtle extends Animal implements AblityToEat {
@Override
public void walk() {
System.out.println("I am walking!");
}
@Override
public void talk() {
System.out.println("I can talk!");
}
@Override
public void eat(Creature creature) {
System.out.println("I am eating"+ creature+"!");
}
}
class Fish extends Animal implements ToBeEaten {
@Override
public void walk() {
System.out.println("I cannot walk!");
}
@Override
public void talk() {
System.out.println("I cannot talk!");
}
@Override
public void toBeEatenBy() {
System.out.println("I am being eaten by a"+creature+"!");
}
}
거북이가 말할수 있다는게 이상하지만 넘어가도록하자
위의 예시에서,
거북이와 물고기 모두 근본은 Animal이기때문에 Animal 클래스를 상속했고,
거북이와 물고기는 각각 먹을 수있고, 먹힐 수 있기 때문에
(다른말로, 먹을 수 있고 먹힐 수 있는 기능을 가지고 있기 떄문에)
AblityToEat, ToBeEaten 인터페이스를 구현하도록 하였다.
이떄 추상클래스는 이 클래스를 상속할 하위 클래스들의 기초(근본)가 되어 어떠한 클래스인지를 나타내는 속성(멤버변수)과 기능(메서드)을 제공한다.
객체에 어떠한 기능이 추가될때마다 해당클래스에 메서드를 추가하는건 너무 번거롭다.
객체에서 연관된 특정 기능들을 분리한 다음,
어떠한 기능들을 제공하는지를 정의해서 인터페이스로서 제공하면,
후에 다른 모든 클래스한테도 해당 기능이 필요하다면 구현을 통해 사용하게 가능하다.