娜样美的观察者模式
来源:互联网 发布:网络机顶盒H3芯片 编辑:程序博客网 时间:2024/05/29 16:35
观察者模式,是一种异常美丽的设计模式。本文包括定义、类图以及应用实例,例子采用 Java 实现。
1. 定义
观察者模式又称订阅者模式(可以将其看做报纸订阅服务,有两个主要角色,出版商和订阅者。假设出版商初版的杂志是周刊,那么一旦新杂志上架,出版商就将其邮寄给一个或多个订阅者)。
观察者模式在生活中每天都在上演。
快递小哥蹲在一家单位门口,被满地的包裹围攻,气定神闲。小哥咬了一口鸡蛋灌饼,对着手机说:“你好,你有快递到了……”
包裹被签收后,小哥将收件人从便签中划掉。
我们来简单分析一下这个场景。首先是人物:快递小哥一枚,收件人多只。那么,是什么将小哥和收件人联系起来的呢?是手机号,即小哥拥有所有收件人的手机号,以便通知收件人来领包裹——收件人事先在快递小哥那里注册了自己的信息(手机号)。
你站在桥上看风景
换言之,快递小哥是“主题”(Subject),收件人是观察者(Observer)。收件人向快递小哥注册自己的信息(实际上是在下订单时写的,然后由电商转给快递公司,再由快递公司分发给快递小哥,我们简化这一流程为收件人直接给快递小哥),然后等待快递小哥的通知,以便做出相应的行动。
言归正传,观察者模式由2部分组成:主题/Subject,观察者/Observer,Observer 在 Subject 中注册,然后等待 Subject 的通知。
《Head First 设计模式》下的定义:
观察者模式定义了对象之间的一对多依赖,当一个对象的状态发生改变时,它的所有依赖者都会收到通知并自动更新。
类图
2. 场景实现
我们用 Java 来实现拿快递这一场景。
2.1 Subject
我们将 Subject 定义为接口,接口中有3个行为:注册观察者,移除观察者,通知观察者。分别对应方法:registerObservers(),removeObservers(),removeObservers()。
快递小哥实现该接口,则具有并对外公开这3个行为,并按照自己的行为具体实现之,而具体实现内容则对外隐藏,更改这些方法的具体实现不影响其他对象的调用形式。
Subject.java
public interface Subject { public void registerObservers(Observer o); public void removeObservers(Observer o); public void removeObservers();}public class ExpressMan implements Subject { List<Observer> observerList; public ExpressMan() { this.observerList = new ArrayList<>(12); } @Override public void registerObservers(Observer o) { observerList.add(o); } @Override public void removeObservers(Observer o) { int i = observerList.indexOf(o); if (i > 0) { observerList.remove(i); } } @Override public void notifyObservers() { for (Observer o : observerList) { o.update(); } }}
2.2 Observer
Observer 接口只有1个行为:update()。观察者实现该接口,在该方法中实现自己的 update() 行为,如对于收件人 update() 则是暂停工作去取快递。
public interface Observer { void update();}public class Recipient implements Observer{ Subject subject; public Recipient(Subject _subject) { this.subject = _subject; subject.registerObservers(this); } @Override public void update() { // 暂停工作,去取快递 }}
2.3 主函数
public class Main { public static void main(String[] args) { Subject Zhansan = new ExpressMan(); Observer Lisi = new Recipient(Zhansan); Observer Wangwu = new Recipient(Zhansan); Observer Chenliu = new Recipient(Zhansan); Zhansan.notifyObservers(); }}
3. 观察者模式的优势
之所以说观察者模式那样美,是因为这种模式实现了两组对象的松耦合,它们只知道对方实现了什么接口,具有什么的样行为,而对其中的实现细节并不了解。
我们再举个具体的例子来说明观察者模式是如何解耦的。下图是美团安卓 APP(v6.8.1) 的个人中心页,图中演示的是匿名评价功能。我们假设“匿名评价”这个 TextView 和“更多按钮”的 ImageButton(假设是个 ImageButton)分别属于两个组件 FeedItemViewModel 和 FeedCommentViewModel。
正常情况下,为了在 FeedCommentViewModel 中对 FeedItemViewModel 中的“匿名评价”这个 TextView 进行更新,必然会在 FeedCommentViewModel 中持有 FeedItemViewModel 的引用,这就造成了耦合。
如果我们想实现两个组件解耦,即任意一个组件都可以单独拿出去使用,我们可以借助 JDK 提供的 基类 Observable 和 接口 Observer。
FeedItemViewModel 是订阅者,实现 Observer 接口,重写 update(Observable observable, Object data) 方法,在方法体中实现更新“匿名评价”的操作;
FeedCommentViewModel 是发布者,继承 Observable,通过 addObserver(Observer o)
方法注册订阅者,通过 notifyObservers(Object data)
通知订阅者更新。
为了简单起见,我们使用 DataBinding 技术实现该功能,关于 DataBinding 更多的内容请参考《安卓 DataBinding 使用经验总结(姐姐篇)》。
代码中接口 BaseObservable 和 notifyOnPropertyChanged(BR.id)
是 DataBinding 相关的内容。由此可以看出 DataBinding 也是使用了观察者模式的。
实现效果如下:
代码如下(代码中我们使用了安卓提供的 DataBinding 技术):
MainActivity.java
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); FeedViewModel model = new FeedViewModel(); binding.setUser(model); }}
FeedItemViewModel.java
public class FeedViewModel extends BaseObservable implements Observer { public @Bindable String firstName; public FeedCommentViewModel mFeedCommentViewModel; public FeedViewModel() { firstName = "mmlovesyy"; mFeedCommentViewModel = new FeedCommentViewModel(this); } public void setFirstName(String firstName) { this.firstName = firstName; notifyPropertyChanged(com.mmlovesyy.java_observable.BR.firstName); } @Override public void update(Observable observable, Object data) { setFirstName((String) data); }}
FeedCommentViewModel.java
public class FeedCommentViewModel extends Observable { public FeedCommentViewModel(Observer observer) { addObserver(observer); } public void onClick(View view) { setChanged(); notifyObservers("mm***yy"); }}
main_activity.xml
<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bind="http://schemas.android.com/apk/res-auto"> <data> <variable name="user" type="com.mmlovesyy.java_observable.FeedViewModel" /> </data> <LinearLayout xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.mmlovesyy.java_observable.MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text='@{user.firstName}' android:textColor="#ff0000" /> <include layout="@layout/activity_comment" bind:comment='@{user.mFeedCommentViewModel}' /> </LinearLayout></layout>
activity_comment.xml
<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bind="http://schemas.android.com/apk/res-auto"> <data> <variable name="comment" type="com.mmlovesyy.java_observable.FeedCommentViewModel" /> </data> <LinearLayout xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.mmlovesyy.java_observable.MainActivity"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick='@{comment.onClick}' android:text="匿名评价" /> </LinearLayout></layout>
开发中我们会用到 ItemView 和其对应的 ItemModel,如果让 ItemView 观察 ItemModel,那么 ItemModel 中的数据更新时,ItemView 不就自动更新了吗?看,我们实现了一个简化版的 Data Binding!
3. 应用实例
3.0 JDK 自带 Observer & Observable
在上一节匿名评价的例子中,我们使用了 JDK 自带的 java.util.Observer 和 java.util.Observable。
应当注意的是,Observable 是一个类,而非接口。由于 Java 的单继承性,只能继承一个父类,却可以实现多个接口,所以当某个类已经继承了父类,就不能再继承 Observable 类了,这是不太方便的地方。
同时,这个不太方便的地方告诉我们:如果可以,优先使用接口,而非基类。
3.1 BaseAdapter
使用安卓的 ListView 或 RecyclerView 的时候,BaseAdapter 是无法回避的。
BaseAdapter 里面也有一对观察者模式组合:DataSetObserver & DataSetObservable。并且提供了一系列的注册/注销/通知的方法:
private final DataSetObservable mDataSetObservable = new DataSetObservable(); public boolean hasStableIds() { return false; } public void registerDataSetObserver(DataSetObserver observer) { mDataSetObservable.registerObserver(observer); } public void unregisterDataSetObserver(DataSetObserver observer) { mDataSetObservable.unregisterObserver(observer); } /** * Notifies the attached observers that the underlying data has been changed * and any View reflecting the data set should refresh itself. */ public void notifyDataSetChanged() { mDataSetObservable.notifyChanged(); }
如果 ListView / RecyclerView 内部有地方(如 headerView 或 footerView),或者 ListView / RecyclerView 外部有地方(如 ListView / RecyclerView 所在的 Fragment / Activity 内的某个 View),要监听 BaseAdapter 数据的变化,则可以通过注册 DataSetObserver 的方法来做到。
需要注意的是,这种方法只能监听数据的变化,而无法区分数据的增加或减少等细节。
3.2 BroadcastReceiver
3.3 RxJava
3.4 Data Binding
请参考我的另外两篇博客:《安卓 DataBinding 使用经验总结(姐姐篇)》 和 《安卓 DataBinding 使用经验总结(妹妹篇)》。
3.5 EventBus
3.6 OnClickListener
以 View.OnClickListener 为首的各种 Listener,也属于观察者模式。只不过是简化版的观察这模式,因为只有 1 个观察者,即 1 个 Observer。此时,register 方法被 setter 方法所取代 。
4. 更多资料
- 《安卓 DataBinding 使用经验总结(姐姐篇)》
- 《安卓 DataBinding 使用经验总结(妹妹篇)》
- 娜样美的观察者模式
- 观察者模式的应用
- 观察者模式的应用
- 增强的观察者模式
- 观察者模式的理解
- iphone 的观察者模式
- 观察者模式的应用
- 观察者模式的应用
- 观察者模式的使用
- 派生的观察者模式
- 观察者模式的浅析
- 观察者模式的使用
- 观察者模式的结构
- 常用的 观察者模式
- 观察者模式的思考
- 随处可见的观察者模式
- Magento的观察者模式
- iOS的观察者模式
- Volley框架全解析
- Tomcat7+Redis存储Session
- 2015.9.5
- POJ 2376 Cleaning Shifts (区间覆盖)
- XCode单元测试
- 娜样美的观察者模式
- 最小生成树的两种算法
- 1718:Rank
- PHP学习(二)--变量
- java.lang.IllegalStateException: you have not supplyed the global app context info from SDKInitializ
- spoj -705 New Distinct Substrings--后缀数组
- 鸟哥的linux私房菜中推荐的linux学习网站
- OpenGL 多重纹理
- graham扫描法求凸包