본문 바로가기

Study/Kotlin Study

[Kotlin] 인터페이스

+ 항공대학교 김철기 교수님의 객체 지향 프로그래밍 과목 내용를 정리한 글입니다.

인터페이스

일종의 추상 클래스로, 추상 클래스보다 제약이 많으나, 여러 개를 상속 받도록 할 수 있는 특수한 개념이다.

- 원래 클래스는 하나의 상위 클래스만 상속 받을 수 있다.

- 하지만, 인터페이스는 갯수에 제한 없이 상속이 가능하다.

-  class 대신에 interface라는 예약어로 시작한다.

- interface의 멤버는 default가 추상(abstract) 멤버이다.

 

=> 즉, 인터페이스는 자격의 의미이다. (상속 받는 클래스에게 자격을 부여한다.)

interface Vehicle {
    // 아래 프로퍼티는 abstract가 붙은 것으로 간주한다.
    val currentSpeed : Int
    fun move()
    fun stop()
}

인터페이스의 상속

인터페이스는 다른 인터페이스만 상속할 수 있다. (class 상속 불가)

 

비추상 클래스가 interface를 상속하면 interface의 모든 추상 멤버를 구현해야 한다.

 

인터페이스는 생성자가 없다.

=> 상속 시 ()를 쓰지 않아도 된다.

 

+ alt + enter를 통해 상속한 인터페이스의 멤버를 구현할 코드를 쉽게 불러올 수 있다.

 

+ 인터페이스 타입의 변수를 생성할 수 있다.

interface Vehicle {
    // 아래 프로퍼티는 abstract가 붙은 것으로 간주한다.
    val currentSpeed: Double
    fun start()
    fun stop()
}

class Car : Vehicle {
    override var currentSpeed = 0.0
        private set

    override fun start() {
        println("Start riding")
        currentSpeed = 50.0
    }
    override fun stop() {
        println("Stop riding")
        currentSpeed = 0.0
    }
}

fun main() {
    // 인터페이스 타입의 변수를 생성
    val v: Vehicle = Car()

    v.start()
    println("Current Speed = ${v.currentSpeed}")

    v.stop()
    println("Current Speed = ${v.currentSpeed}")
}

인터페이스 멤버의 구현

인터페이스의 함수나 프로퍼티는 구현을 추가할 수 있다.

 

인터페이스의 멤버는 구현을 했더라도 default로 open이다.

interface Vehicle {
    val currentSpeed: Double

    // currentSpeed가 0이면 true, 아니면 false 값을 갖는 Boolean형 변수
   	// 자신의 상태가 아닌 currentSpeed의 상태를 갖는다.
    val isMoving get() = currentSpeed != 0.0

    fun start()
    fun stop()

    fun report() {
        println(if(isMoving) "Moving at $currentSpeed" else "Still")
    }
}

class Car : Vehicle {
    override var currentSpeed = 0.0
        private set

    override fun start() {
        println("Start riding")
        currentSpeed = 50.0
    }
    override fun stop() {
        println("Stop riding")
        currentSpeed = 0.0
    }
}

fun main() {
    val v: Vehicle = Car()

    v.start()
    v.report()

    v.stop()
    v.report()
}

상태 정의가 금지된 인터페이스

인터페이스는 상태를 가질 수 없다.

- 상태를 가질 수 없으므로 생성자를 가질 수 없다.

 

따라서 프로퍼티를 구현할 때도 뒷받침하는 필드를 두거나 초기화금지된다. (메모리를 가질 수 없다.)

interface Vehicle {
    val currentSpeed: Double = 0.0 // Error: 자신의 상태 정의
    val maxSpped: Double by lazy { 100.0 } // Error: by lazy 역시 메모리 사용
    val altitude: Double // Error
        get() { // get()은 사용 가능
            println("Altitude get read")
            return field // 뒷받침하는 필드 사용
        }
    }
}

인터페이스와 다중 상속

package kr.kau.main

interface Car {
    fun ride()
}

interface Aircraft {
    fun fly()
}

interface Ship {
    fun sail()
}

// Car interface와 Aircraft interface를 상속하는 FlyingCar interface (다중 상속)
interface FlyingCar : Car, Aircraft {

}

class Transformer : FlyingCar, Ship {
    override fun ride() {
        println("I'm riding")
    }

    override fun fly() {
        println("I'm flying")
    }

    override fun sail() {
        println("I'm sailing")
    }
}

fun main() {
    // Transformer interface 타입의 t
    val t = Transformer()
    t.ride()
    t.fly()
    t.sail()

    // Ship interface 타입의 s에 Transformer interface 타입의 t 할당 
    val s: Ship = t
    s.sail()

    // FlyingCar interface 타입의 fc에 Transformer interface 타입의 t 할당
    val fc: FlyingCar = t
    fc.fly()
    fc.ride()
}

같은 메소드가 정의된 인터페이스들의 다중 상속

같은 메소드가 여러 인터페이스에 선언된 경우 상속 받은 클래스는 반드시 내부 구현이 존재해야한다.

override 해야한다.

 

만일 한쪽 인터페이스에만 내부 구현이 있는 경우라도 안전성을 위해서 상속 받은 클래스는 별도의 내부 구현을 가져야한다.

 

< 한 쪽 인터페이스에만 내부 구현이 있는 경우 >

interface Car  {
    fun move() {
        println("I'm moving")
    }
}

interface Ship {
    fun move()
}

class Amphibia : Car, Ship {
    
    // 상속하는 인터페이스의 2개 함수 중 하나만 구현이 되어 있을 경우
    override fun move() {
        super.move() // 구현된 부모 인터페이스의 함수 사용
    }
}

fun main() {
    val a = Amphibia()
    a.move()
}

 

 

< 두 인터페이스 모두 내부 구현이 있는 경우 >

interface Car  {
    fun move() {
        println("I'm moving")
    }
}

interface Ship {
    fun move() {
        println("I'm sailing")
    }
}

class Amphibia : Car, Ship {

    // 상속하는 인터페이스의 2개 함수 모두 구현이 되어 있을 경우
    override fun move() {
        super<Car>.move()
        println("Or")
        super<Ship>.move()
    }
}

fun main() {
    val a = Amphibia()
    a.move()
}

Delegate 패턴

인터페이스에서는 함수의 구체적인 Body를 구현할 수 없다.

=> 같은 인터페이스를 상속 받는 클래스가 같은 함수를 사용할 때, 중복해서 코드를 작성해야하는 문제가 발생한다.

 

< Delegate 패턴을 사용하지 않은 코드 >

abstract class Terran(var xcoord: Double, var ycoord: Double) {
    abstract fun die()
}

interface Flyable {
    fun fly()
}

class Wraith(x: Double, y: Double) : Terran(x, y), Flyable {
    override fun die() {
        println("펑")
    }

    override fun fly() {
        println("슈욱")
    }
}

class 발키리(x: Double, y: Double) : Terran(x, y), Flyable {
    override fun die() {
        println("광")
    }

    override fun fly() {
        println("슈욱")
    }
}

 

< Delegate 패턴을 사용한 코드 >

abstract class Terran(var xcoord: Double, var ycoord: Double) {
    abstract fun die()
}

interface Flyable {
    fun fly()
}

// Delegate 클래스 생성
class FlyingDelegate : Flyable {
    
    // 여러번 사용되는 함수를 구현
    override fun fly() {
        println("슈욱")
    }
}

class 레이스(x: Double, y: Double) : Terran(x, y), Flyable {
    override fun die() {
        println("펑")
    }
    
    // Delegate Pattern => 함수 재사용
    private val flyingDelegate = FlyingDelegate()
    override fun fly() {
        flyingDelegate.fly()
    }
}

class 발키리(x: Double, y: Double) : Terran(x, y), Flyable {
    override fun die() {
        println("광")
    }

    // Delegate Pattern => 함수 재사용
    private val flyingDelegate = FlyingDelegate()
    override fun fly() {
        flyingDelegate.fly()
    }
}

'Study > Kotlin Study' 카테고리의 다른 글

[Kotlin] data 클래스  (0) 2023.11.14
[Kotlin] enum 클래스  (1) 2023.11.13
[Kotlin] 추상 클래스와 추상 멤버  (0) 2023.11.02
[Kotlin] 공통 메소드  (1) 2023.10.23
[Kotlin] 타입 검사와 캐스팅  (2) 2023.10.23