观察者模式(Observer Pattern)
定义了对象之间的一对多依赖,让多个观察者对象同时监听一个主体对象,当主体对象发生变化时,它的所有依赖者(观察者)都会收到通知并更新。
属于行为型模式。
观察者模式有时也叫发布订阅模式(微信,qq通知等都用了这种模式)。
现实场景:
我们以两个qq之间互相发消息为现实场景来模拟一下:
/**
* @Author Darker
* @Note 我心净处,何处不是西天。
* @Descrption JDK提供的一种观察者的实现方式,我是qq主线程,我是被观察的人
* @E-Mail : [email protected]
* @Date : Created in 12:55 2020-3-15
*/
public class QQ extends Observable {
private String Name = "QQ";
private static QQ qq = null;
private QQ(){}
public static QQ getInstance(){
if(qq == null){
qq = new QQ();
}
return qq;
}
public String getName() {
return Name;
}
//发消息的人在qq上发起一个聊天,并把想要发的信息传过去
public void chat(MessageQQ message){
System.out.println(message.getQQnickName()+" 在"+this.Name+"上发了条消息,内容:"+message.getMessage());
setChanged();
notifyObservers(message);
}
}
/**
* @Author Darker
* @Note 我心净处,何处不是西天。
* @Descrption 接收消息的qq,观察者,我一直观察着是否有人给我发qq消息
* @E-Mail : [email protected]
* @Date : Created in 17:17 2020-3-15
*/
public class ReceiveQQ implements Observer {
private String QQnickName;
public ReceiveQQ(String name){
this.QQnickName = name;
}
//接收消息的qq,谁发给了我消息,就要自动调用这个方法
@Override
public void update(Observable o, Object arg) {
QQ qq = (QQ) o;
MessageQQ message = (MessageQQ) arg;
System.out.println("==============");
System.out.println(this.QQnickName+" 你收到了联系人 "+message.getQQnickName()+" 的一条消息,内容:"+message.getMessage());
}
}
/**
* @Author Darker
* @Note 我心净处,何处不是西天。
* @Descrption 发送消息的qq消息的人
* @E-Mail : [email protected]
* @Date : Created in 17:11 2020-3-15
*/
@Data
@AllArgsConstructor
public class MessageQQ {
private String QQnickName;
private String Message;
}
测试一下代码:
/**
* @Author Darker
* @Note 我心净处,何处不是西天。
* @Descrption 测试
* @E-Mail : [email protected]
* @Date : Created in 17:28 2020-3-15
*/
public class Client {
public static void main(String[] args) {
//启动qq了
QQ qq = QQ.getInstance();
//被动接收消息的人
ReceiveQQ 刀怒斩雪翼雕 = new ReceiveQQ("刀怒斩雪翼雕");
//点开要发送的老铁
qq.addObserver(刀怒斩雪翼雕);
//主动发送消息的人,要发送什么消息
MessageQQ 删库跑路 = new MessageQQ("删库跑路", "老铁,没毛病");
//回车发送给老铁
qq.chat(删库跑路);
}
}
这是用jdk实现观察者的方式,但是里面的原理是怎么样的呢,不急,监听器大家都听过吧,不过相信应该是前端小伙伴们用的比较多,比如给鼠标单击新增个事件之类的,那我们现在用java来实现一下。
/**
* @Author Darker
* @Note 我心净处,何处不是西天。
* @Descrption 监听器的一种包装
* @E-Mail : [email protected]
* @Date : Created in 17:50 2020-3-15
*/
@Data
@Accessors(chain = true)
public class Event {
//事件源,保存信息,事件是哪个类发起的
private Object source;
//发起这个事件要通知谁
private Object target;
//这个事件触发后要做什么,一个回调方法
private Method callback;
//事件的名称,触发的是什么事件
private String trigger;
//发生的时间
private long time;
public Event(Object target,Method callback){
this.target = target;
this.callback = callback;
}
}
/**
* @Author Darker
* @Note 我心净处,何处不是西天。
* @Descrption 监听器,观察者
* @E-Mail : [email protected]
* @Date : Created in 17:55 2020-3-15
*/
public class EventLisenter {
//JDK底层的Lisenter通常也是这样设计的
protected Map<String,Event> eventMap = new HashMap<>();
//事件名称和一个目标对象来触发事件
public void addLisenter(String eventType,Object target){
try {
this.addLisenter(eventType,target,target.getClass().getMethod("on"+toUpperFirstCase(eventType),Event.class));
} catch (Exception e) {
e.printStackTrace();
}
}
public void addLisenter(String eventType, Object target, Method callback){
//注册事件
eventMap.put(eventType,new Event(target,callback));
}
//触发,只要有动作就触发
public void trigger(Event event){
event.setSource(this);
event.setTime(System.currentTimeMillis());
try {
event.getCallback().invoke(event.getTarget(),event);
} catch (Exception e) {
e.printStackTrace();
}
}
//事件名称触发
protected void trigger (String trigger){
if(!this.eventMap.containsKey(trigger)){
return;
}
trigger(this.eventMap.get(trigger));
}
//一种高效的首字母大写方法
private String toUpperFirstCase(String str){
char[] chars = str.toCharArray();
chars[0] -= 32;
return String.valueOf(chars);
}
}
/**
* @Author Darker
* @Note 我心净处,何处不是西天。
* @Descrption
* @E-Mail : [email protected]
* @Date : Created in 18:21 2020-3-15
*/
public interface MouseEventType {
//单击
String ON_CLICK = "click";
//双击
String ON_DOUBLE_CLICK = "doubleClick";
}
/**
* @Author Darker
* @Note 我心净处,何处不是西天。
* @Descrption 鼠标,被观察者
* @E-Mail : [email protected]
* @Date : Created in 18:22 2020-3-15
*/
public class Mouse extends EventLisenter{
public void click(){
System.out.println("调用单击方法");
this.trigger(MouseEventType.ON_CLICK);
}
public void doubleClick(){
System.out.println("调用双击给老铁一波666");
this.trigger(MouseEventType.ON_DOUBLE_CLICK);
}
}
/**
* @Author Darker
* @Note 我心净处,何处不是西天。
* @Descrption 要回掉的方法
* @E-Mail : [email protected]
* @Date : Created in 18:25 2020-3-15
*/
public class MouseEvenCallback {
public void onClick(Event e){
System.out.println("======触发了单击事件======="+"\n"+e);
}
public void onDoubleClick(Event e){
System.out.println("======触发了双击事件,已经给老铁加了一个鸡腿======="+"\n"+e);
}
}
测试一下看看:
/**
* @Author Darker
* @Note 我心净处,何处不是西天。
* @Descrption
* @E-Mail : [email protected]
* @Date : Created in 18:27 2020-3-15
*/
public class MouseEvenTest {
public static void main(String[] args) {
Mouse mouse = new Mouse();
MouseEvenCallback callback = new MouseEvenCallback();
//给鼠标添加事件
mouse.addLisenter(MouseEventType.ON_DOUBLE_CLICK,callback);
mouse.doubleClick();
}
}
Event(
source=
DesigPnattern.observer.event.Mouse@71bc1ae4, (被监听的类)
target=DesigPnattern.observer.event.MouseEvenCallback@6ed3ef1, (对监听的类做出对应的事件的类)
callback=public void DesigPnattern.observer.event.MouseEvenCallback.onDoubleClick(DesigPnattern.observer.event.Event), (做出对应的事件)
trigger=null,
time=1584271173813)
以上的代码就是一个监听器的实现了,当然也可以把回调的方法动态传进去,有兴趣的小伙伴可以自己去写一下。
好,现在我们再回过头来看看我们jdk的监听者模式的代码,首先看看被观察者类。
被观察者里面放了一个Vector,里面是他所有的观察者。
然后找到里面的所有的观察者,循环调用他们的update方法,就像我们qq群发一样,选择了你要群发的人,然后每调用一下他们各自的update方法,也就是让他们各自都收到消息。
就像我们上面写的监听器,监听到了你想要的事件后,进行一个回调,我们就没有循环了,直接定位了一个回调方法。
一般spring中用了Listenter的类就是使用了观察者模式。
总结:
优点:
1.观察者和被观察者之间建立了一个抽象的耦合
2.观察者模式支持广播通信
缺点:
1.观察者之间有过多的细节依赖,提高时间消耗及程序的复杂度
2.使用要得当,避免循环调用
附上彩蛋:谷歌提供,轻松落地观察者模式的一种方案。