观察者模式

205 阅读18分钟

安卓开发中的观察者模式

一、什么是观察者模式?

1.概念

观察者模式(Observer Pattern)是一种对象行为模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会接到通知,并更新自己。

观察者模式主要解决的是当对象间存在一对多关系时,当一个对象被修改,会自动通知依赖它的其它对象。

观察者模式包括了可观察对象(Observable,生产者、发射者、源这些称呼都是指可观察对象,可以被观察)和观察对象(Observers,订阅者、收集者、接收者这些称呼都是指观察对象,可以观察Observable)。

2.结构

观察者模式的基本结构如下图所示: uml图

在这个结构中,有以下几个角色:

Subject(主题): 是一个抽象类或接口,它定义了一个Observer列表和一些用于添加、删除和通知Observer的方法; ConcreteSubject(具体主题): 是Subject的实现类,它维护了自己的状态,并在状态改变时通知所有注册的Observer; Observer(观察者): 是一个抽象类或接口,它定义了一个用于接收Subject通知的方法; ConcreteObserver(具体观察者): 是Observer的实现类,它实现了自己感兴趣的逻辑,并在接收到Subject通知时更新自己;

二、为什么要使用观察者模式?

在安卓开发中,我们经常需要处理各种事件和数据变化,并及时*更新界面或执行相应的逻辑。例如:

  • 用户点击按钮或滑动屏幕时,需要响应用户操作并改变视图状态;
  • 网络请求返回数据时,需要将数据显示在界面上或存储到数据库中;
  • 数据库中的数据发生变化时,需要通知界面刷新或执行其他操作;
  • 应用程序状态或配置发生变化时,需要通知相关组件做出调整;

这些场景都涉及到了一个或多个源头(Observable)和一个或多个目标(Observer),并且源头和目标之间存在着动态的依赖关系。如果我们不使用设计模式来管理这种关系,我们可能会遇到以下问题:

  • 源头和目标之间耦合度高,难以复用和扩展;
  • 源头需要维护目标列表,并且在每次状态改变时遍历列表进行通知;
  • 目标需要主动向源头注册和注销自己,并且在接收到通知时判断是否有效;
  • 源头和目标之间可能存在循环依赖或内存泄漏等问题;

使用观察者模式可以有效地解决这些问题。通过定义一个抽象的Subject接口和一个抽象的Observer接口,并让具体的源头类实现Subject接口, 具体的目标类实现Observer接口, 我们就可以将源头和目标之间解耦, 并且让源头只负责维护一个Observer列表, 而让目标只负责实现自己感兴趣

三、安卓源码中的实例

安卓源码中有很多使用了观察者模式的实例,例如:

  • java.util.Observablejava.util.Observer类:这是Java提供的标准库类,用于实现简单的观察者模式。Observable类封装了维护和通知观察者列表的逻辑,而Observer接口定义了接收更新通知的方法。

  • android.database.Observableandroid.database.DataSetObserver类:这是安卓提供的专门用于数据集变化通知的类。Observable类继承自Java标准库中的同名类,并添加了注册和注销具体类型(如DataSetObserver)的方法。DataSetObserver类实现了Java标准库中的Observer接口,并定义了不同类型(如数据集改变、无效、打开等)的更新方法。

  • android.arch.lifecycle.LiveData<T>android.arch.lifecycle.Observer<T>类:这是安卓架构组件库提供 的用于在组件之间传递数据和通知变化的类。LiveData类是一个可观察的数据持有者类,它可以感知观察者(如Activity或Fragment)的生命周期,并只在观察者处于活跃状态时通知它们。Observer接口定义了一个方法,用于接收LiveData对象发出的更新通知。

  • ListViewAdapter:ListView 是被观察者,Adapter 是观察者。当 Adapter 的数据发生变化时,它会调用 notifyDataSetChanged() 方法来通知 ListView 更新界面

  • ContentProviderContentObserver:ContentProvider 是被观察者,ContentObserver 是观察者。当 ContentProvider 的数据发生变化时,它会调用 notifyChange() 方法来通知所有注册了 ContentObserver 的组件更新数据

  • BroadcastBroadcastReceiver:Broadcast 是被观察者,BroadcastReceiver 是观察者。当 Broadcast 发送消息时,它会通过 IntentFilter 来匹配所有注册了 BroadcastReceiver 的组件,并调用 onReceive() 方法来传递消息

四、观察者模式在安卓开发中的应用

在安卓开发中,有许多场景可以使用观察者模式来简化代码和提高效率。例如:

4.1使用LiveData来实现数据驱动界面。

LiveData是一种可观察数据持有者类,它可以感知生命周期并避免内存泄漏。我们可以将LiveData作为数据源, 将Activity或Fragment作为数据目标, 并通过LiveData.observe()方法建立观察关系。 当LiveData中 当LiveData中的数据发生变化时,它会自动通知所有注册的观察者,并根据观察者的生命周期状态决定是否更新界面。这样我们就不需要手动管理数据和界面之间的同步,也不需要担心内存泄漏或空指针异常等问题。

例如,我们可以在ViewModel中创建一个LiveData对象来存储用户信息:

    class UserViewModel : ViewModel() {
        // 创建一个LiveData对象来存储用户信息
        val user: MutableLiveData<User> = MutableLiveData()
        
        // 模拟从网络获取用户信息并更新LiveData对象
        fun getUserInfo(userId: String) {
            // 省略网络请求的细节
            val user = User(userId, "Alice", 20)
            // 更新LiveData对象
            this.user.value = user
        }
    }

然后,在Activity中获取ViewModel实例,并观察user对象:

    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)

            // 获取ViewModel实例
            val viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
            
            // 观察user对象,并在数据变化时更新界面
            viewModel.user.observe(this) { user ->
                // 更新界面,例如显示用户姓名和年龄
                tv_name.text = user.name
                tv_age.text = user.age.toString()
            }
            
            // 模拟点击按钮获取用户信息
            btn_get_user.setOnClickListener {
                viewModel.getUserInfo("123")
            }
        }
    }

这样,当我们点击按钮时,就会从网络获取用户信息,并更新LiveData对象。然后,LiveData对象会自动通知Activity,并根据Activity的生命周期状态决定是否更新界面。如果Activity处于活跃状态(STARTED或RESUMED),则会更新界面;如果Activity处于非活跃状态(CREATED、STOPPED或DESTROYED),则不会更新界面。这样就避免了在非活跃状态下更新界面导致的异常或资源浪费。

4.2使用RxJava来实现异步编程和响应式编程。

RxJava是一个基于观察者模式的库,它提供了一套丰富而灵活的操作符来处理异步事件和数据流。我们可以将RxJava中的Observable作为数据源, 将Subscriber作为数据目标, 并通过Observable.subscribe()方法建立观察关系。 当Observable中发射出事件时, 它会自动通知所有订阅了它的Subscriber, 并根据Subscriber订阅时指定的线程调度器决定在哪个线程执行回调方法。 这样我们就不需要手动管理线程切换和同步问题, 也不需要使用复杂的回调接口或Future等机制来处理异步结果。 例如,我们可以使用RxJava来简化网络请求和数据库操作:

    // 创建一个Observable对象来执行网络请求,并返回一个User对象
    fun getUserFromNetwork(userId: String): Observable<User> {
        return Observable.create { emitter ->
           try {
               // 省略网络请求的细节
               val user = User(userId, "Bob", 25)
               // 发射成功事件
               emitter.onNext(user)
               // 发射完成事件
               emitter.onComplete()
           } catch (e: Exception) {
               // 发射错误事件
               emitter.onError(e)
           }
        }
    }

    // 创建一个Observable对象来执行数据库操作,并返回一个Boolean值表示是否成功插入User对象到数据库中
    fun insertUserToDatabase(user: User): Observable<Boolean> {
        return Observable.create { emitter ->
           try {
               // 省略数据库操作的细节
               val success = true 
               // 发射成功事件 
               emitter.onNext(success)
               // 发射完成事件 
               emitter.onComplete()
           } catch (e: Exception) {
              // 发射错误事件 
              emitter.onError(e)
           }
       }

然后,在Activity中获取Observable实例,并订阅它们:

    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)

            // 模拟点击按钮获取用户信息并插入到数据库中
            btn_get_user.setOnClickListener {
                // 从网络获取用户信息的Observable对象
                val networkObservable = getUserFromNetwork("456")
                // 插入用户信息到数据库中的Observable对象
                val databaseObservable = networkObservable.flatMap { user ->
                    // 使用flatMap操作符将一个User对象转换为一个Boolean值的Observable对象
                    insertUserToDatabase(user)
                }
                
                // 订阅databaseObservable对象,并指定在IO线程执行网络和数据库操作,在主线程执行回调方法
                databaseObservable.subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(object : Observer<Boolean> {
                        override fun onSubscribe(d: Disposable) {
                            // 订阅开始时执行的方法,例如显示进度条
                            showProgressBar()
                        }

                        override fun onNext(t: Boolean) {
                            // 接收成功事件时执行的方法,例如显示结果
                            showResult(t)
                        }

                        override fun onError(e: Throwable) {
                            // 接收错误事件时执行的方法,例如显示错误信息
                            showError(e.message)
                        }

                        override fun onComplete() {
                            // 接收完成事件时执行的方法,例如隐藏进度条
                            hideProgressBar()
                        }
                    })
            }
        }
    }

这样,当我们点击按钮时,就会从网络获取用户信息,并插入到数据库中。然后,根据订阅时指定的线程调度器,在不同的线程执行不同的回调方法。如果网络或数据库操作成功,则会在主线程显示结果;如果失败,则会在主线程显示错误信息。这样就避免了在子线程更新界面导致的异常或卡顿问题。

4.3使用EventBus来实现组件间通信。

EventBus是一个基于观察者模式和发布-订阅模式的库,它提供了一种简单而高效地实现组件间通信(如Activity、Fragment、Service等)的方式。我们可以将EventBus作为事件总线, 将任意类型的事件作为数据源, 将任意类型的订阅者作为数据目标, 并通过EventBus.register()和EventBus.unregister()方法建立和取消观察关系。 当某个组件通过EventBus.post()方法发送一个事件时, 它会自动通知所有订阅了该类型事件的订阅者, 并根据订阅者注解指定的线程模式决定在哪个线程执行回调方法。 这样我们就不需要使用复杂而低效地广播机制或接口回调机制来实现组件间通信。 例如,我们可以使用EventBus来简化Activity和Fragment之间传递消息:

    // 定义一个MessageEvent类来封装消息内容
    data class MessageEvent(val message: String)

    // 在MainActivity中发送一个MessageEvent对象给FragmentA和FragmentB
    class MainActivity : AppCompatActivity() {

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)

            // 模拟点击按钮发送消息给FragmentA和FragmentB
            btn_send_message.setOnClickListener {
                val message = "Hello from MainActivity"
                val event = MessageEvent(message)
                EventBus.getDefault().post(event)
            }
        }
    }

    // 在FragmentA中接收MessageEvent对象并更新界面
    class FragmentA : Fragment() {

        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
           return inflater.inflate(R.layout.fragment_a, container, false)
        }

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
           super.onViewCreated(view, savedInstanceState)

           // 注册到EventBus对象 
           EventBus.getDefault().register(this)
        }

        override fun onDestroyView() {
           super.onDestroyView()

           // 取消注册到EventBus对象
    // 取消注册到EventBus对象
       EventBus.getDefault().unregister(this)
    }

    // 使用@Subscribe注解标记一个方法来接收MessageEvent对象,并指定线程模式为主线程
    @Subscribe(threadMode = ThreadMode.MAIN)
    fun onMessageEvent(event: MessageEvent) {
        // 更新界面,例如显示消息内容
        tv_message.text = event.message
    }
    }

    // 在FragmentB中接收MessageEvent对象并更新界面 class FragmentB : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
       return inflater.inflate(R.layout.fragment_b, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
       super.onViewCreated(view, savedInstanceState)

       // 注册到EventBus对象 
       EventBus.getDefault().register(this)
    }

    override fun onDestroyView() {
       super.onDestroyView()

       // 取消注册到EventBus对象
       EventBus.getDefault().unregister(this)
    }

    // 使用@Subscribe注解标记一个方法来接收MessageEvent对象,并指定线程模式为主线程
    @Subscribe(threadMode = ThreadMode.MAIN)
    fun onMessageEvent(event: MessageEvent) {
        // 更新界面,例如显示消息内容
        tv_message.text = event.message
    }
    }

这样,当我们点击按钮时,就会发送一个MessageEvent对象给FragmentA和FragmentB。然后,根据订阅者注解指定的线程模式,在主线程执行回调方法。如果FragmentA和FragmentB处于活跃状态,则会更新界面;如果处于非活跃状态,则不会更新界面。这样就避免了在非活跃状态下更新界面导致的异常或资源浪费。

五、Kotlin、Java、C++实现

5.1 Kotlin实现

Kotlin是一种基于JVM的静态类型编程语言,它支持函数式编程和面向对象编程。Kotlin提供了一些特性,可以简化观察者模式的实现,例如:

  • 委托属性:可以使用Delegates.observable()函数创建一个可观察的属性,它接受一个初始值和一个属性变化时调用的回调函数 。
  • 扩展函数:可以为任何类添加新的方法,而不需要修改原始类或继承它 。
  • Lambda表达式:可以将函数作为参数传递给其他函数,或者作为返回值返回 。

下面是一个使用Kotlin实现观察者模式的示例代码:

// 定义一个主题接口
interface Subject {
    // 添加观察者
    fun addObserver(observer: Observer)
    // 移除观察者
    fun removeObserver(observer: Observer)
    // 通知所有观察者
    fun notifyObservers()
}
// 定义一个观察者接口
interface Observer {
    // 接收更新通知
    fun update(subject: Subject)
}
// 定义一个具体主题类
class NewsAgency : Subject {
    // 使用委托属性创建一个可观察的新闻列表
    var news: List<String> by Delegates.observable(emptyList()) { _, _, _ ->
        // 属性变化时通知所有观察者
        notifyObservers()
    }

    // 使用HashSet存储所有注册的观察者
    private val observers = HashSet<Observer>()

    override fun addObserver(observer: Observer) {
        observers.add(observer)
    }

    override fun removeObserver(observer: Observer) {
        observers.remove(observer)
    }
override fun notifyObservers() {
    // 遍历所有观察者并调用它们的更新方法
    for (observer in observers) {
        observer.update(this)
    }
}
}
// 定义一个具体观察者类 
class TVChannel : Observer { // 使用一个可变列表存储收到的新闻 
val newsList = mutableListOf<String>()

override fun update(subject: Subject) {
    // 检查主题是否是NewsAgency类型
    if (subject is NewsAgency) {
        // 获取主题的新闻列表并添加到自己的列表中
        newsList.addAll(subject.news)
        // 打印收到的新闻
        println("TV Channel received news: ${subject.news}")
    }
}
}

// 测试代码 fun main() { // 创建一个NewsAgency对象和两个TVChannel对象 val newsAgency = NewsAgency() val tvChannel1 = TVChannel() val tvChannel2 = TVChannel()

// 将两个TVChannel对象注册到NewsAgency对象上
newsAgency.addObserver(tvChannel1)
newsAgency.addObserver(tvChannel2)

// 改变NewsAgency对象的新闻列表属性,触发通知
newsAgency.news = listOf("Breaking News: Kotlin is awesome!", "Sports News: The Lakers won the NBA championship!")
}

输出结果:

TV Channel received news: Breaking News: Kotlin is awesome!, Sports News: The Lakers won the NBA championship!
TV Channel received news: Breaking News: Kotlin is awesome!, Sports News: The Lakers won the NBA championship!

5.2 Java实现

Java是一种基于JVM的静态类型编程语言,它支持面向对象编程和泛型。Java提供了一些特性,可以实现观察者模式,例如:

  • 接口:可以定义一组抽象方法,由实现类提供具体行为 。
  • 抽象类:可以定义一些公共逻辑和部分抽象方法,由子类继承和实现 。
  • 内部类:可以在一个类中定义另一个类,内部类可以访问外部类的成员 。

下面是一个使用Java实现观察者模式的示例代码:

// 定义一个主题接口
interface Subject {// 添加观察者
void addObserver(Observer observer);
// 移除观察者
void removeObserver(Observer observer);
// 通知所有观察者
void notifyObservers();
}
    // 定义一个观察者接口 
    interface Observer { 
    // 接收更新通知 
    void update(Subject subject); 
    }


    // 定义一个具体主题类,继承自java.util.Observable类,实现Subject接口 
    class NewsAgency extends Observable implements Subject { 
    // 使用一个列表存储新闻 
    private List<String> news;

    public NewsAgency() {
        // 调用父类构造方法,初始化列表为空
        super();
        this.news = new ArrayList<>();
    }

    public List<String> getNews() {
        // 返回新闻列表的副本,避免外部修改
        return new ArrayList<>(this.news);
    }

    public void setNews(List<String> news) {
        // 设置新闻列表,并调用父类的setChanged方法,标记状态已改变
        this.news = news;
        setChanged();
        // 调用父类的notifyObservers方法,通知所有观察者,传递自身作为参数
        notifyObservers(this);
    }
    @Override public void addObserver(Observer observer) { // 调用父类的addObserver方法,将观察者添加到内部的观察者列表中 super.addObserver(observer); }

    @Override
    public void removeObserver(Observer observer) {
        // 调用父类的deleteObserver方法,将观察者从内部的观察者列表中移除
        super.deleteObserver(observer);
    }

    @Override
    public void notifyObservers() {
        // 调用父类的notifyObservers方法,通知所有观察者,不传递参数
        super.notifyObservers();
    }
    }


    // 定义一个具体观察者类,实现java.util.Observer接口和自定义的Observer接口 
    class TVChannel implements java.util.Observer, Observer { 
    // 使用一个可变列表存储收到的新闻
     private List<String> newsList;

    public TVChannel() {
        // 初始化列表为空
        this.newsList = new ArrayList<>();
    }

    @Override
    public void update(Observable o, Object arg) {
        // 检查主题是否是NewsAgency类型,并且参数是否是Subject类型
        if (o instanceof NewsAgency && arg instanceof Subject) {
            // 调用自定义的update方法,传递Subject类型的参数
            update((Subject) arg);
        }
    }

    @Override
    public void update(Subject subject) {
        // 检查主题是否是NewsAgency类型
        if (subject instanceof NewsAgency) {
            // 获取主题的新闻列表并添加到自己的列表中
            newsList.addAll(((NewsAgency) subject).getNews());
            // 打印收到的新闻
            System.out.println("TV Channel received news: " + ((NewsAgency) subject).getNews());
        }
    }
    }


    // 测试代码 
    public class Main { public static void main(String[] args) { 
    // 创建一个NewsAgency对象和两个TVChannel对象 
    NewsAgency newsAgency = new NewsAgency(); TVChannel tvChannel1 = new TVChannel(); TVChannel tvChannel2 = new TVChannel();

        // 将两个TVChannel对象注册到NewsAgency对象上
        newsAgency.addObserver(tvChannel1);
        newsAgency.addObserver(tvChannel2);

        // 改变NewsAgency对象的新闻列表属性,触发通知
        newsAgency.setNews(Arrays.asList("Breaking News: Java is awesome!", "Sports News: The Lakers won the NBA championship!"));
    }
    }

观察者模式是一种设计模式,它允许一个对象(被观察者)通知其他对象(观察者)当它的状态发生变化时。这样可以实现对象之间的依赖关系,避免频繁地检查变化。这种模式在需要基于推送的通知的场景中很常用,比如当数据库状态发生变化时刷新网页。

5.3 C++实现

在C++中,实现观察者模式的一种方法是使用std::vector来存储观察者的引用,并定义一个抽象的观察者类,让具体的观察者类继承它,并实现一个更新方法。被观察者类则需要提供注册和注销观察者的方法,以及一个通知方法,用来遍历所有注册的观察者,并调用它们的更新方法。

下面是一个简单的例子,演示了如何用C++实现观察者模式:

// 抽象的观察者类
class Observer {
public:
  virtual ~Observer() {}
  virtual void update(int value) = 0; // 更新方法
};

// 具体的观察者类A
class ConcreteObserverA : public Observer {
public:
  void update(int value) override {
    std::cout << "ConcreteObserverA: " << value << "\n";
  }
};

// 具体的观察者类B
class ConcreteObserverB : public Observer {
public:
  void update(int value) override {
    std::cout << "ConcreteObserverB: " << value << "\n";
  }
};

// 被观察者类
class Subject {
private:
  int m_value; // 状态值
  std::vector<std::reference_wrapper<Observer>> m_observers; // 观察者列表
public:
  void attach(Observer& observer) { // 注册观察者
    m_observers.push_back(observer);
  }

  void detach(Observer& observer) { // 注销观察者
    m_observers.erase(std::remove(m_observers.begin(), m_observers.end(), observer), m_observers.end());
  }

  void notify() { // 通知所有注册的观察者
    for (auto& observer : m_observers) {
      observer.get().update(m_value);
    }
  }

  void set_value(int value) { // 设置状态值,并通知变化
    m_value = value;
    notify();
  }
};

int main() {
  
  Subject subject; // 创建被观察者对象

  ConcreteObserverA observerA; // 创建具体的观察者对象A
  ConcreteObserverB observerB; // 创建具体的观察者对象B

  subject.attach(observerA); // 注册A为subject的观察者
  subject.attach(observerB); // 注册B为subject的观察者

  
subject.set_value(10); // 设置subject状态值为10,并通知所有注册过得observer

subject.detach(observerA); // 注销observer A

subject.set_value(20); // 设置subject状态值为20,并通知所有注册过得observer

return0;
}

六、优缺点

6.1观察者模式有以下优点:

  • 它支持被观察者和观察者之间的松耦合,它们只需要知道对方的接口,而不需要知道具体的实现细节。
  • 它允许被观察者在状态变化时有效地向观察者发送数据,而不需要修改被观察者或观察者的类。
  • 它支持广播通信,一个被观察者可以通知任意数量的观察者。

6.2观察者模式也有以下缺点:

  • 观察者接口必须由具体的观察者类实现,这涉及到继承。没有组合的选项,因为观察者接口不能被实例化。
  • 如果没有正确地实现,观察者模式可能会增加复杂性,并导致意外的性能问题。在软件应用中,通知有时可能会导致链式反应或循环依赖。
  • 观察者模式可能会违反封装原则,因为它暴露了被观察者的状态给外部对象。这可能会导致数据不一致或安全风险。

七、观察者模式和状态模式的对比如下表所示:

设计模式目的优点缺点使用场景
观察者模式实现对象间的一对多的依赖关系,使得当一个对象的状态发生改变时,所有依赖于它的对象都能够得到通知并自动更新。支持被观察者和观察者之间的松耦合;允许被观察者在状态变化时有效地向观察者发送数据;支持广播通信。观察者接口必须由具体的观察者类实现,没有组合的选项;可能会增加复杂性,并导致意外的性能问题;可能会违反封装原则,暴露被观察者的状态给外部对象。在需要基于推送的通知的场景中使用,比如当数据库状态发生变化时刷新网页,或当用户界面上的一个组件改变时更新其他组件。
状态模式允许一个对象在其内部状态改变时改变它的行为,使得对象看起来像是修改了它的类。支持单一职责原则,将每个状态对应的行为封装在一个类中;支持开闭原则,可以通过添加新的状态类来扩展功能;避免了过多的条件判断语句。增加了系统中类的数量,可能会导致维护困难;需要定义所有可能出现的状态,并且保证它们之间正确地转换。在需要根据对象内部状态改变其行为的场景中使用,比如当订单有不同的支付方式或配送方式时执行不同的操作,或当游戏角色有不同的等级或技能时表现出不同的行为。