7.4.1 接口定义
和Java类似,Kotlin使用interface作为接口的关键词:
interface ProjectService
Kotlin 的接口与 Java 8 的接口类似。与抽象类相比,他们都可以包含抽象的方法以及方法的实现:
interface ProjectService {
val name: String
val owner: String
fun save(project: Project)
fun print() {
println("I am project")
}
}
7.4.2 实现接口
接口是没有构造函数的。我们使用冒号: 语法来实现一个接口,如果有多个用,逗号隔开:
class ProjectServiceImpl : ProjectService
class ProjectMilestoneServiceImpl : ProjectService, MilestoneService
我们也可以实现多个接口:
class Project
class Milestone
interface ProjectService {
val name: String
val owner: String
fun save(project: Project)
fun print() {
println("I am project")
}
}
interface MilestoneService {
val name: String
fun save(milestone: Milestone)
fun print() {
println("I am Milestone")
}
}
class ProjectMilestoneServiceImpl : ProjectService, MilestoneService {
override val name: String
get() = "ProjectMilestone"
override val owner: String
get() = "Jack"
override fun save(project: Project) {
println("Save Project")
}
override fun print() {
// super.print()
super<ProjectService>.print()
super<MilestoneService>.print()
}
override fun save(milestone: Milestone) {
println("Save Milestone")
}
}
当子类继承了某个类之后,便可以使用父类中的成员变量,但是并不是完全继承父类的所有成员变量。具体的原则如下:
1.能够继承父类的public和protected成员变量;不能够继承父类的private成员变量;
2.对于父类的包访问权限成员变量,如果子类和父类在同一个包下,则子类能够继承;否则,子类不能够继承;
3.对于子类可以继承的父类成员变量,如果在子类中出现了同名称的成员变量,则会发生隐藏现象,即子类的成员变量会屏蔽掉父类的同名成员变量。如果要在子类中访问父类中同名成员变量,需要使用super关键字来进行引用。
7.4.3 覆盖冲突
在kotlin中, 实现继承通常遵循如下规则:如果一个类从它的直接父类继承了同一个函数的多个实现,那么它必须重写这个函数并且提供自己的实现(或许只是直接用了继承来的实现) 为表示使用父类中提供的方法我们用 super 表示。
在重写print()时,因为我们实现的ProjectService、MilestoneService都有一个print()函数,当我们直接使用super.print()时,编译器是无法知道我们想要调用的是那个里面的print函数的,这个我们叫做覆盖冲突:
这个时候,我们可以使用下面的语法来调用:
super<ProjectService>.print()
super<MilestoneService>.print()
7.4.4 接口中的属性
在接口中声明的属性,可以是抽象的,或者是提供访问器的实现。在企业应用中,大多数的类型都是无状态的,如:Controller、ApplicationService、DomainService、Repository等。因为接口没有状态, 所以它的属性是无状态的。
interface MilestoneService {
val name: String // 抽象的
val owner: String get() = "Jack" // 访问器
fun save(milestone: Milestone)
fun print() {
println("I am Milestone")
}
}
class MilestoneServiceImpl : MilestoneService {
override val name: String
get() = "MilestoneServiceImpl name"
override fun save(milestone: Milestone) {
println("save Milestone")
}
}
7.5 抽象类和接口的差异
概念上的区别
1.接口主要是对动作的抽象,定义了行为特性的规约。
2.抽象类是对根源的抽象。当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。
语法层面上的区别
1.接口不能保存状态,可以有属性但必须是抽象的。
2.一个类只能继承一个抽象类,而一个类却可以实现多个接口。
3.类如果要实现一个接口,它必须要实现接口声明的所有方法。但是,类可以不实现抽象类声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
4.接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。
设计层面上的区别
1.抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
2.继承是 is a的关系,而 接口实现则是 has a 的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现就不需要有这层类型关系。
设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。也就是说:
对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;
而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。
实际应用上的差异
在实际使用中,使用抽象类(也就是继承),是一种强耦合的设计,用来描述A is a B 的关系,即如果说A继承于B,那么在代码中将A当做B去使用应该完全没有问题。比如在Android中,各种控件都可以被当做View去处理。
如果在你设计中有两个类型的关系并不是is a,而是is like a,那就必须慎重考虑继承。因为一旦我们使用了继承,就要小心处理好子类跟父类的耦合依赖关系。组合优于继承。