상속은 기본적으로 객체 지향 프로그래밍의 핵심 개념 중 하나로, 하나의 클래스가 다른 클래스의 속성 및 메서드를 이어받아 사용하는 것을 의미합니다.
Kotlin에서는 :를 사용해 상속을 표현합니다.
open class Parent {
fun parentFunction() {
println("This is a parent function")
}
}
class Child : Parent()
fun main() {
val child = Child()
child.parentFunction() // This is a parent function
}
✅ 여기서 open 키워드는 Parent 클래스가 상속 가능하다는 것을 나타냅니다. Kotlin에서는 기본적으로 모든 클래스가 final로 선언되어 있어, 상속을 위해서는 open을 명시해야 합니다.
위임은 특정 객체의 기능이나 속성의 사용 권한을 다른 객체에게 넘기는 패턴을 의미합니다. Kotlin에서는 위임 패턴을 쉽게 구현할 수 있도록 by 키워드를 제공합니다.
Kotlin에서 속성 위임은 프로퍼티의 getter와 setter를 다른 객체로 위임하는 것입니다.
class Example {
var p: String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
상속은 "is-a" 관계를 의미합니다. 예를 들어 Bird는 Animal이라고 할 수 있습니다. 하지만 때로는 "has-a" 관계로도 많은 일을 수행할 수 있습니다.
예를 들어, Car는 Engine을 갖고 있다고 할 수 있죠. 위임은 이 "has-a" 관계를 활용하여 특정 객체의 기능을 다른 객체에 위임하는 것을 말합니다.
interface Engine {
fun start()
fun stop()
}
class DieselEngine : Engine {
override fun start() {
println("Diesel Engine started!")
}
override fun stop() {
println("Diesel Engine stopped!")
}
}
class ElectricEngine : Engine {
override fun start() {
println("Electric Engine started!")
}
override fun stop() {
println("Electric Engine stopped!")
}
}
Car 클래스는 Engine의 기능을 필요로 합니다. 그러나 Car 자체가 엔진을 어떻게 시작하고, 어떻게 멈추는지 알 필요는 없습니다. 대신, 그 일을 Engine 구현체에게 위임합니다.
class Car(engine: Engine) : Engine by engine
이렇게 하면 Car는 start와 stop 메서드를 직접 구현할 필요가 없습니다. 대신, 생성 시점에 어떤 엔진을 사용할지 결정하고 그 엔진의 start와 stop을 호출하게 됩니다.
fun main() {
val dieselCar = Car(DieselEngine())
val electricCar = Car(ElectricEngine())
dieselCar.start() // 출력: Diesel Engine started!
electricCar.start() // 출력: Electric Engine started!
dieselCar.stop() // 출력: Diesel Engine stopped!
electricCar.stop() // 출력: Electric Engine stopped!
}
결과적으로 Car는 어떤 종류의 엔진을 갖고 있는지 알 필요가 없습니다. 단순히 주어진 엔진의 기능을 사용(위임)하면 됩니다. 이렇게 위임을 활용하면, 코드의 재사용성이 향상되고 확장성 또한 높아집니다.
inline fun <T : ViewBinding> AppCompatActivity.viewBinding(
crossinline inflater: (LayoutInflater) -> T,
) = lazy(LazyThreadSafetyMode.NONE) {
inflater.invoke(layoutInflater)
}
이 함수는 viewBinding이라는 확장 함수로, AppCompatActivity에 적용됩니다. 제네릭 타입 T를 사용하여 뷰 바인딩의 타입을 유동적으로 받아올 수 있습니다. lazy delegate 활용하여 뷰 바인딩 객체가 필요할 때만 초기화됩니다.
class MainActivity : AppCompatActivity() {
private val binding by viewBinding(ActivityMainBinding::inflate)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
// 나머지 로직
}
}
MainActivity에서는 viewBinding 확장 함수를 활용하여 ActivityMainBinding을 쉽게 초기화할 수 있습니다.
상속은 클래스 간의 관계를 구축하고, 코드 재사용을 가능케 하는 반면, 위임은 코드의 복잡성을 줄이고, 런타임에 동작을 변경하는 등 유연한 코드 구조를 도와줍니다. 이 두 개념은 모두 코드의 재사용성을 향상시키는 데 있어 중요한 역할을 하므로, 적절히 혼용하여 효율적인 코드 설계를 진행하는 것이 중요합니다.