7.4 接口

7.4.1 接口定义

和Java类似,Kotlin使用interface作为接口的关键词:

  1. interface ProjectService

Kotlin 的接口与 Java 8 的接口类似。与抽象类相比,他们都可以包含抽象的方法以及方法的实现:

  1. interface ProjectService {
  2. val name: String
  3. val owner: String
  4. fun save(project: Project)
  5. fun print() {
  6. println("I am project")
  7. }
  8. }

7.4.2 实现接口

接口是没有构造函数的。我们使用冒号: 语法来实现一个接口,如果有多个用逗号隔开:

  1. class ProjectServiceImpl : ProjectService
  2. class ProjectMilestoneServiceImpl : ProjectService, MilestoneService

我们也可以实现多个接口:

  1. class Project
  2. class Milestone
  3. interface ProjectService {
  4. val name: String
  5. val owner: String
  6. fun save(project: Project)
  7. fun print() {
  8. println("I am project")
  9. }
  10. }
  11. interface MilestoneService {
  12. val name: String
  13. fun save(milestone: Milestone)
  14. fun print() {
  15. println("I am Milestone")
  16. }
  17. }
  18. class ProjectMilestoneServiceImpl : ProjectService, MilestoneService {
  19. override val name: String
  20. get() = "ProjectMilestone"
  21. override val owner: String
  22. get() = "Jack"
  23. override fun save(project: Project) {
  24. println("Save Project")
  25. }
  26. override fun print() {
  27. // super.print()
  28. super<ProjectService>.print()
  29. super<MilestoneService>.print()
  30. }
  31. override fun save(milestone: Milestone) {
  32. println("Save Milestone")
  33. }
  34. }

当子类继承了某个类之后,便可以使用父类中的成员变量,但是并不是完全继承父类的所有成员变量。具体的原则如下:

1.能够继承父类的public和protected成员变量;不能够继承父类的private成员变量;

2.对于父类的包访问权限成员变量,如果子类和父类在同一个包下,则子类能够继承;否则,子类不能够继承;

3.对于子类可以继承的父类成员变量,如果在子类中出现了同名称的成员变量,则会发生隐藏现象,即子类的成员变量会屏蔽掉父类的同名成员变量。如果要在子类中访问父类中同名成员变量,需要使用super关键字来进行引用。

7.4.3 覆盖冲突

在kotlin中, 实现继承通常遵循如下规则:如果一个类从它的直接父类继承了同一个函数的多个实现,那么它必须重写这个函数并且提供自己的实现(或许只是直接用了继承来的实现) 为表示使用父类中提供的方法我们用 super 表示。

在重写print()时,因为我们实现的ProjectService、MilestoneService都有一个print()函数,当我们直接使用super.print()时,编译器是无法知道我们想要调用的是那个里面的print函数的,这个我们叫做覆盖冲突:

Kotlin极简教程

这个时候,我们可以使用下面的语法来调用:

  1. super<ProjectService>.print()
  2. super<MilestoneService>.print()

7.4.4 接口中的属性

在接口中声明的属性,可以是抽象的,或者是提供访问器的实现。

在企业应用中,大多数的类型都是无状态的,如:Controller、ApplicationService、DomainService、Repository等。

因为接口没有状态, 所以它的属性是无状态的。

  1. interface MilestoneService {
  2. val name: String // 抽象的
  3. val owner: String get() = "Jack" // 访问器
  4. fun save(milestone: Milestone)
  5. fun print() {
  6. println("I am Milestone")
  7. }
  8. }
  9. class MilestoneServiceImpl : MilestoneService {
  10. override val name: String
  11. get() = "MilestoneServiceImpl name"
  12. override fun save(milestone: Milestone) {
  13. println("save Milestone")
  14. }
  15. }

7.5 抽象类和接口的差异

概念上的区别

接口主要是对动作的抽象,定义了行为特性的规约。
抽象类是对根源的抽象。当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。

语法层面上的区别

接口不能保存状态,可以有属性但必须是抽象的。
一个类只能继承一个抽象类,而一个类却可以实现多个接口。

类如果要实现一个接口,它必须要实现接口声明的所有方法。但是,类可以不实现抽象类声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。

接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。

设计层面上的区别

抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。

继承是 is a的关系,而 接口实现则是 has a 的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现就不需要有这层类型关系。

设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。也就是说:

  • 对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;

  • 而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。

    实际应用上的差异

在实际使用中,使用抽象类(也就是继承),是一种强耦合的设计,用来描述A is a B 的关系,即如果说A继承于B,那么在代码中将A当做B去使用应该完全没有问题。比如在Android中,各种控件都可以被当做View去处理。

如果在你设计中有两个类型的关系并不是is a,而是is like a,那就必须慎重考虑继承。因为一旦我们使用了继承,就要小心处理好子类跟父类的耦合依赖关系。组合优于继承。