java设计模式————观察者模式,手写一个监听器

观察者模式(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.使用要得当,避免循环调用

附上彩蛋:谷歌提供,轻松落地观察者模式的一种方案。

发布了27 篇原创文章 · 获赞 1 · 访问量 3636

猜你喜欢

转载自blog.csdn.net/qq_40111437/article/details/104876868