인터페이스(interface)와 추상 클래스(abstract class)는 모두 객체지향 프로그래밍에서 다형성을 지원하며 구현을 강제하는 방식으로 사용되지만, 인터페이스를 우선시하는 이유는 다음과 같습니다.
다중 상속 지원
인터페이스는 다중 상속을 지원합니다. 즉, 하나의 클래스가 여러 개의 인터페이스를 구현할 수 있습니다. 반면, 추상 클래스는 단일 상속만 가능합니다.
interface Drivable {
fun drive()
}
interface Flyable {
fun fly()
}
class FlyingCar : Drivable, Flyable {
override fun drive() {
println("Driving")
}
override fun fly() {
println("Flying")
}
}
구현의 분리
인터페이스는 구현을 제공하지 않으며, 단지 계약(contract)만 정의합니다. 즉, 인터페이스는 객체가 제공해야 하는 기능(메소드)만 명시합니다. 이를 통해 구현을 독립적으로 다룰 수 있고, 다양한 클래스에서 다양한 방식으로 구현할 수 있습니다.
반면, 추상 클래스는 일부 구현을 제공할 수 있기 때문에 클래스 간에 구현을 공유할 수 있는 반면, 상속 관계가 고정적입니다. 즉, 추상 클래스는 하나의 부모 클래스만 상속받을 수 있어 유연성이 떨어집니다.
interface Animal {
fun sound(): String
}
class Dog : Animal {
override fun sound() = "Bark"
}
class Cat : Animal {
override fun sound() = "Meow"
}
// Dog와 Cat은 각각 Animal 인터페이스를 구현하여 다르게 동작할 수 있습니다.
기능 확장
인터페이스는 기능을 확장할 수 있는 방법을 제공합니다. 예를 들어, 인터페이스는 디폴트 메소드를 지원하여 기본 구현을 제공할 수 있습니다. 이는 기존 코드를 수정하지 않고도 새로운 기능을 추가할 수 있게 합니다.
interface Printable {
fun print() {
println("Printing...")
}
}
class Document : Printable {
// print()는 이미 제공되는 기본 구현을 사용할 수 있음
}
// Printable 인터페이스는 print() 메소드에 대한 기본 구현을 제공하므로
// Document 클래스에서 따로 구현하지 않아도 사용할 수 있습니다.
다중 상속을 허용하고, 기능을 정의하는 데 더 적합하기 때문입니다.
이를 통해 유연한 설계와 기능 확장이 가능하며, 독립적인 구현을 촉진할 수 있습니다.
추상 클래스는 공통된 동작을 상속하는 데 유리하지만, 단일 상속의 제한이 있기 때문에 인터페이스가 더 선호되는 경우가 많습니다.
따라서 기능 중심 설계에서는 인터페이스를, 공통 동작을 구현하고 상속을 사용하는 경우에는 추상 클래스를 사용할 수 있습니다.