1、什么是托付?
托付,又名托付形式是一种常用的设计形式,它能够让一个目标在不改动自己原有的行为的前提下,将某些特定的行为托付给另一个目标来完成。它经过将目标之间的关系分离,能够降低系统的耦合度,进步代码的复用性和可维护性。
其间有三个角色,束缚、托付目标和被托付目标。
- 束缚: 一般为接口也能够是笼统类,界说了某个行为。
- 被托付目标: 担任履行具体的行为。
- 托付目标: 担任将束缚中界说的行为交给被托付目标。
2、Java中的托付
先来说一说托付在Java中的应用用一个简略的比方来阐明:
老板
在创业初期时由于只有一个人而需求担任产品的客户端
、UI
、服务器
。
这个时分老板
担任的这些作业就能够被笼统出来构成一个束缚接口:
public interface Work {
void app();
void ui();
void service();
}
public class Boss implements Work {
@Override
public void app() {
System.out.println("Boss doing app");
}
@Override
public void ui() {
System.out.println("Boss doing ui");
}
@Override
public void service() {
System.out.println("Boss doing service");
}
}
现在老板
每天都在做这几件事:
public class Main {
public static void main(String[] args) {
Boss boss = new Boss();
boss.app();
boss.ui();
boss.service();
}
}
输出:
Boss doing app
Boss doing ui
Boss doing service
命运不错,产品赚了不少钱,老板
花钱雇了一个职工
,将这些作业托付给他处理,自己直接脱产,只需求知道成果就能够了,所以就有了:
public class Employee implements Work{
@Override
public void app() {
System.out.println("Employee doing app");
}
@Override
public void ui() {
System.out.println("Employee doing ui");
}
@Override
public void service() {
System.out.println("Employee doing service");
}
}
public class Boss implements Work{
private Employee employee;
public Boss(Employee employee) {
this.employee = employee;
}
@Override
public void app() {
employee.app();
}
@Override
public void ui() {
employee.ui();
}
@Override
public void service() {
employee.service();
}
}
public class Main {
public static void main(String[] args) {
Boss boss = new Boss(new Employee());
boss.app();
boss.ui();
boss.service();
}
}
Employee doing app
Employee doing ui
Employee doing service
这便是一个托付形式,老板
(托付目标)将 作业
(束缚)托付给 职工
(被托付者)处理,老板
并不关怀每项作业具体是怎么完成的,职工
在完成作业后也会和老板
报告,就算这几项作业内容发生变化也仅仅职工
需求处理。
3、Kotlin中的托付
那么针对上述的托付所描绘比方在Kotlin中是怎么完成的呢?
答案是运用关键字by,Kotlin专门推出了by来完成托付:
上述比方中的作业
和职工
都不变:
interface Work {
fun app()
fun ui()
fun service()
}
class Employee : Work {
override fun app() {
println("Employee doing app")
}
override fun ui() {
println("Employee doing ui")
}
override fun service() {
println("Employee doing service")
}
}
在老板
这个类中,咱们要将作业
运用关键字by托付给职工
class Boss(private val employee: Employee) : Work by employee
就这么一行,完成了Java代码中老板
类的作用。
fun main(args: Array<String>) {
val boss = Boss(Employee())
boss.app()
boss.ui()
boss.service()
}
成果肯定是相同的。
那么by是怎么完成Java中托付的作用的呢?经过反编译Kotlin字节码后咱们看到:
public final class Boss implements Work {
private final Employee employee;
public Boss(@NotNull Employee employee) {
Intrinsics.checkNotNullParameter(employee, "employee");
super();
this.employee = employee;
}
public void app() {
this.employee.app();
}
public void service() {
this.employee.service();
}
public void ui() {
this.employee.ui();
}
}
其实便是Java中完成托付的代码,Kotlin将它包成一个关键字by,效率大幅进步。
4、特点托付
上述阐明的托付都属于类托付,而在Kotlin傍边by不只能够完成类托付,还能够完成特点托付,特点托付为Kotlin的一大特性,将对特点的拜访托付给另一个目标。运用特点托付能够让咱们编写更简练、更模块化的代码,而且能够进步代码的可重用性。
4.1 怎么完成特点托付?
Kotlin官方文档中给出了界说:
运用办法:val/var <特点名>: <类型> by <表达式>
。
在by后边的表达式是该托付, 特点对应的get()
和set()
会被托付给它的getValue()
与setValue()
办法。 假如该特点是只读的(val)其托付只需求供给一个getValue()
函数假如该特点是var则还需求供给setValue()
函数。例如:
class Example {
var str: 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.")
}
}
fun main(args: Array<String>) {
val p = Example()
p.str = "Hello"
println(p.str)
}
由于特点str是可变的所以在Delegate类中完成了getValue和setValue两个函数,其间一共出现了三个参数分别是
-
thisRef :读出
str
的目标 -
property :保存了对
str
自身的描绘 (例如你能够取它的名字) - value :保存将要被赋予的值
运转成果如下:
Hello has been assigned to 'str' in Example@1ddc4ec2.
Example@1ddc4ec2, thank you for delegating 'str' to me!
咱们再将Example类中的代码转为Kotlin字节码反编译得到以下代码:
public final class Example {
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{Reflection.mutableProperty1(new MutablePropertyReference1Impl(Example.class, "str", "getStr()Ljava/lang/String;", 0))};
@NotNull
private final Delegate str$delegate = new Delegate();
@NotNull
public final String getStr() {
return this.str$delegate.getValue(this, $$delegatedProperties[0]);
}
public final void setStr(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.str$delegate.setValue(this, $$delegatedProperties[0], var1);
}
}
便是创建了一个Delegate目标,再经过调用setVaule和getValue一对办法来获取和设置值的。
4.2 标准托付
在Kotlin标准库为托付供给了几种办法
4.2.1 推迟特点 Lazy
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
首次拜访特点时才进行初始化操作,lazy()
是承受一个 lambda 并回来一个Lazy <T>
实例的函数,回来的实例能够作为完成推迟特点的托付, 该lambda表达式将在第一次拜访该特点时被调用,初始化特点并回来特点值,之后的拜访将直接回来初始化后的值。
简略的比方:
fun main(args: Array<String>) {
val str : String by lazy {
println("Hello str")
"lazy"
}
println(str)
println(str)
}
输出:
Hello str//只在第一次拜访时履行
//后续拜访只回来值
lazy
lazy
当咱们运用 by lazy
托付完成推迟初始化时,Kotlin 编译器会生成一个私有的内部类,用于完成托付特点的懒加载逻辑,其内部包含一个名为 value
的特点,用于存储真实的特点值。同时,还会生成一个名为 isInitialized
的私有 Boolean 特点,用于标识特点是否现已初始化。
当咱们首次拜访被 lazy
修饰的特点时,假如它还未被初始化,就会调用 lazy
所接纳的 lambda 表达式进行初始化,并将成果保存在 value
特点中。之后,每次拜访该特点时,都会回来 value
中存储的特点值。
4.2.2 可调查特点 Observable
Delegates.observable()
承受两个参数:初始值与修改时处理程序(handler)。 每当咱们给特点赋值时会调用该处理程序(在赋值后履行)。它有三个参数:被赋值的特点、旧值与新值:
class User {
var name : String by Delegates.observable("no value") {
property, oldValue, newValue ->
println("property :${property.name}, old value $oldValue -> new value $newValue")
}
}
fun main() {
val user = User()
user.name = "Alex"
user.name = "Bob"
}
property :name, old value no value -> new value Alex
property :name, old value Alex -> new value Bob
假如你想截获赋值并“否决”它们,那么运用vetoable()
取代observable()
。 在特点被赋新值收效之前会调用传递给vetoable
的处理程序,简略来说便是运用你设定的条件来决议设定的值是否收效,仍是以上述代码为例,在User中增加一个年纪特点:
var age : Int by Delegates.vetoable(0) {
_, oldValue, newValue ->
println("old value : $oldValue, new value : $newValue")
newValue > oldValue
}
在这里咱们设定了输入的年纪大于现在的年纪才收效,运转一下看看输出什么:
old value : 0, new value : 20
20
old value : 20, new value : 19
20
old value : 20, new value : 25
25
0
old value : 0, new value : 20
20
old value : 20, new value : 19
20
old value : 20, new value : 25
25
4.2.3 将特点储存在映射中
映射(map)里存储特点的值。 这常常出现在像解析 JSON 或许做其他“动态”事情的应用中。 在这种情况下,你能够运用映射实例自身作为托付来完成托付特点。
class User(map: MutableMap<String, Any?>) {
val name: String by map
val age: Int by map
}
fun main(args: Array<String>) {
val user = User(
mutableMapOf(
"name" to "Alex",
"age" to 18
)
)
println("name : ${user.name}, age : ${user.age}")
}
输出:
name : Alex, age : 18
5、总结
托付是一种常见的软件设计形式,旨在进步代码的复用性和可维护性,在 Java 中,托付经过界说接口和完成类来完成。完成类持有接口的实例,并将接口的办法托付给实例来完成。这种办法能够完成代码的复用宽和耦,但是需求手动完成接口中的办法,比较繁琐,而在 Kotlin 中,托付经过by关键字完成托付其间还包含了特点托付一大特性,Kotlin 供给了许多内置的特点托付,比方推迟特点、映射特点等。此外,Kotlin 还支撑自界说特点托付。自界说特点托付需求完成 getValue
和 setValue
办法,用于获取和设置特点的值,与 Java 的托付相比,Kotlin 的特点托付愈加便利和简练,削减样板代码。
6、感谢
- 校稿:ChatGpt
- 文笔优化:ChatGpt
参阅:Kotlin官方文档:托付 ,Kotlin官方文档:特点托付