본문 바로가기
Kotlin/Kotlin 프로그래밍

[Kotlin] 다양한 클래스와 인터페이스 (1)

by 주 녕 2021. 4. 21.
728x90

모든 내용은 Do it! 코틀린 프로그래밍을 바탕으로 정리한 것입니다. 

추상 클래스와 인터페이스

추상 : 구체적이지 않은 것

▶ 추상 클래스와 인터페이스 모두 대략적인 설계 명세를 가지고 몇 가지 기본적인 부분은 구현할 수 있으나, 하위에서 더 자세히 구현해야 함. 

추상 클래스

  1. abstract 키워드와 함께 선언
  2. 일반적인 객체를 생성하는 방법으로 인스턴스화 X
  3. 프로퍼티상태 정보 저장 가능
abstract class Vehicle(val name: String, val color: String, val weight: Double) {

    abstract var maxSpeed: Double  // 추상 프로퍼티
    var year = "2018"  // 일반 프로퍼티

    // 추상 메서드
    abstract fun start()
    abstract fun stop()

    // 일반 메서드
    fun displaySpecs() {
        println("Name: $name, Color: $color, Weight: $weight, Year: $year, Max Spped: $maxSpeed")
    }
}
  • abstract 키워드 자체가 상속과 오버라이딩을 허용하기 때문에 open 키워드를 사용할 필요가 없음
  • 추상 클래스의 프로퍼티나 메서드도 abstract로 선언될 수 있음 : 추상 프로퍼티, 추상 메서드
    • 클래스에 추상 프로퍼티나 메서드가 하나라도 있다면 해당 클래스는 추상 클래스
    • name, color, weight, year는 일반 프로퍼티 / maxSpeed는 추상 프로퍼티
    • displaySpecs()는 일반 메서드 / start(), stop()은 추상 메서드
abstract class Printer {
    abstract fun print()
}

val myPrinter = object: Printer() {
    override fun print() {
        println("출력합니다")
    }
}

fun main() {
    myPrinter.print()
}
  • 추상 클래스로부터 하위 클래스를 생성하지 않고 단일 인스턴스로 객체 생성 가능
    • object 키워드 : 익명 객체를 지정함
    • 콜론(:) 오른쪽에 생성자 이름을 사용하고 블록에서 관련 메서드를 오버라이딩해 구현해야 함

 

추상 클래스의 한계

  1. 하위 클래스는 상속을 하나만 허용함 → 2개 이상의 클래스로부터 프로퍼티나 메서드를 상속받을 수 없음
  2. 상위 클래스와 하위 클래스에 강한 연관이 생기면서 하위 클래스는 상위 클래스에 영향을 받음

 

인터페이스

  1. interface 키워드와 함께 선언
  2. abstract로 정의된 추상 메서드나 일반 메서드가 포함됨
  3. 다른 객체 지향 언어와는 다르게 메서드에 구현 내용이 포함될 수 있음
  4. 프로퍼티를 통해 상태를 저장할 수는 없음
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 cate a tuna can!")
    }
}

fun main() {
    val obj = Cat("small")
    println("Pet Category: ${obj.category}")
    obj.feeding()
    obj.patting()
}
  • 추상 클래스와 달리 abstract를 붙이지 않아도 기본적으로 추상 프로퍼티와 추상 메서드로 지정됨
  • Cat 클래스는 Pet 인터페이스를 구현한 클래스
    • 인터페이스의 구현은 클래스의 상속과 동일하게 콜론(:)을 사용
    • 추상 프로퍼티와 추상 메서드를 override 키워드를 통해 구현

 

게터를 구현한 프로퍼티

인터페이스는 프로퍼티에 값을 지정할 수 없음.

그렇다면 val로 선언된 프로퍼티의 값은?     초기화 할 수는 없지만 getter를 통해 필요한 내용을 구현

interface Pet {
    var category: String
    val msgTags: String
        get() = "I'm your lovely pet!"
    ...
}

fun main() {
    ...
    println("Pet MsgTags: ${obj.msgTags}")
    ...
}

 

인터페이스 구현의 필요성

open class Animal(val name: String)

class Dog(name: String, override var category: String) : Animal(name),Pet {
    override fun feeding() {
        println("Feed the dog a bone")
    }
}

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)
}
  • playWithPet() : 놀고자 하는 동물에 따라 매개변수를 다르게 정한 오버로딩되 메서드
    • dog, cat 클래스에 대해 필요한 playWithPet() 
    • 애완동물의 종류가 늘어난다면 많은 수의 오버로딩 된 메서드가 필요함

→ 이때 필요한 것이 인터페이스

interface Pet {
    var category: String  // 추상 프로퍼티
    val msgTags: String
        get() = "I'm your lovely pet!"

    var species: String
    ...
}

class Cat(name: String, override var category: String) : Pet {
    override var species: String = "cat"
    override fun feeding() {
        println("Feed the cate a tuna can!")
    }
}

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 Master {
    fun playWithPet(pet: Pet) {
        println("Enjoy with my ${pet.species}")
    }
}
  • 인터페이스에 종을 위한 프로퍼티를 선언하고 이것을 이용해 어떤 애완동물과 놀게 될지 알 수 있음
    • Master 클래스의 playWithPet()은 각 애완동물에 따라 오버로딩을 하지 않아도 됨
    • 기존의 Master 클래스가 Cat/Dog 클래스에 의존적이었으나 인터페이스를 통해 의존성을 제거함

 

여러 인터페이스의 구현

interface Bird {
    val wings: Int
    fun fly()
    fun jump() {
        println("bird jump!")
    }
}

interface Horse {
    val maxSpeed: Int
    fun run()
    fun jump() {
        println("jump!, max spped: $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()
}
  • Bird, Horse라는 2개의 인터페이스로부터 Pegasus 클래스를 정의함
  • 기본 구현되어 있는 메서드는 필요에 따라서만 오버라이딩할 수 있음
  • 이름이 동일한 경우 : super<인터페이스 이름>.메서드() 

 

인터페이스의 위임

인터페이스에서도 by 위임자를 사용할 수 있음

interface A {
    fun functionA() { }
}

interface B {
    fun functionB() { }
}

class C(val a: A, val b: B) {
    fun functionC() {
        a.funcitonA()
        b.funcitonB()
    }
}

현재 코드에서 클래스 C는 funcitonA()와 functionB()를 접근하기 위해 a, b 변수를 사용했음

class C(val a: A, val b: B): A by a, B by b {
    fun functionC() {
        functionA()
        functionB()
    }
}

by 위임자를 사용하여 a, b를 인터페이스 A, B에 위임함으로서 해당 메서드를 사용할 때 점(.) 표기법 접근 없이 사용할 수 있음

 

위임을 이용한 멤버 접근

Person 클래스가 상속과 같은 형태로 위임을 사용하고 있음

interface Nameable {
    var name: String
}

class StaffName : Nameable {
    override var name: String = "Jun"
}

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()
}
728x90

댓글