在 Android 中,一提到 观察者模式(Observer Pattern),肯定联想到 RxJava,这种响应式编程号称是十分解耦的操作,对于这样一个牛逼的设计模式,我们很有理由要好好学习之。
定义: 一个对象通过 “观察” 另一个对象的某个行为,而进行响应操作。
使用场景:如果 观察者模式 用于对象之间存在一对多的关系时,能够发挥最大的优势。在 RecyclerView 中,如果数据发生变化,需要通知列表更新UI;通过观察 Avtivity 的生命周期,来管理一些对象或者逻辑。
实现要素:理解 被观察者、观察者 和 订阅 三个概念。
观察者模式 清晰的划分两个角色—— 被观察者 和 观察者,这两者完全可以各自独立,不会产生耦合,唯有通过 订阅 这个操作,才会将两个耦合在一起。被观察者 可以自己一个人拼命的产生事件,当我想要得到这个事件的时候,我随时可以将 观察者 通过 订阅 来接收 被观察者 产生的事件。
以报社发布新闻,人们订阅报社为例。
class Newspaper(private val person: Person) {
fun publishNews(content:String){
person.read(content)
}
}
class Person{
fun read(content: String) {
println(content)
}
}
fun main() {
val person = Person()
val newspaper = Newspaper(person)
newspaper.publishNews("特朗普为抵御新冠病毒,每日一瓶敌敌畏!")
}
如上所示,Newspaper 就是作为被观察者,Person 就是一个观察者,因为在 Newspaper 的构造方法中强耦合了一个 Person 对象,所以我们没有看到 订阅 这个概念。那么接下来就展示 订阅 的概念。
分别有两种不同的表达(两个方法二选一),但是作用都是一样的,都是为了初始化 被观察者 中的 观察者。
class Newspaper() {
var person:Person? = null
/**
* <方法1>
*/
fun addObserver(person:Person){
this.person = person
}
fun publishNews(content:String){
person?.read(content)
}
}
class Person{
/**
* <方法2>
*/
fun subscribe(newspaper: Newspaper){
newspaper.person = this
}
fun read(content: String) {
println(content)
}
}
fun main() {
val person = Person()
val newspaper = Newspaper()
//方法1 被观察者添加观察者
newspaper.addObserver(person)
//方法2 观察者订阅被观察者
person.subscribe(newspaper)
newspaper.publishNews("特朗普为抵御新冠病毒,每日一瓶敌敌畏!")
}
想象一下,一家报社每天不停的发新闻,就算没有一个读者,它也不会断更,他不会等待有了第一个读者后,才开始更新。这时候报社的更新行为,与读者没有任何的关联,但是当读者订阅了报社之后,报社的每次新闻内容,就能够传达给读者。
还是看上面的例子,我修改一下 main 函数:
fun main() {
val person = Person()
val newspaper = Newspaper()
var i = 1
Thread {
while (i < 12) {
println("报社发布第${i}个月的新闻了:")
newspaper.publishNews("特朗普为抵御新冠病毒,每日一瓶敌敌畏!")
i++
Thread.sleep(200)
}
}.start()
Thread {
while (i < 12) {
//等 i == 6 的时候,再使得读者订阅报社
if (i == 6) {
person.subscribe(newspaper)
}else{
Thread.yield()
}
}
}.start()
}
看打印日志:
报社发布第1个月的新闻了:
报社发布第2个月的新闻了:
报社发布第3个月的新闻了:
报社发布第4个月的新闻了:
报社发布第5个月的新闻了:
报社发布第6个月的新闻了:
特朗普为抵御新冠病毒,每日一瓶敌敌畏!
报社发布第7个月的新闻了:
特朗普为抵御新冠病毒,每日一瓶敌敌畏!
报社发布第8个月的新闻了:
特朗普为抵御新冠病毒,每日一瓶敌敌畏!
报社发布第9个月的新闻了:
特朗普为抵御新冠病毒,每日一瓶敌敌畏!
报社发布第10个月的新闻了:
特朗普为抵御新冠病毒,每日一瓶敌敌畏!
报社发布第11个月的新闻了:
特朗普为抵御新冠病毒,每日一瓶敌敌畏!
既然是报社,不可能是只为一个人服务的,而是为千千万万个读者服务的。所以我们可以在 Newspaper 中创建一个数组,来维护读者们。
class Newspaper() {
private val persons = mutableListOf<Person>()
fun addObserver(person: Person){
if (!persons.contains(person)){
persons.add(person)
}
}
fun removeObserver(person: Person){
persons.remove(person)
}
fun publishNews(content: String) {
persons.forEach { person ->
person.read(content)
}
}
}
class Person(val name:String) {
fun read(content: String) {
println("${name}阅读了:${content}")
}
}
fun main() {
val person1 = Person("张三")
val person2 = Person("李四")
val newspaper = Newspaper()
newspaper.addObserver(person1)
newspaper.publishNews("后浪们涌起摆摊热潮,程序员们或将辞职摆摊!")
newspaper.addObserver(person2)
newspaper.publishNews("是什么让程序员变得更强?或许是因为发型!")
newspaper.removeObserver(person2)
newspaper.publishNews("报社绩效不佳,读者流失惨重!")
}
打印内容:
张三阅读了:后浪们涌起摆摊热潮,程序员们或将辞职摆摊!
张三阅读了:是什么让程序员变得更强?或许是因为发型!
李四阅读了:是什么让程序员变得更强?或许是因为发型!
张三阅读了:报社绩效不佳,读者流失惨重!
接下来看一个骚操作,Talk is cheap,show you code:
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class PublishEvent {}
interface Observer {
}
class Person(val name: String) : Observer {
@PublishEvent
fun read(content: String) {
println("${name}阅读了:${content}")
}
fun copy(content: String){
println("${name}抄写了:${content}")
}
@PublishEvent
fun relay(content: String){
println("${name}转发了:${content}")
}
}
class Newspaper() {
private val persons = mutableListOf<Observer>()
fun addObserver(person: Observer) {
if (!persons.contains(person)) {
persons.add(person)
}
}
fun removeObserver(person: Observer) {
persons.remove(person)
}
fun publishNews(content: String) {
persons.forEach { person ->
val publishEventMethods = person.javaClass.methods.filter { method ->
method.annotations.find { annotation ->
annotation is PublishEvent
} != null
}.filter { it.parameterTypes.size == 1 && it.parameterTypes[0] == String::class.java }
publishEventMethods.forEach { method ->
method.invoke(person, content)
}
}
}
}
fun main() {
val person = Person("张三")
val newspaper = Newspaper()
newspaper.addObserver(person)
newspaper.publishNews("后浪们涌起摆摊热潮,程序员们或将辞职摆摊!")
newspaper.publishNews("是什么让程序员变得更强?或许是因为发型!")
newspaper.publishNews("报社绩效不佳,读者流失惨重!")
}
打印内容:
张三阅读了:后浪们涌起摆摊热潮,程序员们或将辞职摆摊!
张三转发了:后浪们涌起摆摊热潮,程序员们或将辞职摆摊!
张三阅读了:是什么让程序员变得更强?或许是因为发型!
张三转发了:是什么让程序员变得更强?或许是因为发型!
张三阅读了:报社绩效不佳,读者流失惨重!
张三转发了:报社绩效不佳,读者流失惨重!
这样写有什么好处呢?
这样写的好处就是:不仅将 被观察者 和 观察者 解耦了,还把书写代码的过程也解耦了,这意味着可以使两个开发者分开完成同一件事情,一个人写 被观察者 的实现(加上一个注解),另一个人写 观察者 的实现(使用注解)。
这在比较大型的框架中有很好的实用性。例如一个团队开发 IM 模块,一批人写 Socket 并且通过注解定义消息类型,设定一个发送事件源的 被观察者 ,另一批人根据定义好的注解,写带有具体业务逻辑的 观察者,两者之间再通过 订阅 连接,岂不美哉~
又因为是使用注解的方式,通过继承空的 Observer 接口,再手动订阅,你甚至在任何一个自定义类中就可以收到消息。
RxJava 整个框架就是建立在 观察者模式 上,其使用技巧繁多复杂,令人头晕目眩,但是等你剥开它一层又一层的外衣,你会发现,原来你小子身上就这几根毛呢~
点击查看:五分钟速读RxJava源码
这里留个坑位,以后会写一篇源码分析文章。
许许多多的设计模式都被人挖掘出新的使用方式,但不变的仍然是它的原理,我曾说过无数遍,基于设计模式的基本原则,你想怎么玩就怎么玩。