Android 앱/Kotlin Language

kotlin 코틀린 - 클래스 상속(inheritance), 추상화(abstract), interface

arvigoes 2020. 3. 6. 22:38

상속(inheritance)

상속은 객체 지향 프로그래밍을 구성하는 중요한 특징 중 하나입니다.

상속을 통해 코드 재활용성을 제공하고, 클래스 간의 계층적 관계를 구성하여 다형성의 토대를 마련 합니다.

 

실제 주변에서 상속이 어떤 개념으로 사용되는지 한번 알아 볼까요?

 

이전까지 Dog 라는 클래스를 만들었습니다.

개(Dog)와 고양이(Cat)는 동물(Animal)이라는 속성을 상속 받고 있습니다.

그리고 진돗개, 삽살개, 등은 개 라는 속성을 상속 받고 있습니다.

 

이걸 코드로 한번 표현해 보도록 하겠습니다.

open class Animal(var name:String, var owner:String, var type:String) {
    fun nameTag() {
        println("Type:${type}, name:${name}, owner:${owner}")
    }
}

class Dog (name:String, owner:String) : Animal(name, owner, "Dog") {

}

class Cat (name:String, owner:String) : Animal(name, owner, "Cat") {

}

open 은 클래스 선언시 클래스가 상속 될 수 있다고 표시해주는 키워드 입니다.

open 키워드가 붙어있지 않는 클래스는 상속이 될 수 없습니다.

 

상속이 되는 클래스는 수퍼클래스(부모클래스) 라고 부르고,

상속을 받는 클래스는 서브 클래스(자식클래스) 라고 합니다.

여기서 수퍼클래스는 Animal 서브 클래스는 Dog, Cat 이 됩니다.

 

서브클래스 속성의 이름은 수퍼클래스와 같은 이름을 가질 수 없습니다.

이는 함수에도 동일하게 적용 됩니다.

그렇지만 수퍼클래스에서 허용한다면 오버라이딩(overriding)이라는 형태로 같은이름과 형태로된 함수를 구현 할 수 있습니다.

서브 클래스의 인스턴스를 생성할때는 꼭 수퍼클래스의 생성자까지 호출을 해 줘야 합니다.

 

fun main(){
    var mung = Dog("mung", "boss")
    var mimi = Cat("mimi", "mother")

    mung.nameTag()
    mimi.nameTag()
}

Dog 와 Cat 객체를 인스턴스로 만들어봤습니다.

fun main(){
    var mung = Animal("mung", "boss", "Dog")
    var mimi = Animal("mimi", "mother", "Cat")

    mung.nameTag()
    mimi.nameTag()
}

위 두 내용의 결과는 동일합니다.

궂이 상속을 사용하면서 해결을 할 필요가 없습니다.

그렇다면 상속을 사용하는 이유를 한번 더 고민 해보도록 할까요?

개와 고양이는 분명한 차이점이 있습니다.

여러 차이점이 있지만 그중에 짖을때내는 소리가 다릅니다.

이걸 함수로 추가해서 Animal 과 , Dog, Cat 에 차이점을 만들어 보겠습니다.

open class Animal(var name:String, var owner:String, var type:String) {
    fun nameTag() {
        println("Type:${type}, name:${name}, owner:${owner}")
    }
}

class Dog (name:String, owner:String) : Animal(name, owner, "Dog") {
    fun sound(){
        println("mung mung")
    }
}

class Cat (name:String, owner:String) : Animal(name, owner, "Cat") {
    fun sound(){
        println("meow meow")
    }
}

fun main(){
    var mung = Dog("mung", "boss")
    var mimi = Cat("mimi", "mother")

    mung.nameTag()
    mimi.nameTag()

    mung.sound()
    mimi.sound()

}

 

 

sound 라는 함수로 3클래스에 차이점을 뒀습니다.

Animal 에는 sound 라는 함수가 없고, 나머지 두객체에는 존재합니다.

var mung = Animal("mung", "boss", "Dog")

mung.sound() 

이렇게 사용하게 된다면 문제가 되겟죠?

이런식의 사용을 해결하기 위해 오버라이딩이 존재 합니다.

위 코드는 조금 억지 이지만

fun AnimalCry(ani:Animal)
{
    ani.sound()    
}
fun main(){
    var mung = Dog("mung", "boss")
    var mimi = Cat("mimi", "mother")

    AnimalCry(mung)
    AnimalCry(mimi)
}

이렇게 다른 함수를 통해 호출이 되는 경우는 매우 빈번합니다.

이러한 개념을 오버라이딩 이라고 하는데, 부모클래스에서 정의된 내용을 자식 클래스에서 재정의 해서 

동일한 함수를 통해 자식의 시그니쳐 형태를 가지게 됩니다.

그리고 오버라이딩은 다형성의 관점에서 매우 중요하고 기본이 되는 내용입니다.

 

위와 같이 사용 할 수 있도록 구현 해 보도록 하겠습니다.

open class Animal(var name:String, var owner:String, var type:String) {
    fun nameTag() {
        println("Type:${type}, name:${name}, owner:${owner}")
    }

    open fun sound(){
        println("?????")
    }
}

class Dog (name:String, owner:String) : Animal(name, owner, "Dog") {
    override fun sound(){
        println("mung mung")
    }
}

class Cat (name:String, owner:String) : Animal(name, owner, "Cat") {
    override fun sound(){
        println("meow meow")
    }
}

Animal 클래스의 sound 의 경우 어떤울음소리가 나와야 할까요? 

원래 이렇게 부모에서 정의가 불가능한 함수의 경우

추상클래스 , 추상 함수 (abstract) 이라는 개념이 있는데 추후에 다뤄 보도록하겠습니다.

현재는 abstract 라는 개념이 없으니 ???? 로 대체 하겠습니다.

우선 수퍼 클래스에서 이 함수를 오버라이딩을 통해 재구현 할 수 있다는 의미로 함수 앞에 open 키워드를 붙여둡니다.

그리고 서브 클래스에서는 재구현 한다는 의미로 override 키워드를 붙여주면 됩니다.

 

fun animalCry(ani:Animal)
{
    ani.sound()
}
fun main(){
    var mung = Dog("mung", "boss")
    var mimi = Cat("mimi", "mother")

    mung.nameTag()
    mimi.nameTag()

    animalCry(mung)
    animalCry(mimi)
}

원하는 형태로 잘 동작 합니다.

만약 mung 개체를 Animal 로 생성한다면 sound 에서 ????? 가 나올 것 입니다.

이러한 현상을 막으려면 Animal 클래스로 인스턴스 생성을 막아야 하는데 이 또한 추상클래스로 해결이 가능합니다.

 

추상화 (abstact)

Animal instance 생성, 및 ???? 함수 호출을 해결해 보도록 하겠습니다.

추상화는 함수의 선언부만 있고, 기능이 구현되지 않는 추상함수

추상함수를 포함하는 추상 클래스로 구성됩니다.

이 추상클래스를 상속 받은 서브 클래스는 수퍼클래스의 추상함수를 꼭 재구현 해 주어야 합니다.

추상화 개념을 통해 다시 한번 Animal 클래스를 구현 해 보도록 하겠습니다.

abstract class Animal(var name:String, var owner:String, var type:String) {
    fun nameTag() {
        println("Type:${type}, name:${name}, owner:${owner}")
    }

    abstract fun sound()
}

class Dog (name:String, owner:String) : Animal(name, owner, "Dog") {
    override fun sound(){
        println("mung mung")
    }
}

class Cat (name:String, owner:String) : Animal(name, owner, "Cat") {
    override fun sound(){
        println("meow meow")
    }
}

 이제 더이상 Animal 클래스로 instacne 를 생성 할 수 없습니다.

var mung = Animal("mung", "boss", "Dog")

mung.sound() 

이 코드를 통해 더이상 개가 ??????? 로 짖는것을 보지 않아도 됩니다.

객체 생성자체가 에러로 처리가 되니 실수를 막아 줄 수도 있습니다.

 

abstract 키워드 이외에도 추상화를 하는 방법이 또 있습니다.

코틀린에서는 interface 라는 것을 통해 구현이 가능합니다.

 

코틀린에서는 abstract class 와는 큰 차이가 없어 보일 수도 있습니다.

일반적인 언어에서 interface 로 선언된 클래스의 멤버함수는 모두 순수가상으로만 구성되어야 하지만,

코틀린에서는 본문 구현이 포함된 함수도 포함이 가능합니다.

 

interface 는 생성자를 가질 수 없습니다.

본문구현이 포함된 함수는 자동으로 open 키워드가 붙고,

본문구현이 없다면 자동으로 abstract 키워드가 붙어,

별도의 키워드 (open, abstract ) 가 없어도,

 interface 에 포함된 어떤 함수라도 자식 클래스에서는 override 가능합니다.

그리고 한번에 여러 인터페이스를 상속 받을 수 있어 조금 더 유연한 클래스 구성이 가능합니다.

interface Bark {
    fun sound()
}

interface Runner {
    fun run() {
        println("2 leg")
    }
}

class Dog : Runner, Bark {
    override fun sound() {
        println("mung mung")
    }
    override fun run() {
        println("4 leg")
    }
}

class Person : Runner, Bark {
    override fun sound() {
        println("A,B,C,D say")
    }
}

두개의 interface 를 상속받는 Dog , Person 입니다.

interface 의 선언은 class 의 선언과 동일합니다. 

class 키워드 대신 itnerface 키워드를 사용합니다.

fun main(){
    var mung = Dog()
    var me = Person()

    mung.sound()
    mung.run()

    me.sound()
    me.run()
}

사람은 두다리로 뛰고 개는 4다리를 사용하고, 이런식으로 사용 해 보았습니다.

참조해서 여러 클래스와 interface 를 사용만들어서 구현해 보시길 바랍니다.