接口和抽象类,到底有啥不一样
写Kotlin的时候,经常遇到需要定义一些通用行为的场景。比如你正在开发一个智能家居系统,灯、空调、窗帘都得能“开关”。这时候你会想:用接口还是抽象类?很多人一开始都搞混,其实它们的定位差得挺远。
接口:定义“能做什么”
接口(interface)就像一份契约,它不关心你怎么实现,只规定你能提供哪些功能。比如所有能开关的设备,都可以实现一个叫Switchable的接口。
interface Switchable {
fun turnOn()
fun turnOff()
}
然后你的台灯类就可以实现它:
class DeskLamp : Switchable {
override fun turnOn() {
println("台灯亮了")
}
override fun turnOff() {
println("台灯关了")
}
}
接口最大的好处是支持多实现。比如窗帘除了能开关,还可能要定时启动,那它可以同时实现Switchable和TimerTask。
抽象类:共享“怎么做”
抽象类(abstract class)更像一个半成品模板,它不仅能定义方法,还能提供部分实现。比如你发现多个设备都有“延时关闭”这种共用逻辑,那就适合放在抽象类里。
abstract class SmartDevice {
abstract fun turnOn()
abstract fun turnOff()
fun delayOff(seconds: Int) {
println("$seconds秒后自动关闭")
// 模拟延时逻辑
}
}
子类继承后,不仅得实现开关方法,还能直接用delayOff这个现成功能。
class CeilingFan : SmartDevice() {
override fun turnOn() {
println("吊扇启动")
}
override fun turnOff() {
println("吊扇停止")
}
}
选择的关键:是“能做什么”还是“怎么做的”
如果你只是想统一调用方式,比如所有可开关设备都能被遥控器控制,那就用接口。如果多个类之间有共用的状态或行为逻辑,比如都带延时、都记录日志,那抽象类更合适。
Kotlin还允许接口有默认实现,这让两者的界限看起来模糊了些。比如:
interface Loggable {
fun log(message: String) {
println("[LOG] $message")
}
}
这样任何实现Loggable的类都自动拥有了log方法,不用强制重写。但这依然不能替代抽象类,因为接口不能保存状态,比如你没法在接口里定义一个计数器变量。
实际开发中,大多数情况优先选接口。它更灵活,也符合Kotlin鼓励组合优于继承的设计哲学。只有当你确实需要共享代码和状态时,才考虑抽象类。