观察者模式在实际工作中其实经常会遇到,凡是和事件相关的,基本上都与观察者模式沾边。观察者模式主要用于解决某个对象触发了一个事件,需要其他的对象来协同处理。
UML图如下
举个简单的例子,小孩半夜醒来开始哭,那么他的父母就会立马过来照看他,例如爸爸给他讲笑话,妈妈给他喂奶,这时候小孩就是父母的观察对象。
从程序的角度来说,小孩是被观察者,父母是观察者,同时也可以添加其他的观察者。首先先将观察者抽象成接口,只有一个方法用作处理。同时,父母是观察者的实现,具有不同的处理方式。
public interface Observer {
void dealWith();
}
public class Dad implements Observer{
@Override
public void dealWith() {
System.out.println("Dad tell jokes");
}
}
public class Mom implements Observer {
@Override
public void dealWith() {
System.out.println("Mom feed...");
}
}
小孩需要将这些观察者都存起来,到触发的时候直接依次调用他们的处理方法即可。
public class Child {
private List<Observer> observers = new ArrayList<>();
{
observers.add(new Dad());
observers.add(new Mom());
}
public void cry() {
System.out.println("child is crying");
for (Observer observer : observers) {
observer.dealWith();
}
}
}
测试一下
public class Main {
public static void main(String[] args) {
Child child = new Child();
child.cry();
}
}
以上就是一个简单的观察者模式实例。但是存在一个问题,那就是只要小孩一开始哭,那所有的观察者都要去进行处理。如果父母是分工明确的,孩子白天哭了爸爸管,孩子晚上哭了妈妈管,那当前这种方式就无法满足,简言之就是当前这种方式还不够灵活。
我们可以把孩子触发观察者处理的事件的具体信息,传递给观察者,让他们来判断,这个事件是不是需要自己去进行处理。我们可以抽象一个孩子的事件,其中有一个属性就是是否发生在晚上。
public class ChildEvent {
// 在晚上
private boolean inNight;
public ChildEvent(){}
public ChildEvent(boolean inNight){
this.inNight = inNight;
}
public boolean isInNight() {
return inNight;
}
public void setInNight(boolean inNight) {
this.inNight = inNight;
}
}
那么观察者接口也需要调整,因为要把事件对象传递进来,对应的父母的实现也需要增加判断。
public interface Observer {
void dealWith(ChildEvent event);
}
public class Dad implements Observer{
@Override
public void dealWith(ChildEvent event) {
if(!event.isInNight()){
System.out.println("Dad tell jokes");
}
}
}
public class Mom implements Observer {
@Override
public void dealWith(ChildEvent event) {
if(event.isInNight()) {
System.out.println("Mom feed...");
}
}
}
对应Child类的cry方法,也需要改变,这里我触发一个在晚上的事件。这样的话,就只有妈妈这个观察者会去执行具体的处理。
public class Child {
private List<Observer> observers = new ArrayList<>();
{
observers.add(new Dad());
observers.add(new Mom());
}
public void cry() {
System.out.println("child is crying");
ChildEvent event = new ChildEvent(true);
for (Observer observer : observers) {
observer.dealWith(event);
}
}
}
一般的话,对于事件这个类,其实也是可以进行抽象的,而且一般会在事件这个类中增加一个source属性,表示由哪个对象来触发的这个事件。就类似于JS中的event,比如click事件,在click方法内部调用event.target,就会得到一个触发这个click事件的DOM。
public class ChildEvent {
// 在晚上
private boolean inNight;
private Child source;
public ChildEvent(){}
public ChildEvent(boolean inNight){
this.inNight = inNight;
}
public ChildEvent(Child source){
this.source = source;
}
public boolean isInNight() {
return inNight;
}
public void setInNight(boolean inNight) {
this.inNight = inNight;
}
public Child getSource() {
return source;
}
public void setSource(Child source) {
this.source = source;
}
}
我这里的事件类叫做ChildEvent,所以我的source对象就直接写成了Child类型。如果事件也做了抽象的话,那么这个source一般是Object类型。