一、前言。
昨天我费尽心思了4个小时,终于把我的设计模式系列的第一篇开了新的大门,有意者可以看看哒。
传送门:Java基础再回首之设计模式系列①—–StrategyPattern 策略者模式(案列教程,附带demo)时间就像海绵,只要愿意挤,还是会有的。 ——-鲁迅
虽然身处工作的我,还是愿意挤出一点时间,充下电,今天给大家详细的图文并茂的分析 Observer 观察者模式,真的想一起学习哒~在博文下方留言,咱们一起学习,共勉设计模式带来的方便。
二、案列。
正是因为上篇文章的设计模式得到好评(哈哈~),又来了新的项目了,不过这次的项目要求有所不同。
1.为贵公司建立一个 新版本的电子杂志社的全新系统。 该电子杂志社必须建立JournalData对象中,由JournalData来负责追踪电子杂志社发来的各大新闻日报信息。
2.而且,该系统还是可以拓展的。可以作为API卖数据给第三方,这样可以建立良好的运营商业模式,一旦有客户上门,他们使用该服务数据都是需要收费的。
我们看看大概的思维导图:
1.JournalData对象作为一个中间站负责采集电子杂志社的各大新闻的信息。
2.只有订阅了对象系统的信息的客户,才可以收到对象系统的发来的公告。
3.各个客户各不知道彼此的信息订阅,如果某刻某个客户退出了订阅,不会影响到其他客户的订阅,而且其他客户也不知道,只有在对象系统中知道,而且从此之后,就不会再向该客户提供任何新闻信息。
三.代码实现。
我们看看代码架构:
这次我们不用普通方案,看了上面的要求。我开始先写3个接口:
①. ISubiect :分别作为 新增客户、移除客户、通知客户更新数据 ;
public interface ISubiect {
//注册一个观察者
void registerObserver(IObserver iObserver);
//移除一个观察者
void removeObserver(IObserver iObserver);
//通知有数据更新啦
void notifyObserver(IObserver iObserver);
}
②. IObject : 当有新闻信息更新时后,系统会把一些新闻数据去传递给所有观察者。
public interface IObserver {
/**
*
* @param entertainmentNews 娱乐新闻
* @param militaryNews 军事新闻
* @param peopleNews 民生新闻
*/
void update(String entertainmentNews,String militaryNews,String peopleNews);
}
③.IDisplayElement:此接口仅仅只是所有观察者要展示用户观察者收到的数据。(可以不用写,此处只是打印出收到的数据)
public interface IDisplayElement {
//所有的观察者要展示自己受到的新闻信息
void DisplayElement();
}
④我们看看杂志系统的代码:
public class JournalData implements ISubject {
//所有的观察者集合,并且泛型指向观察者接口
private List<IObserver> observer;
//娱乐新闻
private String entertainmentNews;
//军事新闻
private String militaryNews;
//民生新闻
private String peopleNews;
//构造方法就为集合实例化
public JournalData() {
observer = new ArrayList();
}
//注册一个观察者
@Override
public void registerObserver(IObserver iObserver) {
observer.add(iObserver);
}
//移除一个观察者
@Override
public void removeObserver(IObserver iObserver) {
int index = observer.indexOf(iObserver);
if (observer.size() > 0) {
observer.remove(index);
}
}
//通知所有的观察者有数据更新啦
@Override
public void notifyObserver() {
for (IObserver temIObserver : observer) {
temIObserver.update(entertainmentNews, militaryNews, peopleNews);
}
}
//设置数据
public void setJourData(String entertainmentNews, String militaryNews, String peopleNews) {
this.entertainmentNews = entertainmentNews;
this.militaryNews = militaryNews;
this.peopleNews = peopleNews;
notifyObserver();//别忘了设置完数据,通知所有观察者有数据更新啦
}
}
⑤ 看看我们的用户怎么做的:
public class User1 implements IObserver, IDisplayElement {
//定义一个观察者管理的接口,方便以后可以移除操作
private ISubject iSubject;
private String entertainmentNews;
private String militaryNews;
private String peopleNews;
public User1(ISubject iSubject) {
this.iSubject = iSubject;
iSubject.registerObserver(this);
}
@Override
public void update(String entertainmentNews, String militaryNews, String peopleNews) {
this.entertainmentNews = entertainmentNews;
this.militaryNews = militaryNews;
this.peopleNews = peopleNews;
DisplayElement();
}
@Override
public void DisplayElement() {
System.out.print("用户一收到了娱乐新闻:" + entertainmentNews + ",军事新闻:" + militaryNews + ", 民生新闻:" + peopleNews);
}
}
⑥、启动杂志社。
public class Main {
public static void main(String[] args) {
JournalData journalData = new JournalData();
//此处我使用匿名对象注册观察者
new User1(journalData);
new User2(journalData);
//有数据过来了
journalData.setJourData("明星黄晓明孩子出世啦"
, "美俄战机一周内黑海上空两度遭遇"
, "广州市民幸福度80%");
}
}
四、总结以上观察者模式。
要点:
1.定义了对象之间的一对多的关系。
2.出版者用一个共同的接口来更新观察者数据更新。
3.出版者和观察者之间用松耦合的方式结合,出版者不知道观察者的细节,只知道了观察者实现了接口。
问题来了:
如果存在观察者想要自己动身去出版社取数据,而不是统一让出版社发出数据。毕竟有时候出版社已经有数据了,只是未到时间、参数不齐等其他原因,未能立刻发出去,但是已经有数据了的。这就造成了观察者心急但是吃不了热豆腐啊。那如何让观察者“心急也能吃到热豆腐呢?”,能立刻吃到“热豆腐呢?”。
五、利用Java内置的观察者模式。
很高兴告诉你,Java API有内置的观察者模式,Java.util包内包括最基本的Observer接口和Obersver类,这个和我们上面的ISubject接口和IObersver接口非常相似,利用内置的观察者模式更方便解决我所引出上面的问题。其功能都已经实现好了的,你在此作为出版社可以把信息放着,等待着观察者来取。当然啦!你也可以直接一次性推送出去给观察者。这样,你就可以免了搭建框架,又可以随心所欲的让观察者“心急也能吃到热豆腐!”。
5.1 内置的观察者模式源码分析。(先上完代码,再一步一步分析。)
1.现在我们继承要写的杂志系统要继承的 java.util包的Observable类。
public class JournalData extends Observable {
//娱乐新闻
private String entertainmentNews;
//军事新闻
private String militaryNews;
//民生新闻
private String peopleNews;
//设置数据
public void setJourData(String entertainmentNews, String militaryNews, String peopleNews) {
this.entertainmentNews = entertainmentNews;
this.militaryNews = militaryNews;
this.peopleNews = peopleNews;
dataChanged();
}
private void dataChanged() {
setChanged();
notifyObservers();
}
//就让取观察者自己来取“热豆腐”吧
public String getEntertainmentNews() {
return entertainmentNews;
}
public String getMilitaryNews() {
return militaryNews;
}
public String getPeopleNews() {
return peopleNews;
}
}
2.看看我们的用户观察者类。
public class User1 implements Observer, Display {
private Observable observable;
//娱乐新闻
private String entertainmentNews;
//军事新闻
private String militaryNews;
//民生新闻
private String peopleNews;
//注册一个订阅
public void addObserver(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
//删除一个订阅
public void deleteObserver(Observable observable) {
observable.deleteObserver(this);
}
//删除所有订阅
public void deleteAllObserver() {
observable.deleteObservers();
}
@Override
public void update(Observable observable, Object arg) {
//先判断JournalData是否属于Observable的实例,如果存在多个Observable 子类,这行代码非常必要
if (observable instanceof JournalData) {
//如果是,把实例向下转型为JournalData对象
JournalData journalData = (JournalData) observable;
this.entertainmentNews = journalData.getEntertainmentNews();
this.militaryNews = journalData.getMilitaryNews();
this.peopleNews = journalData.getPeopleNews();
}
}
@Override
public void Display() {
System.out.print("用户一收到了娱乐新闻:" + entertainmentNews + ",军事新闻:" + militaryNews + ", 民生新闻:" + peopleNews);
}
}
5.3 分析。
1.现在我们的杂志社改为了继承了一个类Obersver,说明一下,这个类不是抽象类!而是一个和我们上面手写的JournalData.java一样。都是有接口集合,看看右边的这个类的方法,发现它还有统计订阅者的个数方法 countObservers()。看看下图:
2.JournalData.java类里面有setChanged()、hasChanged、clearChanged()方法,旨在表示状态已经发生改变,做一个开关,如果出版社想要发送出去就调用父类的 clearChanged()方法,如果只是想让观察者自己来获取数据就setChanged()。总的来说,这就是一个开关。
官方这样解释的:想让推送者有自己的监控,可以随自己的要求去推送或者不推送,可以避免了最近注册的观察者错过了最近发出的通知。
下面我抽出官方源码来看看:(注意只有 changed=true才会推送出去,也就是调用setChanged()方法。)
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
3.现在我们把眼光转到用户的update()方法,可以看到有两个参数Observable observable和Object arg,第一个参数observable之前注册自己的被观察者,第二个参数作为可以传递对象过来的参数,一般地,如果不加以传递参数过来,都是为null,可以不加理会。
六、内置的观察者模式总结:
1.使用内置的Java观察者模式,可以轻松的实现“推”和“拉”的方式。可以按照推送者的要求去推送自己想要的内容。
2.内置的观察者模式通知观察者的次序是不一样的,实现了松耦合。
3.内置的观察者模式的黑暗面:它使用的是一个类,而不是一个接口,这大大限制了它的使用和复用,这违背了“多用组合,少用继承”的原则。
4.不管怎么样,按照自己的需求,选择哪种方式!反正都熟悉了观察者模式了。