观察者设计模式示例
本文是我们名为“ Java设计模式 ”的学院课程的一部分。
在本课程中,您将深入研究大量的设计模式,并了解如何在Java中实现和利用它们。 您将了解模式如此重要的原因,并了解何时以及如何应用模式中的每一个。 在这里查看 !
1.观察者模式
体育大厅是运动爱好者的理想运动场所。 它们涵盖几乎所有类型的体育活动,并提供最新新闻,信息,比赛预定日期,有关特定球员或球队的信息。 现在,他们计划提供实时评论或比赛分数作为SMS服务,但仅针对其高级用户。 他们的目标是在短时间间隔后发送即时比分,比赛情况和重要事件的短信。 作为用户,您需要订阅该程序包,并且在进行实时比赛时,您会收到有关实时评论的短信。 该站点还提供了一个随时取消订阅包的选项。
作为开发人员,运动大厅要求您为他们提供此新功能。 体育大厅的记者将坐在比赛的评论框中,并将实时评论更新为评论对象。 作为开发人员,您的工作是通过从评论对象(如果有)中获取评论来向注册用户提供评论。 进行更新时,系统应通过向订阅的用户发送SMS来更新他们。
这种情况清楚地显示了匹配项和用户之间的一对多映射,因为可能有许多用户订阅单个匹配项。 观察者设计模式最适合这种情况,让我们了解一下这种模式,然后为Sport Lobby创建功能。
2.什么是观察者模式
观察者模式是一种行为模式,与对象之间的职责分配有关。 行为模式表征了复杂的控制流,这些流在运行时很难遵循。 它们使您的注意力从控制流上移开,使您可以专注于对象的互连方式。
观察者模式定义了对象之间的一对多依赖关系,因此当一个对象改变状态时,其所有依赖关系都会得到通知并自动更新。 观察者模式描述了这些依赖关系。 此模式中的关键对象是主题和观察者。 主题可以具有任意数量的从属观察者。 只要对象的状态发生变化,就会通知所有观察者。 作为响应,每个观察者将查询主题以使其状态与主题状态同步。
了解观察者模式的另一种方法是发布者与订阅者之间的关系。 例如,假设您订阅了自己喜欢的体育或时尚杂志的杂志。 每当发行新期刊时,它就会交付给您。 如果您在不再需要该杂志时退订它,则该杂志将不会发送给您。 但是出版商继续像以前一样工作,因为还有其他人也订阅了该杂志。
观察者模式有四个参与者:
- 主题,用于注册观察者。 对象使用此接口注册为观察者,也将自己从观察者中删除。
- 观察者,定义了一个对象的更新接口,该对象应该在主题更改时得到通知。 所有观察者都需要实现Observer接口。 此接口具有
update()
方法,当Subject的状态更改时将调用该方法。 - ConcreteSubject,将感兴趣的状态存储到ConcreteObserver对象。 状态更改时,它将向其观察者发送通知。 具体主题始终实现Subject接口。 每当状态更改时,
notifyObservers()
方法用于更新所有当前观察者。 - ConcreateObserver,维护对ConcreteSubject对象的引用,并实现Observer接口。 每个观察者都向具体主题注册以接收更新。
3.实施观察者模式
让我们看看如何在开发运动大厅功能时使用观察者模式。 有人将更新具体主题的对象,而您的工作是更新在具体主题对象中注册的对象的状态。 因此,只要具体主体对象的状态发生变化,就应该通知其所有依赖对象,然后进行更新。
基于此,我们首先创建一个Subject
接口。 在“主题”界面中,有三种主要方法,可以根据需要选择添加其他一些方法(如果需要)。
package com.javacodegeeks.patterns.observerpattern; public interface Subject { public void subscribeObserver(Observer observer); public void unSubscribeObserver(Observer observer); public void notifyObservers(); public String subjectDetails(); }
“ Subject
界面中的三个关键方法是:
-
subscribeObserver
,用于订阅观察者,或者我们可以说注册观察者,以便如果主题状态发生变化,则应通知所有这些观察者。 -
unSubscribeObserver
,用于取消订阅观察者,以便如果主题的状态发生变化,则不应通知此未订阅的观察者。 -
notifyObservers
,当主题状态发生变化时,此方法通知注册的观察者。
并且可选地,还有一个方法subjectDetails()
,这是一个简单的方法,并且可以根据您的需要。 在这里,其工作是返回主题的详细信息。
现在,让我们看看Observer
界面。
package com.javacodegeeks.patterns.observerpattern; public interface Observer { public void update(String desc); public void subscribe(); public void unSubscribe(); }
- 当主题状态发生变化时,主题会在观察者上调用
update(String desc)
方法,以通知该方法。 -
subscribe()
方法用于订阅主题。 -
unsubscribe()
,方法用于取消订阅主题。
package com.javacodegeeks.patterns.observerpattern; public interface Commentary { public void setDesc(String desc); }
报告者使用以上接口来更新评论对象的实时评论。 这是一个可选接口,仅用于遵循代码与接口的原则 ,与Observer模式无关。 您应该在适用的情况下应用oops原则以及设计模式。 该界面仅包含一种用于更改具体主题对象状态的方法。
package com.javacodegeeks.patterns.observerpattern; import java.util.List; public class CommentaryObject implements Subject,Commentary{ private final List<Observer>observers; private String desc; private final String subjectDetails; public CommentaryObject(List<Observer>observers,String subjectDetails){ this.observers = observers; this.subjectDetails = subjectDetails; } @Override public void subscribeObserver(Observer observer) { observers.add(observer); } @Override public void unSubscribeObserver(Observer observer) { int index = observers.indexOf(observer); observers.remove(index); } @Override public void notifyObservers() { System.out.println(); for(Observer observer : observers){ observer.update(desc); } } @Override public void setDesc(String desc) { this.desc = desc; notifyObservers(); } @Override public String subjectDetails() { return subjectDetails; } }
上面的类作为一个具体的主题,它实现Subject接口并提供其实现。 它还将引用存储到已注册的观察者。
package com.javacodegeeks.patterns.observerpattern; public class SMSUsers implements Observer{ private final Subject subject; private String desc; private String userInfo; public SMSUsers(Subject subject,String userInfo){ if(subject==null){ throw new IllegalArgumentException("No Publisher found."); } this.subject = subject; this.userInfo = userInfo; } @Override public void update(String desc) { this.desc = desc; display(); } private void display(){ System.out.println("["+userInfo+"]: "+desc); } @Override public void subscribe() { System.out.println("Subscribing "+userInfo+" to "+subject.subjectDetails()+" ..."); this.subject.subscribeObserver(this); System.out.println("Subscribed successfully."); } @Override public void unSubscribe() { System.out.println("Unsubscribing "+userInfo+" to "+subject.subjectDetails()+" ..."); this.subject.unSubscribeObserver(this); System.out.println("Unsubscribed successfully."); } }
上面的类是实现Observer
接口的具体观察者类。 它还存储对它订阅的主题的引用以及可选的用于显示用户信息的userInfo
变量。
现在,让我们测试示例。
package com.javacodegeeks.patterns.observerpattern; import java.util.ArrayList; public class TestObserver { public static void main(String[] args) { Subject subject = new CommentaryObject(new ArrayList<Observer>(), "Soccer Match [2014AUG24]"); Observer observer = new SMSUsers(subject, "Adam Warner [New York]"); observer.subscribe(); System.out.println(); Observer observer2 = new SMSUsers(subject, "Tim Ronney [London]"); observer2.subscribe(); Commentary cObject = ((Commentary)subject); cObject.setDesc("Welcome to live Soccer match"); cObject.setDesc("Current score 0-0"); System.out.println(); observer2.unSubscribe(); System.out.println(); cObject.setDesc("It's a goal!!"); cObject.setDesc("Current score 1-0"); System.out.println(); Observer observer3 = new SMSUsers(subject, "Marrie [Paris]"); observer3.subscribe(); System.out.println(); cObject.setDesc("It's another goal!!"); cObject.setDesc("Half-time score 2-0"); } }
上面的示例将产生以下输出:
Subscribing Adam Warner [New York] to Soccer Match [2014AUG24] ... Subscribed successfully. Subscribing Tim Ronney [London] to Soccer Match [2014AUG24] ... Subscribed successfully. [Adam Warner [New York]]: Welcome to live Soccer match [Tim Ronney [London]]: Welcome to live Soccer match [Adam Warner [New York]]: Current score 0-0 [Tim Ronney [London]]: Current score 0-0 Unsubscribing Tim Ronney [London] to Soccer Match [2014AUG24] ... Unsubscribed successfully. [Adam Warner [New York]]: It's a goal!! [Adam Warner [New York]]: Current score 1-0 Subscribing Marrie [Paris] to Soccer Match [2014AUG24] ... Subscribed successfully. [Adam Warner [New York]]: It's another goal!! [Marrie [Paris]]: It's another goal!! [Adam Warner [New York]]: Half-time score 2-0 [Marrie [Paris]]: Half-time score 2-0
如您所见,起初有两个用户订阅了足球比赛并开始接收评论。 但是后来有一个用户取消了订阅,因此该用户没有再收到评论。 然后,另一个用户订阅并开始获取评论。
所有这一切都是动态发生的,无需更改现有代码,不仅如此,假设该公司是否要在电子邮件上广播评论,或者任何其他公司希望与该公司合作以广播评论。 您需要做的就是创建两个新类,例如UserEmail
和ColCompany
并通过实现Observer
接口使它们成为主题的Observer
。 据Subject
知道它是观察者,它将提供更新。
4. Java内置的观察者模式
Java内置了对Observer
模式的支持。 最通用的是java.util
包中的Observer接口和Observable类。 这些与我们的“主题和观察者”界面非常相似,但是为您提供了许多现成的功能。
让我们尝试使用Java的内置Observer模式实现上述示例。
package com.javacodegeeks.patterns.observerpattern; import java.util.Observable; public class CommentaryObjectObservable extends Observable implements Commentary { private String desc; private final String subjectDetails; public CommentaryObjectObservable(String subjectDetails){ this.subjectDetails = subjectDetails; } @Override public void setDesc(String desc) { this.desc = desc; setChanged(); notifyObservers(desc); } public String subjectDetails() { return subjectDetails; } }
这次我们扩展了Observable
类,使我们的类成为一个主体,请注意,上面的类不包含对观察者的任何引用,它是由父类处理的,即Observable
类。 但是,我们声明了setDesc
方法来更改对象的状态,如上例所示。 setChanged
方法是上层类的方法,用于将更改的标志设置为true。 notifyObservers
方法通知其所有观察者,然后调用clearChanged
方法以指示此对象不再更改。 每个观察者都有使用两个参数调用的update方法:一个observable
对象和arg
参数。
package com.javacodegeeks.patterns.observerpattern; import java.util.Observable; public class SMSUsersObserver implements java.util.Observer{ private String desc; private final String userInfo; private final Observable observable; public SMSUsersObserver(Observable observable,String userInfo){ this.observable = observable; this.userInfo = userInfo; } public void subscribe() { System.out.println("Subscribing "+userInfo+" to "+((CommentaryObjectObservable)(observable)).subjectDetails()+" ..."); this.observable.addObserver(this); System.out.println("Subscribed successfully."); } public void unSubscribe() { System.out.println("Unsubscribing "+userInfo+" to "+((CommentaryObjectObservable)(observable)).subjectDetails()+" ..."); this.observable.deleteObserver(this); System.out.println("Unsubscribed successfully."); } @Override public void update(Observable o, Object arg) { desc = (String)arg; display(); } private void display(){ System.out.println("["+userInfo+"]: "+desc); } }
让我们讨论一些关键方法。
上面的类实现了Observer
接口,该接口具有一个关键方法update
,当主题调用notifyObservers
方法时会调用该方法。 update
方法采用一个Observable
对象和一个arg
作为参数。
addObserver
方法用于将观察者注册到主题,而deleteObserver
方法用于将观察者从主题列表中删除。
让我们测试一下这个例子。
package com.javacodegeeks.patterns.observerpattern; public class Test { public static void main(String[] args) { CommentaryObjectObservable obj = new CommentaryObjectObservable("Soccer Match [2014AUG24]"); SMSUsersObserver observer = new SMSUsersObserver(obj, "Adam Warner [New York]"); SMSUsersObserver observer2 = new SMSUsersObserver(obj,"Tim Ronney [London]"); observer.subscribe(); observer2.subscribe(); System.out.println("------------------------------------------------------"); obj.setDesc("Welcome to live Soccer match"); obj.setDesc("Current score 0-0"); observer.unSubscribe(); obj.setDesc("It's a goal!!"); obj.setDesc("Current score 1-0"); } }
上面的示例将产生以下输出:
Subscribing Adam Warner [New York] to Soccer Match [2014AUG24] ... Subscribed successfully. Subscribing Tim Ronney [London] to Soccer Match [2014AUG24] ... Subscribed successfully. ------------------------------------------------------ [Tim Ronney [London]]: Welcome to live Soccer match [Adam Warner [New York]]: Welcome to live Soccer match [Tim Ronney [London]]: Current score 0-0 [Adam Warner [New York]]: Current score 0-0 Unsubscribing Adam Warner [New York] to Soccer Match [2014AUG24] ... Unsubscribed successfully. [Tim Ronney [London]]: It's a goal!! [Tim Ronney [London]]: Current score 1-0
上面的类创建一个主题和两个观察者。 observer
的subscribe
方法将自己添加到主题观察者列表中。 然后setDesc
更改主题的状态,该主题调用setChanged
方法将更改标志设置为true,并通知观察者。 结果,调用了观察者的update
方法,该方法在内部对display
方法进行分类以显示结果。 后来,一位观察者unsubscribe
d,即从观察者列表中将其删除。 由于此原因,以后的评论未更新。
Java为观察者模式提供了内置功能,但是它也有其自身的缺点。 Observable
是一个类,您必须对其进行子类化。 这意味着您不能将Observable行为添加到已经扩展了另一个超类的现有类上。 这限制了重用潜力。 您甚至无法创建自己的实现,以与Java的内置Observer API配合使用。 Observable API中的某些方法受到保护。 这意味着除非已将Observable
子类setChange
否则无法调用setChange
类的方法。 而且,您甚至无法创建Observable
类的实例并将其与自己的对象组合在一起,因此必须子类化。 此设计违反了“优先继承而不是继承”的设计原则。
5.何时使用观察者模式
在以下任何一种情况下,请使用Observer模式:
- 当抽象具有两个方面时,一个方面依赖于另一个方面。 将这些方面封装在单独的对象中,可以使您分别进行更改和重用。
- 当更改一个对象需要更改其他对象时,您不知道需要更改多少个对象。
- 一个对象何时应该能够通知其他对象而无需假设这些对象是谁。 换句话说,您不希望这些对象紧密耦合。
6.下载源代码
这是“观察者模式”的一课。 您可以在此处下载源代码: ObserverPattern –第7课
翻译自: https://www.javacodegeeks.com/2015/09/observer-design-pattern.html