Kotlin中的观察者模式

观察者模式,也称为发布/订阅模式、信号槽模式。时间驱动架构的一部分。在本博客中,我们将向您展示如何在Kotlin中实现观察者模式。此外,我们还将了解Kotlin原生提供哪些功能以及如何在Android应用程序开发生态系统中使用它。

定义对象之间的一对多依赖关系,以便当一个对象更改状态时,其所有依赖项都会受到通知并自动更新。
GoF------设计模式

1、Kotlin中的观察者模式示例

为了演示这种设计模式的用法,我们将描述现实世界中的一个简单用例。想象一个温度传感器每x秒测量一次温度。这个数字是任意的,但我们并不能真正控制它。我们有一个小屏幕,显示传感器输入的温度。为此,它必须向传感器询问温度。

监视器必须重复调用传感器才能显示正确的温度。问题很明显:监视器需要计时,监视器必须重复调用传感器才能显示正确的温度。问题很明显:显示器需要与传感器的读取速率完美同步。如果速度更快(监视器询问的频率比温度变化的频率更高),则它会使用太多资源。如果速度较慢,则温度更新可能不够快。当前描述的系统是所谓的"拉动机制"。

1.1、标准实施

使用观察者模式,可以将拉式系统恢复为推式机制。在这种情况下,传感器将从名为Subject的基类继承。观察者可以附加/分离到一个主题。主题可以通知所有附加的观察者,其内部状态已经改变。它将调用抽象方法update(),该方法必须由Monitor实现。其关系如下面的UML图所示。

kotlin 复制代码
// Standard Observer Pattern

open class Subject {
    private var observers = mutableListOf<Observer>()
    
    fun callObservers() {
        for(obs in observers) obs.update()
    }
    
    fun attach(obs: Observer) {
        observers.add(obs)
    }
    
    fun detach(obs: Observer) {
        observer.remove(obs)
    }
}

interface Observer {
    fun update()
}

class Sensor: Subject() {
    var temperature: Int = 0
        set(value) {
            field = vale
            callObservers()
        }
}

class Monitor(val sensor: Sensor): Observer {
    init {
        sensor.attach(this)
    }
    
    override fun update() {
        val newTemperature = sensor.temperature
        println("update Monitor")
    }
}

fun main() {
    val sensor = Sensor()
    val monitor = Monitor(sensor)
    
    sensor.temperature = 5
}

这是发布/订阅模式最简单的形式。它已经达到了实现推送机制的目的。然而,订阅的队形并不知道主题的哪一部分发生了变化。如果您需要更多控制,您应该考虑信号槽实现。

1.2、Kotlin中的内置可观察委托

Kotlin提供了一些内置功能。下面这个方式通过委托包装属性。每次观察到有值发生变化时,委托都会给出一个回调函数。一个简单的例子可能看起来像这样。

kotlin 复制代码
class Sensor {
    var temperature: Int by Delegates.observable(0) { property, oldValue, newValue -> onChange() }
    
    private fun onChange() {
    
    }
}

如果您需要在值更改时触发操作,这非常有用。这种方法的要点是两个值(新值和旧值)都可用。这增加了灵活性。然而,在大多数情况下,您会发现可以轻松调用内部成员和属性,但很难调用外部观察者。在我们的示例中,我们有一对多的关系。传感器需要有一个正在监听状态变化的对象列表。因此,最终您将再次需要与标准实现中相同的接口和基类。委托教程中阅读有关委托的更多信息

1.3、信号-时隙机制

观察者模式的常见实现是信号槽机制。这样,observable就是一个模板类,成为信号。每个其他类都可以有signal对象(组合)。其他客户端(例如类)可以通过提供适合所需接口的插槽来连接到信号。槽通常是与信号具有相同接口的函数。上面的例子可以通过信号槽以下面的方式实现。

kotlin 复制代码
// Signal - Slot
class Signal<TType> {
    class Connection
    
    val callbacks = mutableMapOf<Connection, (TType) -> Unit>()
    
    val emit(newValue: TType) {
        for(cb in callbacks) cb.value(newValue)
    }
    
    fun connect(callbask: (newValue: TType) -> Unit): Connection {
        val connection = Connection()
        callbacks[connection] = callback
        return connection
    }
    
    fun disconnect(connection: Connection) {
        callbacks.remove(connection)
    }
}

class Sensor {
    val temperatureChanged = Signal<Int>()
}

class Monitor {
    fun onTemperatureChanged(newTemperature: Int) {
        // doSomething
    }
}

fun main() {
    val sensor = Sensor()
    val monitor = Monitor()
    sensor.tempChanged.connect(monitor::onTemperatureChanged)
    
    sensor.tempChanged.emit(5)
}

这个信号槽示例非常简单,Signal是一个模板,有3个方法。connect方法项所需的接口注册回调。它返回一个Connection对象(这里是一个空类)。该Connection对象对于避免潜在的内存泄露是必要的,因为信号有效地增加了连接观察者的引用计数。要释放回调,可以调用disconnect()emit函数正在使用新值调用所有回调。此实现并未改进性能,但它显示了信号时隙实现的要点。

这种实现的优点是每个对象都可以变得可观察并且每个对象都可以被观察,而不需要继承特定的接口。它更倾向于组合而不是继承。其次,信号不仅可以在制发生变化时调用,还可以在不同情况下调用。第三,它提供了连接插槽的灵活性。上面的例子可以连接到:

kotlin 复制代码
// Slot variants
val sensor = Sensor(0

// Connect member function
val monitor = Monitor()
sensor.temperatureChanged.connect(monitor::onTemperatureChanged)

// Connect anonymous function
sensor.temperatureChanged.connect(fun (newValue: Int) { println("anonymous function")})

//Connect lambda
sensor.temperatureChanged.connect(newValue: Int -> println("lambda function")}

2、Android应用程序开发

在上面的部分中,我们介绍了Kotlin中订阅/发布模式的一般用例。在本节中,我们将重点关注Android应用程序生态系统。

那么Android中的观察者是什么?典型的用例是什么?

观察者模式通常与MVC(模型-视图-控制器)或MVVM(模型-视图-视图模型)架构结合使用。它工作得很好,因为一旦域对象发生更改,UI就会自动更新。这通常与属性绑定一起应用。

2.1、OnClickListener/处理程序

一个常见的用例是OnClickListenenr,它通常也称为处理应用。它们出现在用户可以单击的UI组件中。您可以通过监听器(观察者)设置为按钮来定义需要执行的操作。下面的代码说明了这一点。Kotlin提供了多种附加监听器的方法。当前示例使用lambda函数。

kotlin 复制代码
class MainActivity: AppCompatActivity() {
    private lateinit var button: Button
    private lateinit var text: TextView
    
    override fun onCreate(savedInstanceStae: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        button = findViewById(R.id.myButton)
        text = findViewById(R.id.myTextView)
        
        // By lambda
        button.setOnClickListener {
            text.text = "Clicked"
        }
    }
}

2.2、JetPack - LiveData

Android Jetpack库还提供了观察者模式的实现 ------ LiveData。

过去,LiveData经常与CoRoutines一起使用在函数中。现在,对于新的Kotlin流程,问题是:

LiveData是否已弃用?

我们可以说,对于仅适用Kotlin的应用程序,当它用于创魔更改协程时肯定是这种情况。然而,对于常见的MVVM或MVC应用程序,它仍然被频繁使用。因此我们将使用LiveData来展示上面的示例。

那么,如何在Kotlin中观察实时数据呢?首先,您需要调整Gradle脚本以访问LiveData实现。

arduino 复制代码
implementation 'androidx.lifecycle:lifecycle-livedata:2.3.0'

这样,您应该能够包含模板化的类。对于我们的"单击按钮"示例,我们将添加一个新的文本字段。单击该按钮后,LiveData对象的值将会更改。该对象由两个已注册的监听器。两者都会更改文本字段之一的文本。这只是为了表明通过这种方法我们可以将多个对象注册到一个主题。

kotlin 复制代码
class MainActivity: AppCompatActivity() {
    private lateinit var button: Button
    private lateinit var text: TextView
    private lateinit var otherText: TextView
    
    private var wasClicked = MutableLiveData<Boolean>()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        button = findViewById(R.id.myButton)
        text = findViewById(R.id.myTextView)
        otherText = findViewById(R.id.otherTextView)
        
        button.setOnClickListener {
            wasClicked.value = true
        }
        
        wasClicked.observe(this, {
            text.text = "Clicked"
        })
        
        wasClicked.observe(this, {
            otherText.text = "Also Clicked"
        })
    }
}
相关推荐
血不热了1 小时前
Qt:静态局部变量实现单例(附带单例使用和内存管理)
开发语言·qt·设计模式
仙魁XAN2 小时前
Unity 设计模式 之 结构型模式 -【装饰者模式】【外观模式】【享元模式】【代理模式】
unity·设计模式·代理模式·享元模式·外观模式·装饰者模式
约翰先森不喝酒4 小时前
Android RecyclerView 实现 GridView ,并实现点击效果及方向位置的显示
android
南郁4 小时前
把设计模式用起来!(4) 用不好模式?之原理不明
java·开发语言·设计模式
codelife3214 小时前
设计模式——对象池模式
数据库·设计模式·oracle
胡耀超4 小时前
0.设计模式总览——设计模式入门系列
java·开发语言·设计模式
wk灬丨5 小时前
Android Choreographer 监控应用 FPS
android·kotlin
大胃粥5 小时前
Android U WMS : Activity 冷启动(2) 添加启动窗口
android
胡耀超5 小时前
4.结构型设计模式 - 第1回:引言与适配器模式 (Adapter Pattern) ——设计模式入门系列
java·设计模式·适配器模式
刷帅耍帅5 小时前
java设计模式-适配器模式
java·设计模式·适配器模式