Utils 工具类是无构造参数的 static 方法集合,用于扩展某个对象的功能,如 MathUtils,ToastUtils,FIleUtils,StringUtils, LogUtils。Utils 类在一定程度上减少了重复代码的问题,它是成本最低的 DRY(Don’t repeat yourself)实践。
Utils 工具类实在太常见了,以至于很多开发者都不曾质疑他的合理性。但 Utils 实际上是反 OOP (面向对象模式)的妥协产物。我们从代码设计的角度看,Utils 方法是 static 的,没有 OOP 的继承,重写,抽象的特性(static 本身就是反 OOP 的)。且 Utils 违反了单一职责,一个类应该包含其属性和所有操作方法。而 Utils 实现的方法并不在这个类内。
而从使用者的角度,使用者必须预先知道这个 Utils 工具类的存在,他能使用为这个类添加的扩展方法。 在实际项目实践中,这个条件往往是缺失的,因为在团队开发中,个人无法掌握所有代码,因为不知道这个代码已经有人实现过了,导致大家都实现了自己的 Utils。一个工程里同一个类的 Utils 往往会有好几个。
但存在必然是合理的。我自己就是一个写 Utils 的老司机。从个人角度来看,让我使用 Utils 而不是对象继承的原因,主要是因为:
1. 无法继承/重写这些类及其方法,只能通过 Utils 扩展;
2. 继承一个类比抽取代码块封装为函数的实现成本+替换成本高;
3. Utils 绝大情况下只是用来存储代码块,需求非常稳定,无需面向对象。
依赖的类是 SDK 提供的时候 Utils 往往是不可避免的。且使用 Utils 的场景里很少会用到面向对象的特性,那么没有面向对象的缺点也并没有那么严重了。那么抛开 Utils 的设计缺点,我们是否可以避免使用上的缺点?Kotlin 提供的解决方法就是扩展(extension)。
声明一个 Kotlin 扩展如下:
// StringUtils.kt
fun String.appendHaha(): String {
return this + "haha"
}
它与普通的方法声明很接近,只是方法名前多了一个类名,来表示其归属的类。扩展声明为顶层声明的时候可以被外部调用(是的,因为函数是一等公民,在方法内部也可以声明扩展方法)。
在函数体内用 this 来引用调用的实例,属性和方法的访问权限与普通调用一致。一致的意思是和你正常在其他方法内部调用的权限一致,并不会因为是扩展声明就可以访问 private/propect 权限的属性和方法。这是因为扩展声明在字节码层面上其实是 static 方法。下面是appendHaha
对应 jvm 字节码的反编译结果:
public class StringUtilsKt {
@NotNull
public static final String haha(@NotNull String $this$haha) {
Intrinsics.checkParameterIsNotNull($this$haha, "$this$haha");
return $this$haha + "haha";
}
}
所以从 Java 的角度来看,Kotlin 的扩展方法和 Utils 的调用没有差别,都是调用类的 static 方法然后传入操作的参数。实际上 Java 想要调用 Kotlin 的扩展方法也确实是这样调用的。
扩展方法的调用和实例方法调用一致,在调用者角度没有区别。 Android Studio 会自动提示对应类所有的扩展方法,且扩展方法的颜色(黄色)会和普通实例方法(白色)区分开来。
Kotlin 的扩展特性和 objective-C 的 category 特性功能非常相似,都是为一个现有的类添加方法(且只能添加方法),只是代码组织结构上有些许差异。但 objective-C 的 category 特性是 runtime 特性,Kotlin 扩展的实现更接近语法糖。
Kotlin 扩展依然没有解决 Utils 类的设计缺点。就像 Kotlin companion object 对 Java static,Kotlin Int 对 Java int,Kotlin property 对 Java field 一样,Kotlin 扩展是 Kotlin 对 Java 不完全面向对象的“清理”,使 Kotlin 更接近完全面向对象。相比 Utils 工具类,Kotlin 扩展特性能有效减少开发者心智负担和沟通成本,从而提高开发效率。