Kotlin interface 호출 - Kotlin interface hochul

References: Do it! 코틀린 프로그래밍

코틀린에서의 인터페이스에 대해 알아봅니다.

1. 인터페이스(Interface)

코틀린의 인터페이스에는 추상 메서드나 일반 메서드가 포함될 수 있습니다. 

다른 객체지향 언어와는 달리 메서드에 구현 내용이 포함될 수 있습니다. 단, 추상 클래스처럼 프로퍼티를 통해 상태를 지정할 수는 없습니다.

추상 클래스는 기본적으로 클래스이기 때문에 상속을 통해 하위로 확장이 가능합니다. 

그런데 하위 클래스는 상속을 하나만 허용합니다. 또한 상위 클래스와 강한 관계가 생기며 상위 클래스의 영향을 받게 됩니다.

반면에 인터페이스는 기본적으로 클래스가 아닙니다. 따라서 상속으로 하위 클래스에 프로퍼티와 메서드를 전달하지 않습니다. 상속받은 하위 클래스라고 하기보단 구현 클래스라고 합니다.

구현 클래스는 인터페이스와 강한 관계를 갖지 않습니다. 

또한 원하는 인터페이스 만큼 구현 클래스에 붙여서 필요한 메서드를 구현할 수 있습니다.

2. 인터페이스 선언 및 구현 방법.

인터페이스는 interface라는 선언자를 통해 선언합니다.

구현 클래스에서는 override 라는 선언자를 통해 메서드들을 구현합니다.

다음 예시를 통해 확인하겠습니다.

interface Pet{
    var category: String
    fun feeding()
    fun patting(){ println("Keep patting") }
}
class Cat(override var category: String): Pet{
    override fun feeding() { println("Feed the cat a tuna can!") }
}
fun main() {
    val obj = Cat("small")
    println("Pet category: ${obj.category}")
    obj.feeding()
    obj.patting()
}
  • var category / fun feeding: 인터페이스에서는 별도의 선언자 없어도 기본적으로 abstract로 선언됩니다.
  • fun patting: 단, 이와 같이 메서드를 구현하면 일반 메서드가 됩니다.
  • override var category / override fun feeding: 추상 프로퍼티와 클래스를 구현합니다.
  • : Pet: Pet 인터페이스를 구현하는 클래스임을 알려줍니다.

위의 코드를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

Kotlin interface 호출 - Kotlin interface hochul

3. 인터페이스의 필요성.

다음 예시를 통해 인터페이스의 필요성에 대해 알아보겠습니다.

우선 애완동물과 주인의 관계를 표현하는 코드를 작성해 보겠습니다.

open class Animal(val name: String)
interface Pet{
    var category: String
    fun feeding()
    fun patting(){ println("Keep patting") }
}
class Dog(name: String, override var category: String): Animal(name), Pet{
    override fun feeding() { println("Feed the dog a bone.") }
}
class Cat(name: String, override var category: String): Animal(name), Pet{
    override fun feeding() { println("Feed the cat a tuna can.") }
}
class Master {
    fun playWithPet(dog: Dog){ println("Enjoy with my dog.") }
    fun playWithPet(cat: Cat){ println("Enjoy with my cat") }
}
fun main(){
    val master = Master()
    val dog = Dog("Toto", "small")
    val cat = Cat("Coco", "BigFat")
    master.playWithPet(dog)
    master.playWithPet(cat)
}
  • Dog / Cat: Animal클래스를 상속받고 Pet 인터페이스를 구현하는 클래스
  • playWithPet: 함수 인자에 따른 함수 오버로딩

위의 코드를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

Kotlin interface 호출 - Kotlin interface hochul

여기서 만약 애완동물 종류가 늘어나면 어떻게 될까요? 애완동물마다 playWithPet함수를 새로 오버로딩해야 하는 문제가 생깁니다. 

이러한 문제를 피하기 위해 코드를 다음과 같이 수정해 봅시다.

open class Animal(val name: String)
interface Pet{
    var category: String
    var species: String
    fun feeding()
    fun patting(){ println("Keep patting") }
}
class Dog(name: String, override var category: String): Animal(name), Pet{
    override var species: String = "dog"
    override fun feeding() { println("Feed the dog a bone.") }
}
class Cat(name: String, override var category: String): Animal(name), Pet{
    override var species: String = "cat"
    override fun feeding() { println("Feed the cat a tuna can.") }
}
class Master {
    fun playWithPet(pet: Pet){ println("Enjoy with my ${pet.species}.") }
}
fun main(){
    val master = Master()
    val dog = Dog("Toto", "small")
    val cat = Cat("Coco", "BigFat")
    master.playWithPet(dog)
    master.playWithPet(cat)
}

위와 같이 수정하면 더 이상 애완동물을 추가 할 때 마다 새로운 함수를 오버로딩할 필요가 없어집니다.

기존의 Master 클래스는 각각의 애완동물 클래스에 의존적이었으나 이젠 더이상 의존적인 클래스가 아니게 됩니다.

이런 식으로 클래스의 독립성을 유지하는데 인터페이스가 큰 역할을 합니다.

4. 여러 인터페이스를 구현하는 방법

클래스는 하나의 클래스만 상속이 가능합니다.

하지만 인터페이스를 이용하면 다중 상속과 같은 형태로 여러 인터페이스를 구현할 수 있습니다.

다음 예시를 통해 확인해 보겠습니다.

interface Bird{
    val wings: Int
    fun fly()
    fun jump(){ println("Bird jump!") }
}
interface Horse{
    val maxSpeed: Int
    fun run()
    fun jump(){ println("Jump!, max speed: $maxSpeed") }
}
class Pegasus: Bird, Horse{
    override val wings: Int = 2
    override val maxSpeed: Int = 100
    override fun fly(){ println("Fly") }
    override fun run(){ println("Run!") }
    override fun jump() {
        super<Horse>.jump()
        println("Pegasus jump!")
    }
}
fun main(){
    val pegasus = Pegasus()
    pegasus.fly()
    pegasus.run()
    pegasus.jump()
}
  • class Pegasus: Bird, Horse: 페가수스 클래스를 만들고 Bird, Horse 인터페이스를 구현하였습니다.
  • super<Horse>.jump(): 부모 인터페이스 중 Horse 인터페이스의 jump 메서드를 호출합니다.

해당 코드를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

Kotlin interface 호출 - Kotlin interface hochul

위와 같은 방법을 통해 한 클래스에 여러 인터페이스를 구현하게 할 수 있습니다.

5.  인터페이스 위임

인터페이스에서 by위임을 사용하는 방법을 예시를 통해 알아보겠습니다.

interface Nameable { var name: String }
class StaffName: Nameable { override var name: String = "Sean" }
class Work: Runnable { override fun run() { println("Work...") } }
class Person(name: Nameable, work: Runnable): Nameable by name, Runnable by work
fun main() {
    val person = Person(StaffName(), Work())
    println(person.name)
    person.run()
}
  • class Person: 각 매개변수에 인터페이스들을 위임합니다.
  • Person(StaffName(), Work()): 클래스의 생성자를 통해 객체를 전달합니다.

위의 코드를 실행한 결과는 다음과 같습니다.

Kotlin interface 호출 - Kotlin interface hochul

이처럼 각 클래스의 위임된 멤버에 접근해 사용할 수 있습니다.