동아리의 루키 온보딩 활동으로 단순히 개념만 알고 있었던
코틀린의 상속, 추상 클래스, 인터페이스에 대해 더욱 알아보고자 한다.
추상 클래스와 인터페이스에 대해 알아보기 전에 먼저 상속에 대해 알아보자
상속은 기존 클래스의 특성과 기능을 새로운 클래스가 물려받는 것으로
코드의 재사용성을 높이고, 유사한 기능을 가진 클래스들을 효과적으로 관리할 수 있다는 장점이 있다.
그중 기존 클래스를 슈퍼클래스, 기존 클래스를 상속받는 클래스를 서브클래스라고 한다.
코틀린에서 클래스는 기본적으로 final
으로 만약 해당 클래스를 상속받고 싶다면 open
을 붙여줘야 햔다.
open X | open O |
---|
마찬가지로 코틀린의 메소드도 final
이 기본값이어서 서브클래스에서 메소드를 재정의하는 override
를 하고 싶다면 open
을 붙여줘야 한다.
open class Person(val name : String){
open fun eat(){
println("$name eat rice")
}
}
class Student : Person("Kim")
서브클래스를 생성할 때 슈퍼클래스의 매개변수화된 생성자를 사용하려면 서브클래스 정의할 때 입력한다.
만약 미리 정의하지 않고 서브클래스를 생성할 때 서브클래스에서 매개변수로 받고 그것을 슈퍼클래스로 넘겨주고자 한다면
open class Person(val name : String){
open fun eat(){
println("$name eat rice")
}
}
class Student(name : String) : Person(name)
fun main(){
val man = Student("Kim")
man.eat()
}
위와 같이 서브클래스의 파라미터로 받은 값을 다시 슈퍼클래스로 넘겨주면 된다.
여기서 알아야할 것은 오직 한 개의 클래스만을 상속할 수 있다는 것이다.
그렇다면 언제 어떻게 사용될지 몰라 메소드 내부를 미리 정의하지 않거나 여러 개의 클래스를 상속받고 싶다면 어떻게 해야할까?
그 해답이 바로 추상 클래스
와 인터페이스
이다.
추상 클래스는 말 그대로 추상적인 클래스로 아직 명확하게 정의되지 않은 클래스이다.
추상클래스는 일반 클래스와 달리 open
을 사용하지 않아도 되지만 abstract
를 클래스와 메소드에 붙여주어야 한다.
앞서 말했듯 아직 명확하게 정의되지 않은 클래스이기 때문에 추상 클래스를 직접 선언하여 인스턴스를 생성할 수 없다.
또한 추상 클래스 내부의 abstract
가 붙은 추상 메소드들은 반드시 상속받은 서브클래스에서 정의가 되어야 한다.
(그렇지 않다면 위처럼 에러가 발생한다)
그러나 서브클래스에서 추상 클래스의 추상 메소드를 정의하지 않고도 사용할 수 있는 방법이 있다!
바로 object
를 사용하는 것이다.
이번에 공부하면서 처음 알게된 것인데 object를 사용하면 추상메소드를 정의하지 않고도 바로 사용할 수 있다.
이렇게
abstract class Person{
abstract fun eat()
}
fun main(){
val man = object : Person(){
override fun eat() {
println("쿰척쿰척")
}
}
}
추상 클래스는 일정한 기본 특성을 공유하지만, 일부 기능을 명시적으로 정의하도록 강제하고 싶을 때 유용하게 사용할 수 있다.
인터페이스는 추상클래스처럼 인터페이스를 직접 선언하여 인스턴스를 생성할 수 없다.
interface Human {
fun walk()
fun eat(){println("GOOD")}
}
class Male : Human{
override fun walk(){
println("저벅저벅")
}
}
그러나 추상 클래스와는 다르게 abstract
를 사용하지 않고 일반 클래스와도 다르게 open
을 사용하지 않고도 override
할 수 있다.
또한 클래스와 다르게 다중 상속이 가능하다는 장점이 있다
interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C : A {
override fun bar() { print("bar") }
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
override fun bar() {
super<B>.bar()
}
}
하지만 인터페이스는 추상 클래스와 비슷하지만 차이점은 상태를 저장할 수 없다.
위와 같이 값을 저장하면 에러가 발생한다.
이것도 이번에 공부하며 새롭게 알게된 것인데 여기서 또다시 인터페이스에 값을 저장할 수 있는 방법이 있다!
interface Person {
val name: String get() = "Kim"
}
class Male:Person{
}
fun main(){
val man = Male()
println(man.name)
}
이때는 무조건 val로 선언해야한다는 제약이 있긴하지만 위와 같이 get(), 게터로 구현이 가능하다!
1.유연성
-인터페이스를 통해 다중 상속이 가능하므로, 특정 클래스가 여러 동작을 가질 수 있게 할 수 있다.
2.강제성
-추상 클래스와 인터페이스는 구현을 강제할 수 있어, 특정 메소드가 반드시 필요하다는 것을 보장한다.
3.유지보수성
-추상 클래스와 인터페이스를 사용하면 코드가 더 명확해지고 유지보수가 쉬워지고 코드의 재사용성이 높아진다.
https://play.kotlinlang.org/byExample/01_introduction/07_Inheritance
https://kotlinlang.org/docs/interfaces.html#resolving-overriding-conflicts
https://velog.io/@k906506/Kotlin-%EC%B6%94%EC%83%81-%ED%81%B4%EB%9E%98%EC%8A%A4-VS-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4
그거 아시나요 마크다운으로 코드 블럭 만들 때 ```뒤에 언어 이름을 명시하면 코드가 언어에 맞게 하이라이팅 된다는 사실을