设计模式篇(六)——观察者模式

文章目录

    • 一、简介
    • 二、实现
        • 1、一对一的耦合实现
        • 2、一对一的手动耦合实现
        • 3、分析一下为什么叫完全解耦?
        • 4、一对多的实现
        • 5、抽象出来 + 注解 (干货)
    • 三、相关源码
        • 1、RxJava
        • 2、生命周期组件 Lifecycle
    • 四、小结

一、简介

Android 中,一提到 观察者模式(Observer Pattern),肯定联想到 RxJava,这种响应式编程号称是十分解耦的操作,对于这样一个牛逼的设计模式,我们很有理由要好好学习之。

定义: 一个对象通过 “观察” 另一个对象的某个行为,而进行响应操作。

使用场景:如果 观察者模式 用于对象之间存在一对多的关系时,能够发挥最大的优势。在 RecyclerView 中,如果数据发生变化,需要通知列表更新UI;通过观察 Avtivity 的生命周期,来管理一些对象或者逻辑。

实现要素:理解 被观察者观察者订阅 三个概念。

  1. 被观察者: 产生某个事件。
  2. 观察者: 收到 被观察者 的某个事件,从而进行一些处理。
  3. 订阅: 观察者 通过 订阅被观察者,才能收到 被观察者 产生的事件。

观察者模式 清晰的划分两个角色—— 被观察者观察者,这两者完全可以各自独立,不会产生耦合,唯有通过 订阅 这个操作,才会将两个耦合在一起。被观察者 可以自己一个人拼命的产生事件,当我想要得到这个事件的时候,我随时可以将 观察者 通过 订阅 来接收 被观察者 产生的事件。

二、实现

以报社发布新闻,人们订阅报社为例。

1、一对一的耦合实现

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 对象,所以我们没有看到 订阅 这个概念。那么接下来就展示 订阅 的概念。

2、一对一的手动耦合实现

分别有两种不同的表达(两个方法二选一),但是作用都是一样的,都是为了初始化 被观察者 中的 观察者

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("特朗普为抵御新冠病毒,每日一瓶敌敌畏!")
}

3、分析一下为什么叫完全解耦?

想象一下,一家报社每天不停的发新闻,就算没有一个读者,它也不会断更,他不会等待有了第一个读者后,才开始更新。这时候报社的更新行为,与读者没有任何的关联,但是当读者订阅了报社之后,报社的每次新闻内容,就能够传达给读者。

还是看上面的例子,我修改一下 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个月的新闻了:
特朗普为抵御新冠病毒,每日一瓶敌敌畏!

4、一对多的实现

既然是报社,不可能是只为一个人服务的,而是为千千万万个读者服务的。所以我们可以在 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("报社绩效不佳,读者流失惨重!")
}

打印内容:

张三阅读了:后浪们涌起摆摊热潮,程序员们或将辞职摆摊!
张三阅读了:是什么让程序员变得更强?或许是因为发型!
李四阅读了:是什么让程序员变得更强?或许是因为发型!
张三阅读了:报社绩效不佳,读者流失惨重!

5、抽象出来 + 注解 (干货)

接下来看一个骚操作,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 接口,再手动订阅,你甚至在任何一个自定义类中就可以收到消息。

三、相关源码

1、RxJava

RxJava 整个框架就是建立在 观察者模式 上,其使用技巧繁多复杂,令人头晕目眩,但是等你剥开它一层又一层的外衣,你会发现,原来你小子身上就这几根毛呢~

点击查看:五分钟速读RxJava源码

2、生命周期组件 Lifecycle

这里留个坑位,以后会写一篇源码分析文章。

四、小结

许许多多的设计模式都被人挖掘出新的使用方式,但不变的仍然是它的原理,我曾说过无数遍,基于设计模式的基本原则,你想怎么玩就怎么玩。

你可能感兴趣的:(#,设计模式篇)