1.引言
体育大厅是体育爱好者的绝佳运动场所。他们几乎涵盖了所有的运动项目,并提供最新的新闻、信息、比赛预定日期、特定球员或球队的信息。现在,他们计划提供现场解说或比赛分数作为短信服务,但只为他们的优质用户。他们的目标是在短时间间隔后,通过短信发送实况分数、比赛情况和重要事件。作为一个用户,你需要订阅这个包,当有一个现场比赛,你会得到一个短信到现场评论。该站点还提供了一个选项,可以随时取消订阅包。
作为一个开发者,体育游说团要求你为他们提供这个新功能。体育游说团的记者将坐在比赛的解说框中,他们将现场解说更新为解说对象。作为开发人员,您的工作是通过在注释对象可用时从注释对象获取注释来向注册用户提供注释。当有更新时,系统应通过向订阅用户发送短消息来更新他们。
这种情况清楚地显示了匹配和用户之间的一对多映射,因为可能有许多用户订阅一个匹配。观察者设计模式最适合这种情况,让我们看看这个模式,然后为运动大厅创建功能。
2.什么是观察者模式
观察者模式是一种与对象之间的责任分配有关的行为模式。行为模式描述了在运行时难以遵循的复杂控制流。它们将您的注意力从控制流中转移开,让您只关注对象相互连接的方式。
观察者模式定义了对象之间的一对多依赖关系,这样当一个对象改变状态时,它的所有依赖关系都会被自动通知和更新。观察者模式描述了这些依赖性。这个模式中的关键对象是被观察者和观察者。一个被观察者可以有任何数量的独立观察者。当被观察者的状态发生变化时,所有观察者都会得到通知。
理解观察者模式的另一种方法是发布者-订阅者关系的工作方式。例如,假设您订阅了您最喜欢的体育杂志或时尚杂志。每当一个新的版本发布时,它就会被交付给您。如果你不想再订阅杂志的时候就退订,杂志就不会发给你了。但是出版商继续像以前一样工作,因为还有其他人也订阅了那本特定的杂志。
观察者模式中有四个角色:- Subject被观察者,它必须能动态的添加、删除观察者。一般是抽象类或者实现类。
- Observer观察者定义了一个更新接口,应该在被观察者发生更改时得到通知。所有观察者都需要实现观察者接口。此接口有一个方法
update()
,当被观察者的状态更改时将调用该方法。 - ConcreteSubject具体的被观察者,存储
ConcreteObserver
对象的感兴趣状态。当状态改变时,它会向观察者发送通知。notifyObservers()
方法用于在状态更改时更新所有当前的观察器。 - ConcreateObserver具体的观察者,每个观察者在接收到消息的处理反应是不同的,各个观察者有自己的业务处理逻辑。
3.实例
基于此,我们首先创建一个观察者接口。其中有三个关键方法,如果需要,您可以根据需要添加更多的方法。package com.javacodegeeks.patterns.observerpattern;
public interface Subject {
public void subscribeObserver(Observer observer);
public void unSubscribeObserver(Observer observer);
public void notifyObservers();
public String subjectDetails();
}
复制代码
其中的三个关键方法:
subscribeObserver
它用于订阅观察员,或者我们可以说注册观察员,以便如果被观察者的状态发生变化,那么所有这些观察员都应该得到通知。unSubscribeObserver
用于取消订阅观察员,以便在被观察者的状态发生变化时,不应通知该未订阅观察员。notifyObservers
当被观察者的状态发生变化时,此方法通知注册的观察员。 还有一个可选的方法subjectDetails()
,它是一个很普通的方法,根据您的需要而定。在这里,它的工作是返回被观察者的细节。
现在,让我们看看观察者接口。
package com.javacodegeeks.patterns.observerpattern;
public interface Observer {
public void update(String desc);
public void subscribe();
public void unSubscribe();
}
复制代码
update
当被观察者的状态发生变化时,被观察者对观察者调用此方法以通知它。subscribe
方法用于订阅被观察者。unSubscribe
方法用于取消订阅被观察者。
package com.javacodegeeks.patterns.observerpattern;
public interface Commentary {
public void setDesc(String desc);
}
复制代码
以上接口供调用者更新评论对象的现场评论。它是一个可选的接口,只是遵循代码到接口的原则,与观察者模式无关。
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;
}
}
复制代码
上面的类作为一个具体的被观察者类,它实现了被观察者接口并提供了它的实现。它还存储了对它注册的观察员的引用。
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.");
}
}
复制代码
上面的类是实现观察者接口的具体观察者类。它还存储对它订阅的主题的引用,以及可选的用于显示用户信息的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
接口使它们成为被观察者的观察者。
4.基于Java内置的观察者模式
Java具有对观察者模式的内置支持。最常见的是Observer
接口和java.util包中的Observable
类。这些与我们的被观察者和观察者接口非常相似,但是可以提供开箱即用的大量功能。 让我们尝试使用Java内置的观察者模式来实现上面的例子。
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;
}
}
复制代码
我们扩展了可观察类,使我们的类成为一个被观察者,请注意,上面的类不包含任何对观察者的引用,它是由父类处理的。
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);
}
}
复制代码
上面的类实现了具有一个update
方法的观察者接口,当被观察者调用notifyobservers
方法时调用该接口。 让我们测试这个例子。
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
复制代码
Java为观察者模式提供了内置的工具,但也有其自身的缺点。Observable
是一个类,您必须继承它。这意味着您不能将Observable
的行为添加到已经扩展了另一个超类的现有类中。(单继承)
5.什么时候使用观察者模式
在以下任何情况下使用观察者模式:
- 当有两个抽象,一个依赖于另一个。将这些对象封装在单独的对象中可以让您独立地改变和重用它们。
- 当对一个对象的更改需要更改其他对象时,您不知道需要更改多少对象。
- 当一个对象能够通知其他对象而不必假设这些对象是谁时。换句话说,您不希望这些对象紧密耦合。