今天我们来谈谈观察者模式,这个模式其实无论是在编程还是在生活中都是随处可见的,就比如说,你在烧一壶水,某个时间段后,当水壶“叮”的一声,你知道水开了,这个时候,你可能会去拔掉电源插头,可能会去拿起水壶泡一壶茶;当然这一切到底做不做都取决于你,水壶已经履行了它的义务,“将水烧开再叮的一声通知你...”
又比如说,你在优酷订阅了某个有趣的节目,当节目有更新时,你会收到通知,当然,还有其它订阅了该频道的用户也会收到通知...等等等等,那么我们通过这两个列子来看看,观察者模式它是怎么定义的呢?它有些什么样的角色呢?让我们画个图来更直观的了解下吧!
画图之前,我们先说,观察者模式有哪些角色和定义 ;
1:观察者 ; 2:被观察者;3:事件触发;4:事件通知
我们先来将烧水这个例子画个图看看!
在这个图中,你在观察着水壶,而水壶将水烧开后,发出叮的一声告诉你水开了,你应该做些什么了! (注:面向对象中,我们可以将叮的一声看成一个行为,也就是一个方法)
那么,这里面的几个角色和动作,我们可以定义为:
你 ===== 观察者;水壶 ===== 被观察者;水开了 ===== 事件触发;叮的一声 ===== 事件通知;
这个例子有点抽象,让我们来看一个更具体的吧!
我们用第二个例子来画图!
我们先看看如果不用观察者模式,代码可能会是什么样子...
/**某个节目*/
class Broadcast {
//节目组名称
private String name ;
//发布节目内容
private String content;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public String getContent() {return content;}
public void setContent(String content) {this.content = content;}
}
/**用户*/
class User {
/**更新用户的通知栏显示内容方法
* @param content 通知内容
* */
public void notifyBar(String content){
System.out.println("收到通知:"+content);
}
}
/**栏目发布中心*/
public class PublishController {
/**发布节目*/
public void publish(Broadcast broadcast){
System.out.println("节目发布!");
//我们假设就只有一个用户,给用户发通知
User user = new User();
user.notifyBar("您关注的\""+broadcast.getName()+"\"发布了新的节目:"+broadcast.getContent());
}
}
public class Test {
public static void main(String[] args) {
Broadcast broadcast = new Broadcast();
broadcast.setName("社会百态");
broadcast.setContent("论未成年人的保护重要性");
PublishController pc = new PublishController();
pc.publish(broadcast);
}
}
输出结果:
**************************************************************************************
节目发布!
收到通知:您关注的"社会百态"发布了新的节目:论未成年人的保护重要性
**************************************************************************************
让我们接着看随着需求的变化,代码可能要发生怎样的改变...
/**栏目发布中心*/
public class PublishController {
/**发布节目*/
public void publish(Broadcast broadcast){
System.out.println("节目发布!");
//我们假设就只有一个用户,给用户发通知
User user = new User();
user.notifyBar("您关注的\""+broadcast.getName()+"\"发布了新的节目:"+broadcast.getContent());
//随着产品影响力的扩大和用户反馈,需求的迭代导致功能修改不可避免的发生,无论怎样,我们都得继续改动这个方法的代码
//需求1:某些质量比较好的节目,一旦发布就得推送到网站首页,好,咱们加代码!
Website ws = new Website();
ws.checkQuality(broadcast.getContent());
//需求2:每发布一次节目,节目制作方可获得100点积分...
//XXX xx = new Xxx();
//xx.xxx();
//需求3:每发布一次节目,节目制作方将获得一笔鼓励费用...
//需求4...以后可能还有各种各样的需求...
}
}
是的,我们看到了随着需求的变化对PublishController类带来的变化,看到了它的设计存在着众多的问题;
1:面向对象设计原则,多扩展开发,对修改关闭,很显然,它完全违背了这个思想,每次扩展功能都要修改既有代码,导致测试同童鞋每次都需要将全部功能测试一遍!;
2:与各种类严重耦合,我们说过,要依赖与抽象不依赖实现,这是对为今后能扩展功能的最基本的要求;
3:违背面向对象单一职责原则,它不应该知道具体如何去通知用户,如何去更新积分,如何去更新网站首页...更好的做法是,它只负责对某个抽象类发送一个通知,并告诉它们更新了什么东西,至于你接下来要做什么,我可不管!
好吧,不多说,我们马上来将上面的代码改造成观察者模式!
增加观察者和被观察者接口:
/**表示某个类具备观察的能力 (也可以称这个类为监听对象)*/
public interface Observer {
/**被观察者对观察者的事件通知方法,也可以称之为回调方法*/
void update(Broadcast broadcast);
}
/**表示某个对象具备被观察的能力*/
public interface Observerable {
/**注册一个观察者*/
void registryObserver(Observer observer);
/**移除一个观察者*/
void removeObserver(Observer observer);
/**通知所有观察者*/
void notifyAllObserver(Broadcast broadcast);
}
对user和Website的改动:
/**用户*/
class User implements Observer{
//保存被观察者的引用,这样一来,后期我可以自己决定是否要取消关注
private Observerable observerable;
public User( Observerable observerable ) {
this.observerable = observerable;
//添加对被观察者的监听
observerable.registryObserver(this);
}
/**更新用户的通知栏显示内容方法
* @param content 通知内容
* */
public void notifyBar(String content){
System.out.println("收到通知:"+content);
}
/**增加回调方法*/
@Override
public void update(Broadcast broadcast) {
notifyBar(broadcast.getContent());
}
}
/**表示整个站点*/
class Website implements Observer{
//保存被观察者的引用,这样一来,后期我可以自己决定是否要取消关注
private Observerable observerable;
public Website( Observerable observerable ) {
this.observerable = observerable;
//添加对被观察者的监听
observerable.registryObserver(this);
}
public void checkQuality(String content){
System.out.println("节目内容检查通过,被鉴定为优质节目...");
System.out.println("站点某个位置数据更新为:"+content);
}
/**增加回调方法*/
@Override
public void update(Broadcast broadcast) {
checkQuality(broadcast.getContent());
}
}
对PublishController的改动:
/**栏目发布中心*/
public class PublishController implements Observerable{
//增加一个集合用于维护所有的观察者
List<Observer> observers ;
public PublishController() {
observers = new ArrayList<>();
}
/**发布节目*/
public void publish(Broadcast broadcast){
System.out.println("节目发布!");
notifyAllObserver(broadcast);
}
@Override
public void registryObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
int index = observers.indexOf(observer);
if(index!=-1){
observers.remove(index);
}
}
/**通知所有观察者*/
@Override
public void notifyAllObserver(Broadcast broadcast) {
for (Observer observer : observers) {
observer.update(broadcast);
}
}
}
测试一下:
public class Test {
public static void main(String[] args) {
Broadcast broadcast = new Broadcast();
broadcast.setName("社会百态");
broadcast.setContent("论未成年人的保护重要性");
PublishController pc = new PublishController();
//可以随意增加监听用户了!
User user = new User(pc);
User user2 = new User(pc);
User user3 = new User(pc);
Website ws = new Website(pc);
pc.publish(broadcast);
//需求改动,说:不再需要更新站点...简单,我们只需要移除站点监听对象就OK啦!
pc.removeObserver(ws);
System.out.println("*******************************************");
pc.publish(broadcast);
//需求改动,说:要增加一个新的功能...
//简单,我们只需要这样做
//Observer xxx = new xxx();
//pc.registryObserver(xxx);
}
}
内容输出:
****************************************************************************
节目发布!
收到通知:论未成年人的保护重要性
收到通知:论未成年人的保护重要性
收到通知:论未成年人的保护重要性
节目内容检查通过,被鉴定为优质节目...
站点某个位置数据更新为:论未成年人的保护重要性
*******************************************
节目发布!
收到通知:论未成年人的保护重要性
收到通知:论未成年人的保护重要性
收到通知:论未成年人的保护重要性
****************************************************************************
这样一来,后面我们需要扩展功能,只需要增加一个监听对象,而要取消某个功能,只需要移除监听就可以啦!再看我们的PublishController类,无论对于以后的功能扩展和取消都完全不需要改变了!而且与具体的实体类,比如User,Website解耦了!在它世界里,耦合的对象只剩下Observer接口;
最后,让我们对观察者模式来下一个定义:
观察者模式:在对象与对象之间建立一对多的关系,当被监听的对象某些状态发生改变或者某个行为被触发,监听它的多的一方将自动收到通知并采取更新动作!
java也提供了Observer和Observable;只不过它的Observable是个类,你需要去继承,而我们的是个接口,可以更灵活的实现 ”多继承“!