본문 바로가기
2024 활동/Kotlin·Android

[Kotlin] 인터페이스 이해하기

by 은행장 노씨 2024. 4. 28.

인터페이스 뿌셔보자!

언어가 지향하고 있는 패러다임을 배우는 것도 중요하다. 

객체지향 프로그래밍에서 중요한 세 가지
클래스, 상속, 인터페이스

 

개인적으로 인터페이스는 정말 활용하기 좋은 것 같다. 

잘 쓰면 정말 유용하기에 자알 써보고 싶다..ㅎㅎ

 


인터페이스 Interface

인터페이스는 추상 메소드를 포함할 수 있는 참조 타입으로, 인터페이스 자체로는 객체를 생성할 수 없지만, 다른 클래스가 인터페이스를 구현(Implement)함으로써 메소드의 실제 구현을 제공할 수 있다.

 

  • 규약(프로토콜)
    • 인터페이스에 비어 있는 것을 채워주면 해당 인터페이스로 봐주겠다.
    • == abstract를 구현하면 해당 Type으로 봐주겠다.
인터페이스를 보고 구현된 클래스의 객체와 의사소통할 수 있다. 
- 의사소통 -> 사용할 수 있다. 
- 이것을 가능하게 만든 규칙이 규약이다. 

 

 

더 자세한 정보를 알고 싶다면 아래의 공식 사이트에서 예제와 설명을 참고하면 좋을 것 같다. 

https://kotlinlang.org/docs/interfaces.html#interfaces-inheritance

 

Interfaces | Kotlin

 

kotlinlang.org

 

 


(예제) 인터페이스 정의하기 : ElectronicDevice

자, 이제 예시를 보자. '전자기기'를 제어하는 시스템을 구현해보겠다. 

이 시스템은 다양한 전자기기(예:TV, 에어컨, 라디오 등)를 켜고 끄는 기능을 통일된 방식으로 제어할 수 있다. 

interface ElectronicDevice {
    fun turnOn()
    fun turnOff()
}

 

 

(예제) 인터페이스를 사용하여 각 클래스 구현

구현은 간단하다. 정의했던 메소드를 override 해주면 된다. 새로운 기능을 추가해도 된다. 

- 안드로이드 스튜디오에서는 구현할 메소드를 자동 생성해주는 기능도 있다. 
    > 인터페이스를 적용할 클래스 좌클릭 > Generate > Inplement Members > 

class AirConditioner : ElectronicDevice {
    override fun turnOn() {
        println("Air Conditioner is now on, cooling down.")
    }

    override fun turnOff() {
        println("Air Conditioner is now off.")
    }
}

class Radio : ElectronicDevice {
    override fun turnOn() {
        println("Radio is playing music now.")
    }

    override fun turnOff() {
        println("Radio has been turned off.")
    }
}

 

 

예시에서 인터페이스는 전자기기의 상태 변경을 표준화하는 역할을 한다. 

ElectronicDevice 인터페이스를 구현함으로써 다양한 기기에 대해 동일한 방법으로 기기를 켜고 끌 수 있으며, 코드의 유연성과 확장성을 보장한다. 

 

 

🤔 궁금해요!

배우다 보니 인터페이스에 대해 더 궁금해져서 GPT에 물어봤다. 

왜 인터페이스에서는 open 키워드를 사용하지 않을까?
- 코틀린에서 인터페이스의 멤버들은 기본적으로 'abstract'로 간주됨
인터페이스 내의 메소드나 속성이 구현이 되어있지 않음 → 인터페이스의 멤버는 이미 재정의 가능한 상태이기 때문

 

인터페이스를 미리 정의할 수 있을까?
(1) 인터페이스에서 속성 정의하기
- 백킹 필드(backing field)를 사용할 수 없다.(상태를 저장 못함)
- 하지만, 속성에 대한 접근자(getter, setter)를 정의할 수 있음
interface Vehicle {
    val numberOfWheels: Int // abstract
    val hasMotor: Boolean get() = true
    
		fun start()
		fun stop()  
}

class Bicycle : Vehicle {
    override val numberOfWheels = 2
}​

(2) 인터페이스에서 메소드 정의하기
- 구현시에는 override를 하지 않아도 된다. 
- 'final override'로 구현하면 구현될 클래스에서 override를 금지한다. 
interface Vehicle {
    val numberOfWheels: Int
    val hasElectronMotor: Boolean

    fun start() { // default method
        println("let's go.")
    }
    fun stop()
}


class ElectricCar : Vehicle {
    override val numberOfWheels = 4
    override val hasElectronMotor = true

    // default can be override
    override fun start() {
        println("Electric car is starting with silent mode.")
    }
}​

 

인터페이스에서는 다중 구현이 가능할까?
- 다중 상속을 통해 여러 부모로부터 기능을 상속받아 더 풍부한 구현 가능
- 코드의 재사용성을 높이고, 유지 보수를 쉽게 하며, 시스템 확장성 향상

- 속도 조절, 방향 조절을 할 수 있는 자동차 인터페이스 다중 구현 예시)
interface SpeedControl {
    fun accelerate()
    fun decelerate()
}

interface DirectionControl {
    fun turnLeft()
    fun turnRight()
}

class Car : SpeedControl, DirectionControl {
    override fun accelerate() {
        println("차량 속도 증가")
    }

    override fun decelerate() {
        println("차량 속도 감소")
    }

    override fun turnLeft() {
        println("차량 좌회전")
    }

    override fun turnRight() {
        println("차량 우회전")
    }
}​

 

인터페스에서 다중 상속 문제인 다이아몬드 문제가 생길 수 있나요?

다이아몬드 문제는 하나의 클래스가 두 개 이상의 클래스로부터 상속받을 때, 같은 메소드나 속성을 두 부모 클래스 모두에서 상속받아 충돌이 발생하는 문제이다. 

→ 이는 클래스가 두 부모의 구현을 어떻게 상속받을지 결정하기 어려워서 발생한다.

아래의 방법으로 인터페이스의 타입을 명시하는 방법으로 해결할 수 있다.  

interface A {
    fun display() {
        println("Display from A")
    }
}

interface B {
    fun display() {
        println("Display from B")
    }
}

class C : A, B {
    override fun display() {
        super<A>.display() 
        super<B>.display()  
    }
}

fun main() {
    val c = C()
    c.display()  // Display from A"와 "Display from B" 둘 다 출력됨
}

 

 


정리해보자. 

  1. 인터페이스
    코틀린의 인터페이스는 클래스 간에 메소드를 공유하는 계약(프로토콜)으로, 구현 클래스에 구체적인 행동을 지정한다. 
    이는 클래스가 인터페이스를 구현하면, 해당 인터페이스의 모든 추상 메소드를 오버라이드해야 한다는 것을 의미한다.
  2. 디폴트 메소드와 추상 속성
    : 인터페이스는 디폴트 메소드를 제공할 수 있어, 인터페이스를 수정할 때 기존 구현에 영향을 미치지 않는 확장이 가능하다.
  3. 다중 구현과 다이아몬드 문제 해결
    코틀린 인터페이스는 다중 구현을 지원하여 하나의 클래스가 여러 인터페이스를 구현할 수 있다. 또한, 다중 구현에서 발생할 수 있는 다이아몬드 문제를 super<Type> 구문을 사용하여 명시적으로 어떤 인터페이스의 메소드를 호출할지 지정함으로써 해결한다.
  4. 확장성 및 유지보수
    인터페이스를 사용하면 시스템의 확장성과 유지보수성이 향상된다. 새로운 기능을 추가하거나 기존 기능을 변경할 때, 인터페이스를 통해 손쉽게 적용할 수 있으며, 코드의 일관성을 유지할 수 있다.