设计模式之观察者模式(Observer)与Java委托

1.观察者模式的概念

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

2.观察者模式的结构图

这里写图片描述
Subject类:抽象通知者,它把所有对观察者对象的引用保存在一个聚集里,每个通知者都可以有任何数量的观察者。抽象通知者提供一个接口,可以增加和删除观察者对象。
ConcreteSubject类:具体通知者,将有关状态存入具体观察者对象;在具体通知者的内部状态改变时,给所有登记过的观察者发出通知。具体通知角色通常用一个具体子类实现。
Observer类:抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个Update方法,这个方法叫做更新方法。
ConcreteObserver类:具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。
由于下面的例子和观察者模式的这个结构图的代码实现类似,下面将举一个小例子来说明观察者模式如何去实现。

3.观察者模式的例子

  假设在一个公司里面,当老板出门去办事,这个时候公司里面就是老虎不在家,猴子称霸王。公司里面的有些员工就开始看自己的大盘炒股票,看NBA球赛等等。但是这样玩着好像心里一直都会悬着,他们就去跟前台的MM商量了一下,老板回来的时候给他们通知一声,然后他们停止娱乐活动,开始进入工作状态。
  在这个例子当中就是一个典型的观察者模式,其中前台的MM和老板都是具体的通知者(ConcreteSubject),而那些看股票,看NBA的人就是具体的观察者。通知者的公共行为抽象出来就成了抽象通知者(Subject),而具体的抽象出来就成了抽象观察者(Observer)。它们的关系如下图的UML结构图所示:
这里写图片描述
当我们画出这个结构图的时候,代码的构成也十分清晰了,下面我们来使用代码实现这个小例子:
Subject类(抽象通知者):

package com.jxs.observer;

/**
 * Created by jiangxs on 2018/5/8.
 *
 * 通知者接口
 */
public interface Subject {

    /**
     * 添加观察者
     * @param observer
     * */
    void attach(Observer observer);

    /**
     * 移除观察者
     * @param observer
     * */
    void detach(Observer observer);

    /**
     * 通知某个观察者
     * @param observer
     * @param info
     * */
    void notifyObserver(Observer observer,Object info);

    /**
     * 通知所有观察者
     * @param info
     */
    void notifyAllObserver(Object info);
}

Secretary类(ConcreteSubject):

package com.jxs.observer;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by jiangxs on 2018/5/8.
 */
public class Secretary implements Subject {

    // 同事列表
    private List<Observer> observers = new ArrayList<>();
    private String action;

    // 前台状态
    private String subjectState;

    public String getAction() {
        return action;
    }

    public void setAction(String action) {
        this.action = action;
    }

    @Override
    public void attach(Observer observer) {

        observers.add(observer);
    }

    @Override
    public void detach(Observer observer) {

        observers.remove(observer);
    }

    @Override
    public void notifyAllObserver(Object info) {

        for (Observer observer : observers) {
            observer.update(this, info);
        }
    }

    @Override
    public void notifyObserver(Observer observer, Object info) {

        observer.update(this, info);
    }
}

Boss类(ConcreteSubject):

package com.jxs.observer;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by jiangxs on 2018/5/8.
 */
public class Boss implements Subject {

    // 同事列表
    private List<Observer> observers = new ArrayList<>();
    private String action;

    // 老板状态
    private String subjectState;

    public String getAction() {
        return action;
    }

    public void setAction(String action) {
        this.action = action;
    }

    @Override
    public void attach(Observer observer) {

        observers.add(observer);
    }

    @Override
    public void detach(Observer observer) {

        observers.remove(observer);
    }

    @Override
    public void notifyAllObserver(Object info) {

        for (Observer observer : observers) {
            observer.update(this, info);
        }
    }

    @Override
    public void notifyObserver(Observer observer, Object info) {

        observer.update(this, info);
    }
}

Observer:

package com.jxs.observer;

/**
 * Created by jiangxs on 2018/5/8.
 *
 * 观察者接口
 */
public interface Observer {

    /**
     * 观察者根据具体情况进行相应的应对措施
     * @param subject
     * @param info
     * */
    void update(Subject subject, Object info);
}

StockObserver类(ConcreteObserver):

package com.jxs.observer;

/**
 * Created by jiangxs on 2018/5/8.
 */
public class StockObserver implements Observer {

    @Override
    public void update(Subject subject, Object info) {

        System.out.println(this.getClass().getSimpleName()
                +","+info+","
                +"赶紧关闭炒股软件!");
    }
}

NBAObserver类(ConcreteObserver):

package com.jxs.observer;

/**
 * Created by jiangxs on 2018/5/8.
 */
public class NBAObserver implements Observer {

    @Override
    public void update(Subject subject, Object info) {

        System.out.println(this.getClass().getSimpleName()
                +","+info+","
                +"赶紧关闭NBA直播视频!");
    }
}

客户端:

package com.jxs.observer;

/**
 * Created by jiangxs on 2018/5/8.
 */
public class Client {

    public static void main(String[] args) {

        Boss boss = new Boss();
        StockObserver stockObserver = new StockObserver();
        NBAObserver nbaObserver = new NBAObserver();

        boss.attach(stockObserver);
        boss.attach(nbaObserver);

        boss.notifyAllObserver("老板我回来了");
    }


}

运行结果:

StockObserver,老板我回来了,赶紧关闭炒股软件!
NBAObserver,老板我回来了,赶紧关闭NBA直播视频!

Process finished with exit code 0

4.观察者模式的总结

(1)观察者模式的优点

  观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体观察者列表,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。因此观察者模式很好地解耦了通知者与观察者,观察者不需要了解通知者内部是怎样实现的,方便于日后代码的修改,体现了依赖倒转的原则。

(2)观察者模式的缺点

①如一个主题被大量观察者注册,则通知所有观察者会花费较高代价
②如果某些观察者的响应方法被阻塞,整个通知过程即被阻塞,其它观察者不能及时被通知

(3)观察者模式的适用场景

①关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。
②事件多级触发场景。
③跨系统的消息交换场景,如消息队列、事件总线的处理机制。

5.使用观察者模式所遇到的问题

  我们从上面的例子中可以看到,当老板回来的时候,调用了Boss对象的notifyAllObserver方法,这个方法中调用了Observer接口中的update方法。但是当StockObserver和NBAObserver中的方法不一样(例如玩股票的员工听说老板来了,它要关闭股票软件,而看NBA的员工听说老板来了,它要关闭视频直播软件),无法抽取成一个接口,在notifyAllObserver方法中该如何去完成通知功能呢?判断每一个对象属于哪个具体的观察者,然后分别调用它们的方法?很显然这样不仅麻烦,写出的代码也具有很强的耦合性,那么我们该怎么办呢?这个时候,我们就要使用一个巧妙的方法————委托。

6.在Java中使用委托

  在《大话设计模式》当中,我发现当使用委托的时候,由于这本书用的是C#,所以里面使用delegate关键字就可以简单的去实现委托。于是我想在Java中有没有和C#一样的捷径去使用某个关键字或者其他简单的方式去实现委托呢?结果找了半天并没有发现解决委托的简单方式,于是,自己参考网上的例子,做了一个委托的封装去实现Java中的委托。
  我们还是以上面的例子为例,只是当老板回来,或者前台MM通知员工老板回来后,员工们具体的动作不一样。也就是取消了公共接口Observer。
代码实现如下所示:

首先我们定义一个事件类Event类:

package com.jxs.observerWithDelegate;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Created by jiangxs on 2018/5/8.
 * <p>
 * 事件类
 */
public class Event {

    // 要执行方法的对象
    private Object object;

    // 要执行方法的名称
    private String methodName;

    // 要执行方法的参数
    private Object[] params;

    // 要执行方法的参数类型
    private Class[] paramTypes;

    // 无参构造器
    public Event() {}

    // 带参构造器
    public Event(Object object, String methodName, Object... args){

        this.object = object;
        this.methodName = methodName;
        this.params = args;
        contractParamTypes(this.params);
    }

    // 根据参数数组生成参数类型数组
    private void contractParamTypes(Object[] params) {

        int length = params.length;
        this.paramTypes = new Class[length];
        for (int i = 0;i<length;i++) {
            paramTypes[i] = params[i].getClass();
        }
    }

    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Object[] getParams() {
        return params;
    }

    public void setParams(Object[] params) {
        this.params = params;
    }

    public Class[] getParamTypes() {
        return paramTypes;
    }

    public void setParamTypes(Class[] paramTypes) {
        this.paramTypes = paramTypes;
    }

    /**
     * 根据对象的方法和参数类型利用反射来执行相关方法
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public void invoke() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        Method method = object.getClass().getMethod(this.getMethodName(), this.getParamTypes());
        if (null == method) {
            return;
        }
        method.invoke(this.getObject(), this.getParams());
    }
}

在上面的代码中,我们将事件封装成Event并放入List集合events中,然后通知事件时,调用invoke方法;就和上面例子中将观察者放入集合当中,当通知观察者时,将观察者遍历,并调用update方法,让观察者完成更新动作的流程是一样的。

然后开始写EventHandler类,它是多个Event事件类的载体,同时它也提供一个为所有Event执行指定时间的功能。

package com.jxs.observerWithDelegate;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by jiangxs on 2018/5/8.
 * <p>
 * EventHandler类,Event类的载体,提供一个执行Event的所有方法的类
 */
public class EventHandler {

    // 使用List集合来装载事件
    private List<Event> events;

    // 在构造器中初始化List
    public EventHandler() {

        events = new ArrayList<>();
    }

    /**
     * 添加某个对象要执行的事件和所需要的参数
     * @param object
     * @param methodname
     * @param args
     */
    public void addEvent(Object object, String methodname, Object... args) {

        events.add(new Event(object, methodname, args));
    }

    /**
     * 通知所有对象执行指定的事件
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public void notifyAllObserver() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {

        for (Event event : events) {
            event.invoke();
        }
    }
}

前台和老板都属于通知者,这里将他们抽取出来作为一个抽象模板类Notifier

package com.jxs.observerWithDelegate;

/**
 * Created by jiangxs on 2018/5/8.
 * <p>
 * 通知者类
 * 前台的秘书和老板都相当于一个通知者,将两者共同点抽取出来成为一个抽象类
 * 他们有功能的方法:
 * ①可能有新的员工开始浪了,增加需要通知的员工
 * ②老板来了需要被通知
 */
public abstract class Notifier {

    private EventHandler eventHandler = new EventHandler();

    public EventHandler getEventHandler() {

        return eventHandler;
    }

    public void setEventHandler(EventHandler eventHandler) {

        this.eventHandler = eventHandler;
    }

    /**
     * 需要增加通知者
     * @param object 要添加的观察者
     * @param methodName 观察者中要委托的方法名
     * @param args 观察者中要委托方法的参数(没有参数时可不写)
     */
    public abstract void addListener(Object object, String methodName, Object... args);

    /**
     * 告诉所有人老板来了
     */
    public abstract void notifyAllObserver();
}

Secretary类(具体通知类,前台MM):

package com.jxs.observerWithDelegate;

import java.lang.reflect.InvocationTargetException;

/**
 * Created by jiangxs on 2018/5/8.
 * <p>
 * 通知者类Secretary类,Boss进来前通知所有人
 */
public class Secretary extends Notifier {

    @Override
    public void addListener(Object object, String methodName, Object... args) {

        System.out.println("有新的员工委托前台放哨了");
        // 使用工厂类获得EventHandler对象,然后调用该对象的方法
        this.getEventHandler().addEvent(object,methodName,args);
    }

    @Override
    public void notifyAllObserver() {

        System.out.println("老板回来了,要准备进去了!");
        try {
            this.getEventHandler().notifyAllObserver();
        }  catch (NoSuchMethodException e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        } catch (InvocationTargetException e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        }


    }
}

Boss类(具体通知类,老板):

package com.jxs.observerWithDelegate;

import java.lang.reflect.InvocationTargetException;

/**
 * Created by jiangxs on 2018/5/8.
 * <p>
 * 通知者类Boss类,Boss进来视察也相当于充当了通知者
 */
public class Boss extends Notifier {

    @Override
    public void addListener(Object object, String methodName, Object... args) {

        System.out.println("有新的员工要为老板的到来准备");
        // 使用工厂类获得EventHandler对象,然后调用该对象的方法
        this.getEventHandler().addEvent(object,methodName,args);
    }

    @Override
    public void notifyAllObserver() {

        System.out.println("老板走过来了!");
        try {
            this.getEventHandler().notifyAllObserver();
        }  catch (NoSuchMethodException e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        } catch (InvocationTargetException e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        }
    }
}

StockListener类(具体的观察者,炒股的员工):

package com.jxs.observerWithDelegate;

import java.util.Date;

/**
 * Created by jiangxs on 2018/5/8.
 *
 * 在上班时间炒股的员工
 */
public class StockListener {

    public void tradingStock() {

        System.out.println("炒股员工正在炒股" + new Date());
    }

    public void stopTradeStock() {

        System.out.println("老板来了,关闭炒股软件" + new Date());
    }
}

NBAListener类(具体的观察者,看NBA的员工):

package com.jxs.observerWithDelegate;

import java.util.Date;

/**
 * Created by jiangxs on 2018/5/8.
 *
 * 在上班时间看NBA的员工
 */
public class NBAListener {

    public void watchingNBA() {

        System.out.println("看球员工在看NBA" + " " + new Date());
    }

    public void stopWatchNBA() {

        System.out.println("老板来了,关闭NBA直播"+ " " +new Date());
    }
}

客户端:

package com.jxs.observerWithDelegate;

/**
 * Created by jiangxs on 2018/5/8.
 *
 * 客户端
 */
public class Client {

    public static void main(String[] args) {

        // 创建一个前台MM去放哨
        Secretary secretary = new Secretary();
        // 创建一个在工作时间炒股的员工
        StockListener stockListener = new StockListener();
        // 创建一个在工作时间看球的员工
        NBAListener nbaListener = new NBAListener();

        // 炒股的员工让前台帮他看着,老板来了记得提醒他
        secretary.addListener(stockListener, "stopTradeStock");

        // 看NBA的员工让前台帮他看着,老板来了记得提醒他
        secretary.addListener(nbaListener, "stopWatchNBA");

        // 看球员工和炒股员工开始愉快的玩耍
        stockListener.tradingStock();
        nbaListener.watchingNBA();

        // 他们愉快的玩了一会以后
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 前台通知所有人老板回来了
        secretary.notifyAllObserver();
    }

}

运行结果:

有新的员工委托前台放哨了
有新的员工委托前台放哨了
炒股员工正在炒股Thu May 10 08:14:33 CST 2018
看球员工在看NBA Thu May 10 08:14:33 CST 2018
老板回来了,要准备进去了!
老板来了,关闭炒股软件Thu May 10 08:14:34 CST 2018
老板来了,关闭NBA直播 Thu May 10 08:14:34 CST 2018

Process finished with exit code 0

7.通过委托的例子得到的结论

从上面的例子当中我们可以看到使用委托的优点有哪些呢?
(1)由于Event和EventHandler这两个通用性很好的类存在,通知者完全不知道观察者的存在,完全解耦。

(2)老板来了后炒股员工关闭炒股软件,看NBA的员工关闭直播软件,实现了一次通知就执行了不同类的不同方法。

(3)扩展性很高,假设再增加一个打王者荣耀的员工,只需要在客户端中向通知者增加一个观察者即可,通知者无需修改。同时代码的重用性好。

注:以上代码均可在github上进行下载:https://github.com/xsongj/designPattern

参考:
《大话设计模式》
https://blog.csdn.net/yanshujun/article/details/6494447

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值