推模式与拉模式
我们上面的场景是观察者模式中的推模式,这种场景是主题主动向观察者推送数据,不管观察者需要不需要。推模式的前提是主题对象知道观察者需要的数据,观察中的update()方法里的参数是按照需要定义的方法,但是随着业务的发展会出现考虑不到的情形。
比如我们上述场景中粉丝只需要知道公众号发布的内容,所以我们先约定String类型的参数,但是有些粉丝却想知道这个消息的真实作者是谁(是否转载?),这个时候就需要提供新的方法,比如update(String message,String author),或者干脆重新实现观察者,不管如何都得作相应的改动。
观察者模式还有另外一个模式拉模式,这个模式不需要知道观察者需要什么数据,他把主题自身都传递给观察者,update(Subject subject),然后对外提供一些getter方法,让观察者按需来取,这样基本上可以适用各种情况的需要。
接下来我们用拉模式来实现上面的场景。
抽象观察者
这里我们不再适用约定参数处理recive方法,而是使用主题直接作为参数。
public interface Observer { /** * 使用主题作为参数 * @param subject */ public void recive(Subject subject); }
具体观察者
收到通知后,我们按需从主题对象中获取相应的数据。
public class Fans implements Observer { private String name; public Fans(String name) { this.name = name; } @Override public void recive(Subject subject) { //观察者可以同时观察多个主题 //所以我们需要确保被观察者属于我们需要的WechatServer类型 //如果是其他类型可能需要作其他方式处理 if(subject instanceof WechatServer){ WechatServer wechatServer = (WechatServer) subject; System.out.println("粉丝 " + this.name + " 收到消息: " + wechatServer.getMessage() + " 作者是:" + wechatServer.getAuthor()); } } }
抽象主题
未发生变化
public interface Subject { /** * 注册成为观察者 * @param observer */ public void attach(Observer observer); /** * 删除观察者 * @param observer */ public void detach(Observer observer); /** * 通知所有观察者 */ public void notifyObservers(); }
具体主题
重点关注 notifyObservers()
方法,直接将this即当前主题作为参数传递给观察者,并对外提供 getMessage()
和 getAuthor()
方法,好让观察者对象可以方便取走想要的数据。
public class WechatServer implements Subject { private ArrayList<Observer> observers; private String message; private String author; /** * 对外提供获取内容的方法 * @return */ public String getMessage() { return message; } /** * 对外提供获取作者的方法 * @return */ public String getAuthor() { return author; } public WechatServer() { observers = new ArrayList<>(); } /** * 让一个用户注册成为观察者即粉丝 * @param observer */ @Override public void attach(Observer observer) { observers.add(observer); } /** * 不喜欢这个观察者,删除掉 * @param observer */ @Override public void detach(Observer observer) { observers.remove(observer); } /** * 通知所有的观察者 */ @Override public void notifyObservers() { for (Observer observer : observers) { //这里不再是具体的参数,而是把主题自身给通知给观察者 observer.recive(this); } } /** * 当主题发生变化时通知观察者 * @param message */ public void publish(String message,String author){ this.message = message; this.author = author; System.out.println("webchat publish message:" + message); notifyObservers(); } }
测试类
public class Test { public static void main(String[] args) { WechatServer wechatServer = new WechatServer(); Fans fans1 = new Fans("张三"); Fans fans2 = new Fans("李四"); Fans fans3 = new Fans("杨五"); wechatServer.attach(fans1); wechatServer.attach(fans2); wechatServer.attach(fans3); wechatServer.publish("CRUD很无聊?跟我一起学命令模式吧!","JAVA日知录"); System.out.println("--------"); wechatServer.detach(fans1); wechatServer.publish("CRUD很无聊?跟我一起学观察者模式吧!","JAVA日知录"); } }
执行结果
使用拉模式后只要主题提供了对应的get方法,基本可以满足各种需求的场景。
再深入一点
观察者模式在JAVA中已经有相应的实现,抽象观察者角色由 java.util.Observer
充当,抽象主题角色由 java.util.Observable
充当。
我们可以利用java内置的观察者模式很容易实现上面的推模式和拉模式的场景代码,这里就不再演示了。
最后提醒大家一下,主题角色Observable是一个类,我们要想要实现具体的主题必须要继承它,如果某类想同时具有Observable和其他一个超类的行为,就会陷入两难,毕竟JAVA不支持多重继承。
如果你的应用场景中不需要考虑如上情形,那么Observable可能会符合你的需求,否则还是需要使用自定义观察者模式来实现你的需求,反正这也很简单,不是吗?