观察者模式 多线程在实际用的多吗

观察者模式在实际用的多吗
看社区和一些问答社区,观察者模式讨论得比较少,是实际就用的很少吗?
观察者模式应该是实际当中应用最多的设计模式之一,至少前三
--- 共有 2 条评论 ---
: 如果你想了解设计模式我推荐你看《Head First设计模式》这是我看过的最好的设计模式的书
好像讨论得不多啊
几乎到处都在用,感觉仅次于单例和工厂了,讨论少个人认为是这个模式用法很明确,而且很多语言里都已经为你实现了一部分(至少我接触的是),比如Java里的Observe、PropertyChange等,WP(C和OC的delegate也算,你应该知道委托的应用有多广
--- 共有 3 条评论 ---
: 要看你怎么理解了,比如http异步,可以看成是观察请求状态,在status变为200之后执行success里的回调函数,变成4xx或者5xx执行fail里的,其实说多了都是文字游戏,没必要去纠结,代码写多了自然有自己的想法了
异步的也算吗?
+1,那些回调函数,listener也算吧?
非常非常多
你是用代码的,而不是开发代码的,所以觉得少
--- 共有 1 条评论 ---
开发代码?
你到底明白什么是观察者模式吗?你点一个按钮出现一个响应就是观察者模式
--- 共有 2 条评论 ---
有多种方式可以实现, C/C++中用回调函数比较多,Java多用接口实现, C#多用委托(一种类似回调函数的类型)实现
这个好像是喔。那,代码方面怎么去监听目标的变化?
引用来自“maxos”的评论你到底明白什么是观察者模式吗?你点一个按钮出现一个响应就是观察者模式确定这种是观察者模式吗?怎么感觉是监听者模式呢?
--- 共有 3 条评论 ---
可以认为一对一是一对多的特列吗?
: GoF版设计模式里,这样描述观察者模式的意图:定义对象间的一种一对多的依赖关系 ,当一个对象的状态发生改变时 , 所有依赖于它的对象 都得到通知并被自动更新。对象间是一种一对多的关系。但点一个按钮出现一个响应这种case,与观察者似乎还是有一些区别的吧。这种实际上是一对一的关系啊。比如像Android里的View/Button,只能设置一个listener吧。
观察者模式跟监听者模式不是一样吗?
用得挺多的,需要1对多的得到通知的场合都适用。谈KVC、KVO(重点观察者模式)机制编程
招聘信息:
一不小心,小明在《跟着贝尔去冒险》这个真人秀节目中看到了“一日警察,一世警察”的Laughing哥,整个节目除了贝尔吃牛睾丸都不用刀叉的不雅餐饮文化外,还是镜头少普通话跟小明一样烂的Laughing Sir那种冷静和沉着稳定留下了深刻印象,不由想起电视剧《学警狙击》中为了不暴露钟立文的身份,要求向自己补一枪的警匪卧底巅峰推动者--Laughing 哥。那么,卧底这样的工作,在我们程序里有没有呢?答案是肯定的,观察者模式。文章内容思维导图一.基本概念1)KVC概念KVC全称Key-value coding.一个非正式的Protocol,提供一种机制来间接访问对象的属性.官方文档提供对KVC很准确的描述Key-value coding is a mechanism for accessing an object’s properties indirectly, using strings to identify properties, rather than through invocation of an accessor method or accessing them directly through instance variables. In essence, key-value coding defines the patterns and method signatures that your application’s accessor methods implement.2)KVO概念KVO全称Key-Value Observing。典型的观察者模式承载者。基于监控键值发生变化,通知观察者。KVO 就是基于 KVC 实现的关键技术之一。官方文档提供对KVO很准确的描述Key-value observing provides a mechanism that allows objects to be notified of changes to specific properties of other objects. It is particularly useful for communication between model and controller layers in an application.二.KVC和KVO的作用Laughing哥上场了,先看看怎么做卧底,首先,Laughing哥先得符合古惑仔行为准则混入黑帮;接着,除了放高利贷和Disco业务外最重的是挤兑从台湾出狱的世孝,选择站在亦天内心的一边得到足够多的信任;最后,凭借“一日警察,一世警察”赤诚初心,秉公执法端掉亦天和制毒窝点。那么,你觉度Laughing Sir的作用是什么?1. 接近需要得到信息隐秘或不隐秘的使用场所。2. 直接监视信息的变化。3. 当产生了有用的信息后,那马上通知汇报。如果亦天制作的毒品比作信息,普通警察只能通过get方式属性,更重要是不知道他什么时候发生了变化。卧底Laughing Sir完美扮演的就是KVC和KVO机制,为什么说完美?KVC是可以直接通过路径获取对应的键的值,KVO的观察通知部分就对应Laughing Sir的监视和汇报,如果Laughing Sir变节了或者没有意志做下去了,那就只能是KVC能获取到信息,但不能通知上级信息的变化,就没有了一个经典的卧底角色Laughing了。三.JAVA中的观察着模式Sun公司早早就把观察者模式视为重要的模式,并在Java中提供方便的接口Observer和类Observable。这个地方注意一下,Observer是一个接口,Observable是一个类。因为很容易先入为主,XXXable第一反应是接口。如果看过《设计模式之禅》这本书的人,自然想起书中举的例子是李斯监视同窗韩非子的一举一动汇报给秦始皇。并且书中的Observer和Observable自定义定义刚好相反,注意下即可。为什么提Java,继续看吧。四.代码实现1. KVC属性读取和修改1)Sense:警官:梁笑棠,从今天开始&,你的生命属于社会的,清楚吗?&&
Laughing&Sir:清楚。&&
警官:出了这个学堂,你要叫Laughing&哥,记好了吗?&&
Laughing&Sir:Yes&sir。
警官:你妹,大声点。&&
Laughing&Sir:_____程序中,Laughing Sir被派于卧底工作前,需要把Laughing Sir的名字属性值更换成Laughing哥.我们就从这个地方开始练练手预热做卧底的体验吧。2)Step:①通过路径方式获取属性值NSString&*preName&=&[laughingSir&valueForKey:@"name"];②修改属性值[laughingSir&setValue:@"laughing&哥"&forKey:@"name"];3)Show Code:NSString*&exchangeName(LaughingSir&*laughingSir){
&&&&NSString&*preName&=&[laughingSir&valueForKey:@"name"];
&&&&NSLog(@"laughing的旧名字:%@",preName);
&&&&[laughingSir&setValue:@"laughing&哥"&forKey:@"name"];
&&&&NSString&*newName&=&[laughingSir&valueForKey:@"name"];
&&&&NSLog(@"laughing的新名字:%@",newName);
&&&&return&newN
}2.KVO观察者模式演绎1)Sense:亦天可能进行制毒。。。
Laughing&Sir开始监控亦天
报告上级亦天制毒数:___程序中,Laughing Sir开始观察YiTian这个实体类中的narcotics属性,一旦亦天制作出毒品,就马上observeValueForKeyPath通知上级,看看如下的具体实现。2)Step:①对被观察者添加观察[self.yiTian&addObserver:self&forKeyPath:@"narcotics"&options:NSKeyValueObservingOptionNew&|NSKeyValueObservingOptionOld&context:nil];②实现观察结果处理方法-(void)observeValueForKeyPath:(NSString&*)keyPath&ofObject:(id)object&change:(NSDictionary&*)change&context:(void&*)context{
&&&&//汇报上级
}3)Show Code:-(void)observeValueForKeyPath:(NSString&*)keyPath&ofObject:(id)object&change:(NSDictionary&*)change&context:(void&*)context{
&&&&if([keyPath&isEqualToString:@"narcotics"]){
&&&&&&&&NSNumber&*narcoticsN&=&[change&objectForKey:@"new"];//修改之后的最新值
&&&&&&&&NSInteger&narcotics&=&[narcoticsN&integerValue];
&&&&&&&&if&(narcotics>0)&{
&&&&&&&&&&&&if&(self.delegate!=nil&&[self.delegate&respondsToSelector:@selector(reportYitian:)])&{
&&&&&&&&&&&&&&&&[self.delegate&reportYitian:narcotics];
&&&&&&&&&&&&}
}注意:留意下[change objectForKey:@"new"]其中这个new是指新赋予narcotics这个属性的值,当然也有一个old而不是[change objectForKey:@"narcotics"];narcotics是毒品意思。3.Java实现观察者模式这里我就直接显示关键代码了,不做过多说明,对比了解和学习。/**
*&亦天实体类&&
*&@author&minggo
*&@time&日&上午10:24:15
public&class&YITian&extends&Observable&{
&&&&private&Observer&
&&&&private&int&
&&&&@Override
&&&&public&synchronized&void&addObserver(Observer&o)&{
&&&&&&&&super.addObserver(o);
&&&&&&&&this.observer&=&o;
&&&&public&void&MakeNarcotics(){
&&&&&&&&for&(int&i&=&0;&i&<3;&i++)&{
&&&&&&&&&&&&narcotics++;
&&&&&&&&&&&&if&(observer!=null)&{
&&&&&&&&&&&&&&&&observer.update(this,&narcotics);
&&&&&&&&&&&&}
}留意Laughing Sir实体类的@override方法/**&&
*&Laughing&Sir实体类&&
*&@author&minggo&&
*&@time&日&上午9:58:36&&
public&class&LaughingSir&implements&Observer{
&&&&@Override
&&&&public&void&update(Observable&o,&Object&arg)&{
&&&&&&&&if&(o&instanceof&YITian)&{
&&&&&&&&&&&&System.out.println("监视到亦天制毒"+arg+"kg");
&&&&public&void&watchOverYiTian(YITian&yiTian){
&&&&&&&&yiTian.addObserver(this);
}最后是测试main方法/**&&
*&观察者模式
*&@author&minggo
*&@time&日&上午10:36:37
public&class&TestOberving&{
&&&&public&static&void&main(String[]&args)&{
&&&&&&&&YITian&yiTian&=&new&YITian();
&&&&&&&&LaughingSir&laughingSir&=&new&LaughingSir();
&&&&&&&&//Laughing&Sir卧底开始监视亦天的一举一动
&&&&&&&&laughingSir.watchOverYiTian(yiTian);
&&&&&&&&System.out.println("Laughing&Sir卧底开始监视亦天的一举一动");
&&&&&&&&System.out.println("-----------亦天开始制作毒品--------");
&&&&&&&&//亦天开始制毒
&&&&&&&&yiTian.MakeNarcotics();
&&&&&&&&System.out.println("-----------亦天结束制作毒品--------");
}五.现状下观察者模式的重要性就犹如Laughing哥这样的角色,观察者模式在实际应用中起到重要的作用。无论你之前发现了,还是现在察觉到它的不可忽视。移动开发的MVVM开发架构思想中的重要解耦页面部分,就是观察者模式实现数据绑定,即时刷新数据。这个在iOS中KVO和Android使用Java的Observer接口都异曲同工之意,RxJava的响应是编程的基本思想也是观察者模式之艺术。现状下的热门的移动开发的关键字,透漏出观察者模式显得越来越重要。其中就包括面试门槛,曾经面试过Android开发者还是iOS开发者,问到观察者这个模式可有了解?有回答iOS观察就是KVO,Java的就是在被观察对象添加回调接口,也有说过《设计模式之禅》中的韩非子被李斯监视的例子。今天,有多了一个,Laughing哥卧底神话,个人感情建议使用这个例子,呵呵~。六.效果图更直观效果图七.源码下载地址更详细Laughing哥如果见到我,估计会说:“小明,其实我真实的名字叫~谢天华!”
微信扫一扫
订阅每日移动开发及APP推广热点资讯公众号:CocoaChina
您还没有登录!请或
点击量12627点击量9185点击量7761点击量6500点击量6482点击量6416点击量6076点击量5818点击量5405
&2016 Chukong Technologies,Inc.
京公网安备89博客访问: 277046
博文数量: 107
博客积分: 1552
博客等级: 上尉
技术积分: 1091
注册时间:
专注于大规模运维场景运维工具解决方案。欢迎有这方面兴趣的朋友跟我联系。
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: Java
Singleton模式及应用创建型模型,它用来确保只产生一个实例,并提供一个访问它的全局访问点,对一些类来说只有一个实例是很重要的。比如有的时候数据库或SOCKET连接要受到一些限制,必须保持同一时间只有一个连接存在。为了实现 Singleton 模式,我们需要的是一个静态的变量,能够在不创建对象的情况下记忆是否已经产生过实例了.静态变量或静态方法都可以在不产生具体实例的情况下直接调用,这样的变量或方法不会因为类的实例化而有所改变.具体实施:一、用静态方法实现Singletonpackage com.singlone;class Sinleton{&&&&private static Sinleton s;&&&&private Sinleton(){&&&&&&&&&&&&}&&&&public static Sinleton getInstance(){&&&&&&&&if (s == null) {&&&&&&&&&&&&s = new Sinleton();&&&&&&&&}&&&&&&&&return s;&&&&}&&&&}public class SingletonTest {&&&&public static void main(String[] args) {&&&&&&&&Sinleton s1 = Sinleton.getInstance();&&&&&&&&Sinleton s2 = Sinleton.getInstance();&&&&&&&&if (s1 == s2) {&&&&&&&&&&&&System.out.println("s1 == s2");&&&&&&&&} else {&&&&&&&&&&&&System.out.println("s1 != s2");&&&&&&&&}&&&&}}用python 来实现的方法可以如下:class Sinleton(object):# Last Change:
2012-08-04 16:18:45&&&&def __new__(cls,
**kw ):&&&&&&&&if not hasattr(cls, '_instance'):&&&&&&&&&&&&orig = super(Sinleton, cls)&&&&&&&&&&&&cls._instance = orig.__new__(cls,
**kw )&&&&&&&&return cls._instanceclass MyClass(Sinleton):&&&&a
MyClass()two = MyClass()print id(one)print id(two)注意几点:1、在多线程的程序中,singleton可能会变的不可靠,可能会出现多个实例,解决的办法很简单,加个同步修饰符: public static synchronized Singleton getInstance(). 这样就保证了线程的安全性.[因为在一个线程内部是认为只有一个对象的]Observer模式主要的应用场景:分布式、多层系统多层体系结构:1、表示层。比如窗口、报表、页面2、业务逻辑层,管理业务过程的任务和规则,它又可以细分为领域对象层和服务层(提供数据库交互、安全性、打印报表)3、存储层- 持久化存储机制。如DB服务器等观察者模式又叫发布/订阅模式、模型-视图模式、源-监听器模式或从属者模式。观察者模式定义了一种一对多的依赖关系,可以让多个观察者对象同时监听某一个主题对象。当这个主题对象状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。观察者模式的类图如下:&可以看出,在这个观察者模式的实现里有下面这些角色:抽象主题(Subject)角色:主题角色把所有对观察考对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,主题角色又叫做抽象被观察者(Observable)角色,一般用一个抽象类或者一个接口实现。抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者角色一般用一个抽象类或者一个接口实现。在这个示意性的实现中,更新接口只包含一个方法(即Update()方法),这个方法叫做更新方法。具体主题(ConcreteSubject)角色:将有关状态存入具体现察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者角色(Concrete Observable)。具体主题角色通常用一个具体子类实现。具体观察者(ConcreteObserver)角色:存储与主题的状态自恰的状态。具体现察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体现察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。从具体主题角色指向抽象观察者角色的合成关系,代表具体主题对象可以有任意多个对抽象观察者对象的引用。之所以使用抽象观察者而不是具体观察者,意味着主题对象不需要知道引用了哪些ConcreteObserver类型,而只知道抽象Observer类型。这就使得具体主题对象可以动态地维护一系列的对观察者对象的引用,并在需要的时候调用每一个观察者共有的Update()方法。这种做法叫做"针对抽象编程"。我的理解:subject(目标):目标知道它的观察者。可以有任意多个观察者观察同一个目标。提供注册与删除观察者对象的接口。Observer(观察者):为那些在目标发生改变时需要获得通知的对象定义一个更新接口。ConcreteSubject(具体目标)将有关状态存入各ConcreteObserver对象。当他的状态发生改变时向他的各个观察者发出通知ConcreteObserver(具体观察者,Student):维护一个指向ConcreteSubject对象的引用。看下JAVA代码的一段演示public interface Subject {&&&&public void attach(Observer o);&&&&public void detach(Observer o);&&&&public void notice();}这个目标接口里面可以支持添加观察者、删除观察者与通知观察者消息的接口。public interface Observer {&&&&public void update();}观察者,定义了一个更新接口。表示目标发生了变化,那需要通知的对象定义了一个更新的接口。[我的理解就是:当我们的对象发生了状态变更的时候,实现了这个接口的对象们就要进行相应的更新处理]public class Teacher implements Subject{&&&&private String phone;&&&&private Vector students;&&&&&&&&public Teacher(){&&&&&&&&phone = "";&&&&&&&&students = new Vector();&&&&}&&&&&&&&&&&&@Override&&&&public void attach(Observer o) {&&&&&&&&// TODO Auto-generated method stub&&&&&&&&students.add(o);&&&&}&&&&@Override&&&&public void detach(Observer o) {&&&&&&&&// TODO Auto-generated method stub&&&&&&&&students.remove(o);&&&&}&&&&@Override& & //表示这个目标对象进行通知所有的观察者们,我的状态变更了你们要进行update操作了.&&&&public void notice() {&&&&&&&&// TODO Auto-generated method stub&&&&&&&&for (int i = 0; i < students.size(); i++) {&&&&&&&&&&&&((Observer)students.get(i)).update();&&&&&&&&}&&&&}&&&&public void setPhone(String phone) {&&&&&&&&this.phone = phone;&&&&&&&&notice();&&&&}&&&&public String getPhone() {&&&&&&&&return phone;&&&&}}这个是表示具体的目标。当它的状态发生更改的时候需要去通知各个观察者。public class Student implements Observer {&&&&private String name;&&&&private String phone;&&&&private Teacher teacher;&&&&public Student(String name,Teacher t){&&&&&&&&this.name = name;&&&&&&&&teacher = t;&&&&}&&&&&&&&public void show() {&&&&&&&&System.out.println("Name:" + name + "\n;Teacher phone:"+phone);&&&&}&&&&&&&&@Override&&&&public void update() {&&&&&&&&// TODO Auto-generated method stub&&&&&&&&phone = teacher.getPhone();&&&&}&&&&}具体的观察者们当收到目标的状态变更的时候触发自身的update操作。客户端的代码public class Client {&&&&public static void main(String[] args) {&&&&&&&&Vector students = new Vector();&&&&&&&&Teacher t = new Teacher();&&&&&&&&for (int i = 0; i < 3; i++) {&&&&&&&&&&&&Student st = new Student("lili" + i, t);&&&&&&&&&&&&students.add(st);&&&&&&&&&&&&t.attach(st);//将三个学生当成观察者给添加进来了&&&&&&&&}&&&&&&&&t.setPhone("123456");//notify通知那些观察者们要进行update操作了&&&&&&&&for (int i = 0; i < 3; i++) {&&&&&&&&&&&&((Student)students.get(i)).show();&&&&&&&&}&&&&&&&&t.setPhone("56789");&&&&&&&&for (int i = 0; i < 3; i++) {&&&&&&&&&&&&((Student)students.get(i)).show();&&&&&&&&}&&&&}}观察者模式的应用?观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时鉴定某一个主题对象,当这个主题对象发生状态变化的时候会通知所有订阅的对象。[这种是推的方式,对于订阅/主题还有一种推的方式的]实际上所做的工作def printInfo(info):&&&&print unicode(info, 'utf-8').encode('gbk')#抽象的通知者class Informer():&&&&observers
#所有观察者列表&&&&action
''&&&&def Attach(self, observer):&&&&&&&&self.observers.append(observer)&&&&def Notify(self):&&&&&&&&for o in self.observers:&&&&&&&&&&&&o.Update(self.action)#Teacherclass Teacher(Informer):&&&&action
'我是老师'class Observer():&&&&name
''&&&&def __init__(self, name):&&&&&&&&self.name
name&&&&def Update(self):&&&&&&&&pass#studentclass StudentObserver(Observer):&&&&name
''&&&&def __init__(self, name):&&&&&&&&Observer.__init__(self, name)&&&&def Update(self, subject):&&&&&&&&printInfo("%s, im, --%s" % (self.name, subject))if __name__
"__main__":&&&&teacher
Teacher()&&&&stuObserver
= StudentObserver("李明")&&&&teacher.Attach(stuObserver)&&&&teacher.Notify()这种是当主题发生变化 的时候主动地将消息推给了观察者们.
阅读(1039) | 评论(0) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。使用Java8实现观察者模式的方法(上)
字体:[ ] 类型:转载 时间:
本文给大家介绍使用java8实现观察者模式的方法,涉及到java8观察者模式相关知识,对此感兴趣的朋友一起学习吧
观察者(Observer)模式又名发布-订阅(Publish/Subscribe)模式,是四人组(GoF,即 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides)在1994合著的《设计模式:可复用面向对象软件的基础》中提出的(详见书中293-313页)。尽管这种模式已经有相当长的历史,它仍然广泛适用于各种场景,甚至成为了标准Java库的一个组成部分。目前虽然已经有大量关于观察者模式的文章,但它们都专注于在 Java 中的实现,却忽视了开发者在Java中使用观察者模式时遇到的各种问题。
本文的写作初衷就是为了填补这一空白:本文主要介绍通过使用 Java8 架构实现观察者模式,并在此基础上进一步探讨关于经典模式的复杂问题,包括匿名内部类、lambda 表达式、线程安全以及非平凡耗时长的观察者实现。本文内容虽然并不全面,很多这种模式所涉及的复杂问题,远不是一篇文章就能说清的。但是读完本文,读者能了解什么是观察者模式,它在Java中的通用性以及如何处理在 Java 中实现观察者模式时的一些常见问题。
观察者模式
根据 GoF 提出的经典定义,观察者模式的主旨是:
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
什么意思呢?很多软件应用中,对象之间的状态都是互相依赖的。例如,如果一个应用专注于数值数据加工,这个数据也许会通过图形用户界面(GUI)的表格或图表来展现或者两者同时使用,也就是说,当底层数据更新时,相应的 GUI 组件也要更新。问题的关键在于如何做到底层数据更新时 GUI 组件也随之更新,同时尽量减小 GUI 组件和底层数据的耦合度。
一种简单且不可扩展的解决方案是给管理这些底层数据的对象该表格和图像 GUI 组件的引用,使得对象可以在底层数据变化时能够通知 GUI 组件。显然,对于处理有更多 GUI 组件的复杂应用,这个简单的解决方案很快显示出其不足。例如,有20个 GUI 组件都依赖于底层数据,那么管理底层数据的对象就需要维护指向这20个组件的引用。随着依赖于相关数据的对象数量的增加,数据管理和对象之间的耦合度也变得难以控制。
另一个更好的解决方案是允许对象注册获取感兴趣数据更新的权限,当数据变化时,数据管理器就会通知这些对象。通俗地说就是,让感兴趣的数据对象告诉管理器:“当数据变化时请通知我”。此外,这些对象不仅可以注册获取更新通知,也可以取消注册,保证数据管理器在数据变化时不再通知该对象。在 GoF 的原始定义中,注册获取更新的对象叫作“观察者”(observer),对应的数据管理器叫作“目标”(Subject),观察者感兴趣的数据叫作“目标状态”,注册过程叫“添加”(attach),撤销观察的过程叫“移除”(detach)。前文已经提到观察者模式又叫发布-订阅模式,可以理解为客户订阅关于目标的观察者,当目标状态更新时,目标把这些更新发布给订阅者(这种设计模式扩展为通用架构,称为发布——订阅架构)。这些概念可以用下面的类图表示:
具体观察者(ConcereteObserver)用来接收更新的状态变化,同时将指向具体主题(ConcereteSubject)的引用传递给它的构造函数。这为具体观察者提供了指向具体主题的引用,在状态变化时可由此获得更新。简单来说,具体观察者会被告知主题更新,同时用其构造函数中的引用来获取具体主题的状态,最后将这些检索状态对象存储在具体观察者的观察状态(observerState)属性下。这一过程如下面的序列图所示:
经典模式的专业化
尽管观察者模式是通用的,但也有很多专业化的模式,最常见是以下两种:
为State对象提供一个参数,传给观察者调用的Update方法。在经典模式下,当观察者被通知Subject状态发生变化后,会直接从Subject获得其更新后状态。这要求观察者保存指向获取状态的对象引用。这样就形成了一个循环引用,ConcreteSubject的引用指向其观察者列表,ConcreteObserver的引用指向能获得主题状态的ConcreteSubject。除了获得更新的状态,观察者和其注册监听的Subject间并没有联系,观察者关心的是State对象,而非Subject本身。也就是说,很多情况下都将ConcreteObserver和ConcreteSubject强行联系一起,相反,当ConcreteSubject调用Update函数时,将State对象传递给ConcreteObserver,二者就无需关联。ConcreteObserver和State对象之间关联减小了观察者和State之间的依赖程度(关联和依赖的更多区别请参见Martin Fowler's的文章)。
将Subject抽象类和ConcreteSubject合并到一个 singleSubject类中。多数情况下,Subject使用抽象类并不会提升程序的灵活性和可扩展性,因此,将这一抽象类和具体类合并简化了设计。
这两个专业化的模式组合后,其简化类图如下:
在这些专业化的模式中,静态类结构大大简化,类之间的相互作用也得以简化。此时的序列图如下:
专业化模式另一特点是删除了 ConcreteObserver 的成员变量 observerState。有时候具体观察者并不需要保存Subject的最新状态,而只需要监测状态更新时 Subject 的状态。例如,如果观察者将成员变量的值更新到标准输出上,就可以删除 observerState,这样一来就删除了ConcreteObserver和State类之间的关联。
更常见的命名规则
经典模式甚至是前文提到的专业化模式都用的是attach,detach和observer等术语,而Java实现中很多都是用的不同的词典,包括register,unregister,listener等。值得一提的是State是listener需要监测变化的所有对象的统称,状态对象的具体名称需要看观察者模式用到的场景。例如,在listener监听事件发生场景下的观察者模式,已注册的listener将会在事件发生时收到通知,此时的状态对象就是event,也就是事件是否发生。
平时实际应用中目标的命名很少包含Subject。例如,创建一个关于动物园的应用,注册多个监听器用于观察Zoo类,并在新动物进入动物园时收到通知。该案例中的目标是Zoo类,为了和所给问题域保持术语一致,将不会用到Subject这样的词汇,也就是说Zoo类不会命名为ZooSubject。
监听器的命名一般都会跟着Listener后缀,例如前文提到的监测新动物加入的监听器会命名为AnimalAddedListener。类似的,register,、unregister和notify等函数命名常会以其对应的监听器名作后缀,例如AnimalAddedListener的register、unregister、notify函数会被命名为registerAnimalAddedListener、 unregisterAnimalAddedListener和notifyAnimalAddedListeners,需要注意的是notify函数名的s,因为notify函数处理的是多个而非单一监听器。
这种命名方式会显得冗长,而且通常一个subject会注册多个类型的监听器,如前面提到的动物园的例子,Zoo内除了注册监听动物新增的监听器,还需注册监听动物减少监听器,此时就会有两种register函数:(registerAnimalAddedListener和 registerAnimalRemovedListener,这种方式处理,监听器的类型作为一个限定符,表示其应观察者的类型。另一解决方案是创建一个registerListener函数然后重载,但是方案一能更方便的知道哪个监听器正在监听,重载是比较小众的做法。
另一惯用语法是用on前缀而不是update,例如update函数命名为onAnimalAdded而不是updateAnimalAdded。这种情况在监听器获得一个序列的通知时更常见,如向list中新增一个动物,但很少用于更新一个单独的数据,比如动物的名字。
接下来本文将使用Java的符号规则,虽然符号规则不会改变系统的真实设计和实现,但是使用其他开发者都熟悉的术语是很重要的开发准则,因此要熟悉上文描述的Java中的观察者模式符号规则。下文将在Java8环境下用一个简单例子来阐述上述概念。
一个简单的实例
还是前面提到的动物园的例子,使用Java8的API接口实现一个简单的系统,说明观察者模式的基本原理。问题描述为:
创建一个系统zoo,允许用户监听和撤销监听添加新对象animal的状态,另外再创建一个具体监听器,负责输出新增动物的name。
根据前面对观察者模式的学习知道实现这样的应用需要创建4个类,具体是:
Zoo类:即模式中的主题,负责存储动物园中的所有动物,并在新动物加入时通知所有已注册的监听器。
Animal类:代表动物对象。
AnimalAddedListener类:即观察者接口。
PrintNameAnimalAddedListener:具体的观察者类,负责输出新增动物的name。
首先我们创建一个Animal类,它是一个包含name成员变量、构造函数、getter和setter方法的简单Java对象,代码如下:
public class Animal {
public Animal (String name) {
this.name =
public String getName () {
return this.
public void setName (String name) {
this.name =
用这个类代表动物对象,接下来就可以创建AnimalAddedListener接口了:
public interface AnimalAddedListener {
public void onAnimalAdded (Animal animal);
前面两个类很简单,就不再详细介绍,接下来创建Zoo类:
public class Zoo {
private List&Animal& animals = new ArrayList&&();
private List&AnimalAddedListener& listeners = new ArrayList&&();
public void addAnimal (Animal animal) {
// Add the animal to the list of animals
this.animals.add(animal);
// Notify the list of registered listeners
this.notifyAnimalAddedListeners(animal);
public void registerAnimalAddedListener (AnimalAddedListener listener) {
// Add the listener to the list of registered listeners
this.listeners.add(listener);
public void unregisterAnimalAddedListener (AnimalAddedListener listener) {
// Remove the listener from the list of the registered listeners
this.listeners.remove(listener);
protected void notifyAnimalAddedListeners (Animal animal) {
// Notify each of the listeners in the list of registered listeners
this.listeners.forEach(listener -& listener.updateAnimalAdded(animal));
这个类比前面两个都复杂,其包含两个list,一个用来存储动物园中所有动物,另一个用来存储所有的监听器,鉴于animals和listener集合存储的对象都很简单,本文选择了ArrayList来存储。存储监听器的具体数据结构要视问题而定,比如对于这里的动物园问题,如果监听器有优先级,那就应该选择其他的数据结构,或者重写监听器的register算法。
注册和移除的实现都是简单的委托方式:各个监听器作为参数从监听者的监听列表增加或者移除。notify函数的实现与观察者模式的标准格式稍微偏离,它包括输入参数:新增加的animal,这样一来notify函数就可以把新增加的animal引用传递给监听器了。用streams API的forEach函数遍历监听器,对每个监听器执行theonAnimalAdded函数。
在addAnimal函数中,新增的animal对象和监听器各自添加到对应list。如果不考虑通知过程的复杂性,这一逻辑应包含在方便调用的方法中,只需要传入指向新增animal对象的引用即可,这就是通知监听器的逻辑实现封装在notifyAnimalAddedListeners函数中的原因,这一点在addAnimal的实现中也提到过。
除了notify函数的逻辑问题,需要强调一下对notify函数可见性的争议问题。在经典的观察者模型中,如GoF在设计模式一书中第301页所说,notify函数是public型的,然而尽管在经典模式中用到,这并不意味着必须是public的。选择可见性应该基于应用,例如本文的动物园的例子,notify函数是protected类型,并不要求每个对象都可以发起一个注册观察者的通知,只需保证对象能从父类继承该功能即可。当然,也并非完全如此,需要弄清楚哪些类可以激活notify函数,然后再由此确定函数的可见性。
接下来需要实现PrintNameAnimalAddedListener类,这个类用System.out.println方法将新增动物的name输出,具体代码如下:
public class PrintNameAnimalAddedListener implements AnimalAddedListener {
public void updateAnimalAdded (Animal animal) {
// Print the name of the newly added animal
System.out.println("Added a new animal with name '" + animal.getName() + "'");
最后要实现驱动应用的主函数:
public class Main {
public static void main (String[] args) {
// Create the zoo to store animals
Zoo zoo = new Zoo();
// Register a listener to be notified when an animal is added
zoo.registerAnimalAddedListener(new PrintNameAnimalAddedListener());
// Add an animal notify the registered listeners
zoo.addAnimal(new Animal("Tiger"));
主函数只是简单的创建了一个zoo对象,注册了一个输出动物name的监听器,并新建了一个animal对象以触发已注册的监听器,最后的输出为:
Added a new animal with name 'Tiger'
新增监听器
当监听器重新建立并将其添加到Subject时,观察者模式的优势就充分显示出来。例如,想添加一个计算动物园中动物总数的监听器,只需要新建一个具体的监听器类并注册到Zoo类即可,而无需对zoo类做任何修改。添加计数监听器CountingAnimalAddedListener代码如下:
public class CountingAnimalAddedListener implements AnimalAddedListener {
private static int animalsAddedCount = 0;
public void updateAnimalAdded (Animal animal) {
// Increment the number of animals
animalsAddedCount++;
// Print the number of animals
System.out.println("Total animals added: " + animalsAddedCount);
修改后的main函数如下:
public class Main {
public static void main (String[] args) {
// Create the zoo to store animals
Zoo zoo = new Zoo();
// Register listeners to be notified when an animal is added
zoo.registerAnimalAddedListener(new PrintNameAnimalAddedListener());
zoo.registerAnimalAddedListener(new CountingAnimalAddedListener());
// Add an animal notify the registered listeners
zoo.addAnimal(new Animal("Tiger"));
zoo.addAnimal(new Animal("Lion"));
zoo.addAnimal(new Animal("Bear"));
输出结果为:
Added a new animal with name 'Tiger'
Total animals added: 1
Added a new animal with name 'Lion'
Total animals added: 2
Added a new animal with name 'Bear'
Total animals added: 3
使用者可在仅修改监听器注册代码的情况下,创建任意监听器。具有此可扩展性主要是因为Subject和观察者接口关联,而不是直接和ConcreteObserver关联。只要接口不被修改,调用接口的Subject就无需修改。
匿名内部类,Lambda函数和监听器注册
Java8的一大改进是增加了功能特性,如增加了lambda函数。在引进lambda函数之前,Java通过匿名内部类提供了类似的功能,这些类在很多已有的应用中仍在使用。在观察者模式下,随时可以创建新的监听器而无需创建具体观察者类,例如,PrintNameAnimalAddedListener类可以在main函数中用匿名内部类实现,具体实现代码如下:
public class Main {
public static void main (String[] args) {
// Create the zoo to store animals
Zoo zoo = new Zoo();
// Register listeners to be notified when an animal is added
zoo.registerAnimalAddedListener(new AnimalAddedListener() {
public void updateAnimalAdded (Animal animal) {
// Print the name of the newly added animal
System.out.println("Added a new animal with name '" + animal.getName() + "'");
// Add an animal notify the registered listeners
zoo.addAnimal(new Animal("Tiger"));
类似的,lambda函数也可以用以完成此类任务:
public class Main {
public static void main (String[] args) {
// Create the zoo to store animals
Zoo zoo = new Zoo();
// Register listeners to be notified when an animal is added
zoo.registerAnimalAddedListener(
(animal) -& System.out.println("Added a new animal with name '" + animal.getName() + "'")
// Add an animal notify the registered listeners
zoo.addAnimal(new Animal("Tiger"));
需要注意的是lambda函数仅适用于监听器接口只有一个函数的情况,这个要求虽然看起来严格,但实际上很多监听器都是单一函数的,如示例中的AnimalAddedListener。如果接口有多个函数,可以选择使用匿名内部类。
隐式注册创建的监听器存在此类问题:由于对象是在注册调用的范围内创建的,所以不可能将引用存储一个到具体监听器。这意味着,通过lambda函数或者匿名内部类注册的监听器不可以撤销注册,因为撤销函数需要传入已经注册监听器的引用。解决这个问题的一个简单方法是在registerAnimalAddedListener函数中返回注册监听器的引用。如此一来,就可以撤销注册用lambda函数或匿名内部类创建的监听器,改进后的方法代码如下:
public AnimalAddedListener registerAnimalAddedListener (AnimalAddedListener listener) {
// Add the listener to the list of registered listeners
this.listeners.add(listener);
重新设计的函数交互的客户端代码如下:
public class Main {
public static void main (String[] args) {
// Create the zoo to store animals
Zoo zoo = new Zoo();
// Register listeners to be notified when an animal is added
AnimalAddedListener listener = zoo.registerAnimalAddedListener(
(animal) -& System.out.println("Added a new animal with name '" + animal.getName() + "'")
// Add an animal notify the registered listeners
zoo.addAnimal(new Animal("Tiger"));
// Unregister the listener
zoo.unregisterAnimalAddedListener(listener);
// Add another animal, which will not print the name, since the listener
// has been previously unregistered
zoo.addAnimal(new Animal("Lion"));
此时的结果输出只有Added a new animal with name 'Tiger',因为在第二个animal加入之前监听器已经撤销了:
Added a new animal with name 'Tiger'
如果采用更复杂的解决方案,register函数也可以返回receipt类,以便unregister监听器调用,例如:
public class AnimalAddedListenerReceipt {
private final AnimalAddedL
public AnimalAddedListenerReceipt (AnimalAddedListener listener) {
this.listener =
public final AnimalAddedListener getListener () {
return this.
receipt会作为注册函数的返回值,以及撤销注册函数输入参数,此时的zoo实现如下所示:
public class ZooUsingReceipt {
// ...Existing attributes and constructor...
public AnimalAddedListenerReceipt registerAnimalAddedListener (AnimalAddedListener listener) {
// Add the listener to the list of registered listeners
this.listeners.add(listener);
return new AnimalAddedListenerReceipt(listener);
public void unregisterAnimalAddedListener (AnimalAddedListenerReceipt receipt) {
// Remove the listener from the list of the registered listeners
this.listeners.remove(receipt.getListener());
// ...Existing notification method...
上面描述的接收实现机制允许保存信息供监听器撤销时调用的,也就是说如果撤销注册算法依赖于Subject注册监听器时的状态,则此状态将被保存,如果撤销注册只需要指向之前注册监听器的引用,这样的话接收技术则显得麻烦,不推荐使用。
除了特别复杂的具体监听器,最常见的注册监听器的方法是通过lambda函数或通过匿名内部类注册。当然,也有例外,那就是包含subject实现观察者接口的类和注册一个包含调用该引用目标的监听器。如下面代码所示的案例:
public class ZooContainer implements AnimalAddedListener {
private Zoo zoo = new Zoo();
public ZooContainer () {
// Register this object as a listener
this.zoo.registerAnimalAddedListener(this);
public Zoo getZoo () {
return this.
public void updateAnimalAdded (Animal animal) {
System.out.println("Added animal with name '" + animal.getName() + "'");
public static void main (String[] args) {
// Create the zoo container
ZooContainer zooContainer = new ZooContainer();
// Add an animal notify the innerally notified listener
zooContainer.getZoo().addAnimal(new Animal("Tiger"));
这种方法只适用于简单情况而且代码看起来不够专业,尽管如此,它还是深受现代Java开发人员的喜爱,因此了解这个例子的工作原理很有必要。因为ZooContainer实现了AnimalAddedListener接口,那么ZooContainer的实例(或者说对象)就可以注册为AnimalAddedListener。ZooContainer类中,该引用代表当前对象即ZooContainer的一个实例,所以可以被用作AnimalAddedListener。
通常,不是要求所有的container类都实现此类功能,而且实现监听器接口的container类只能调用Subject的注册函数,只是简单把该引用作为监听器的对象传给register函数。在接下来的章节中,将介绍多线程环境的常见问题和解决方案。
OneAPM 为您提供端到端的Java 应用性能解决方案,我们支持所有常见的 Java 框架及应用服务器,助您快速发现系统瓶颈,定位异常根本原因。分钟级部署,即刻体验,Java 监控从来没有如此简单。想阅读更多技术文章,请访问OneAPM 官方技术博客。
以上内容给大家介绍了使用Java8实现观察者模式的方法(上)的相关内容,下篇文章给大家介绍,感兴趣的朋友继续学习吧,希望对大家有所帮助!
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具

我要回帖

更多关于 观察者模式 java 的文章

 

随机推荐