大话设计模式java版:观察者模式

看《大话设计模式》第14章观察者模式,发现一个问题:

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

所以其实只有一个主题对象,即Boss类和Secretary类在客户端程序中只能有一个出现,即1个观察者只能观察一个主题对象,而不是能观察多个(实际上,一个观察者也可以观察多个主题对象,但这就不属于观察者模式了)。先把这个问题说清楚。

 

什么时候使用观察者模式呢?

当一个对象的改变需要同时改变其他对象的时候

 

1. 代码示例一

 

1.1 Observer接口 StockObserver类 NBAObserver类

interface Observer{
    void update();
}

class StockObserver implements Observer {

    private String name;
    // Observer必须知道Notifier的状态信息, 所以它里面必须有个当前Notifier的对象成员.
    private Notifier notifier;

    public StockObserver(String name, Notifier notifier) {
        this.name = name;
        this.notifier = notifier;
    }

    @Override
    public void update() {
        System.out.println(notifier.getAction() +" " + name + "关闭股票行情,继续工作 ");
    }
}

class NBAObserver implements Observer {
    private String name;
    private Notifier notifier;
    public NBAObserver(String name, Notifier notifier) {
        this.name = name;
        this.notifier = notifier;
    }

    @Override
    public void update() {
        System.out.println(notifier.getAction() +" " + name + "关闭NBA,继续工作 ");
    }
}

 

1.2 Notifier接口 Boss类 Secretary类

interface Notifier{

    void attach(Observer observer);

    void detach(Observer observer);

    void notifier();

    void setAction(String action);

    String getAction();
}

class Secretary implements Notifier {

    //观察者列表 用LinkedHashSet防止出现重复通知的情况
    private Set<Observer> observers = new LinkedHashSet<>();
    //要观察的状态
    private String action;

    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifier() {
        for (Observer o : observers
                ) {
            o.update();
        }
    }

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

    @Override
    public String getAction() {
        return action;
    }
}

class Boss implements Notifier {

    //观察者列表
    private Set<Observer> observers = new LinkedHashSet<>();
    //要观察的状态
    private String action;

    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifier() {
        for (Observer o : observers
                ) {
            o.update();
        }
    }

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

    @Override
    public String getAction() {
        return action;
    }
}

 

1.3 Test类

 

public class Test {

    public static void main(String[] args) {
        Boss huhansan = new Boss();

        StockObserver tongshi1 = new StockObserver("魏关姹", huhansan);
        NBAObserver tongshi2 = new NBAObserver("易管查", huhansan);

        //注册两位同事
        huhansan.attach(tongshi1);
        huhansan.attach(tongshi2);
        //解除注册一位同事
        huhansan.detach(tongshi1);
        //发现老板回来
        huhansan.setAction("我胡汉三回来了!");
        //通知
        huhansan.notifier();
    }
}

 

1.4 类图

 

 

 

如书中说言观察者模式的不足,如果我们不想(或不能)为已有的StockObserver类及NBAObserver类实现Observer接口,那么如何在Notifier中可以添加若干个StockObserver或NBAObserver的对象并通知他们执行其各自的方法呢?

2. 代码示例二(对应C#委托)

 

2.1 StockObserver类NBAObserver类

class StockObserver{
    private String name;
    private Notifier notifier;

    public StockObserver(String name, Notifier notifier) {
        this.name = name;
        this.notifier = notifier;
    }

    public void CloseStockMarket(){
        System.out.println(notifier.getAction() +" " + name + "关闭股票行情,继续工作 ");
    }

    public void OpenStockMarket(){
        System.out.println(notifier.getAction() +" " + name + "打开股票行情 ");
    }
}

class NBAObserver{
    private String name;
    private Notifier notifier;

    public NBAObserver(String name, Notifier notifier) {
        this.name = name;
        this.notifier = notifier;
    }

    public void CloseNBADirectSeeding(){
        System.out.println(notifier.getAction() +" " + name + "关闭NBA直播,继续工作 ");
    }

    public void OpenNBADirectSeeding(){
        System.out.println(notifier.getAction() +" " + name + "打开NBA直播 ");
    }
}

2.2 Notifier接口ObjMethod类Boss类

interface Notifier{
    void notifier();
    void setAction(String action);
    String getAction();
}


/*这里稍稍想想, 到底如何将类StockObserver和类的指定方法传入到另1个类呢?
        其实我们可以把它拆分成两部分:
        1. 传送对象本身(Object).
        2. 传送方法的名字(String).
        至于怎样把这两种不同类型的东西放入类S的容器?  方法有很多种,
        这里我新建1个类ObjMethod, 把这两种东西封装在一起.
        而且我是打算把它放入HashSet容器的, 所以重写了hashCode()和 equals()方法, 只要上面两个成员相等, 我们就认为是相同的两个对象.*/

class ObjMethod {
    private Object obj;
    private String method;

    public ObjMethod(Object obj, String method){
        this.obj = obj;
        this.method = method;
    }

    public String getMethod() {
        return this.method;
    }

    public Object getObj() {
        return this.obj;
    }

    //自己定义相等的逻辑
    @Override
    public boolean equals(Object o){
        ObjMethod m = (ObjMethod)o;
        //对象之间== 比较两个对象引用中存储的对象地址是不是一样的

        //两个对象引用的地址相同 并且 调用的方法名也相同 那么就是相同的Object,不再插入HashSet中
        return (this.getObj() == m.getObj()) && (this.getMethod().equals(m.getMethod()));
    }

    //对HashSet而言,存入对象的流程为:
    //根据对象的hash码,经过hash算法,找到对象应该存放的位置,如果该位置为空,则将对象存入该位置;
    //如果该位置不为空,则使用equals()比较该位置的对象和将要入的对象,
    //如果两个相等,则不再插入,如果不相等,根据hash冲突解决算法将对象插入其他位置。
    @Override
    public int hashCode(){
        return this.getObj().hashCode() * this.getMethod().hashCode();
    }
}

class Boss implements Notifier {

    //需求:在StockObserver及NBAObserver不被修改的前提下(即不像demo03那样可以集成或实现Observe)
    //在Notifier中可以添加若干个StockObserver或NBAObserver的对象
    //并通知他们执行自己的方法

    private HashSet<ObjMethod> methodList = new HashSet<ObjMethod>();
    private String action;

    public void attach(Object obj, String method){
        this.methodList.add(new ObjMethod(obj,method));
    }
    public void detach(Object obj, String method){
        this.methodList.remove(new ObjMethod(obj,method));
    }
    public void detachAll(){
        methodList.clear();
    }
    @Override
    public void notifier() {
        if (this.methodList.isEmpty()){
            return;
        }
        //1. 从HashSet获取对象Obj 和 方法名method
        Iterator<ObjMethod> it = this.methodList.iterator();
        while (it.hasNext()){
            ObjMethod m = (ObjMethod)it.next();
            Class<?> objClass = m.getObj().getClass(); //get the class of the object
            try{
                Method method = objClass.getMethod(m.getMethod(), new Class[]{}); //no any parameters
                //在具有指定参数的指定对象上调用此Method对象表示的底层方法。
                method.invoke(m.getObj(),new Object[]{});//no parameters
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
    @Override
    public void setAction(String action) {
        this.action = action;
    }
    @Override
    public String getAction() {
        return action;
    }
}

 

2.3 Test类

 

public class Test {
    public static void main(String[] args) {
        Boss huhansan = new Boss();

        //关注胡汉三
        //我想关注多个通知者 如何解决呢? 留坑-->实际上 一个观察者也可以观察多个通知者的,但这就不属于观察者模式了
        StockObserver tongshi1 = new StockObserver("魏关姹", huhansan);
        NBAObserver tongshi2 = new NBAObserver("易管查", huhansan);
        StockObserver tongshi3 = new StockObserver("魏关姹", huhansan);

        huhansan.setAction("老板离开了");
        //清除通知列表
        huhansan.detachAll();
        //注册通知列表
        huhansan.attach(tongshi1,"OpenStockMarket");
        huhansan.attach(tongshi2,"OpenNBADirectSeeding");
        huhansan.notifier();

        //模拟一段时间后
        try {
            TimeUnit.SECONDS.sleep(2l);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //发现老板回来
        huhansan.setAction("老板回来了!");
        //清除通知列表
        huhansan.detachAll();
        //胡汉三注册通知列表
        huhansan.attach(tongshi1,"CloseStockMarket");
        huhansan.attach(tongshi2,"CloseNBADirectSeeding");
        //重复注册(无效)
        huhansan.attach(tongshi2,"CloseNBADirectSeeding");
        //解除注册
        huhansan.detach(tongshi1,"CloseStockMarket");
        huhansan.notifier();
    }
}

2.4 类图

 

 

通过类图可以看到

 

 

1. 观察者必须知道Notifier的状态信息,所以有一个当前Notifier的对象成员

2. 观察者本身并不知道其他观察者的存在

3. 通知者内部维护一个观察者的链表,可以注册和解除注册观察者。

4. 在notifier方法中执行了观察者的方法。而观察者具体的方法是放在各自的方法体内的。

 

3. 实际使用中的观察者模式

在微软Webform中,窗体在程序之外的一个单独的线程中运行。当我们双击webform中的按钮后会自动生成一个btn_OnClick的方法,然后在里面编写一些逻辑,同时也生成了btn.Click+=new EventHandler(btn_OnClick)代码(只是2.0之后这个代码就被隐藏起来了),这就是给按钮btn(订阅者)订阅了一个事件。这些逻辑理当属于按钮所在的页面,而不是需要执行这个方法的代码中。

当按钮点击之后,会触发页面的提交,webform框架可以获取是哪个按钮被点击过(发布者),然后执行btn.Click(),就可以执行我们具体的逻辑了。

同时,这里还涉及到线程间的回调,后台线程会执行窗体线程中的btn.Click方法,通过这种方式通知窗体线程按钮被点击了?

设想如果不用这个模式,按钮的Click方法是不是要写很多switch来判断是哪个按钮,然后调用该有的逻辑

综上来看,观察者模式是一个处理未知方法的模式,他漂亮的把具体逻辑分散到他该属于地方。使得发布者的代码保持不变,而订阅者的事件可以散布在他们自己的代码中。

参考:

观察者模式(Observer) 简介

Java 利用反射实现C#的委托

从抽象谈起(二):观察者模式与回调

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
环境:Windows XP professional, JDK 1.6, Eclipse 3.3 Europa. 我们说学习Java应该从Swing开始,那么学习Swing最重要的思想就是对于观察者模式的理解(Observer Pattern)。因为,该设计模式在Java Swing框架中贯穿了始终。对于C#的委托、代理概念所使用的Callback(回调模式--参见本人的“第一个C#程序Hello World ”资源)来看,Java Swing编程是纯粹的OO语言、是一种非常优雅的编程语言(本人认为)--而C#的学习好像非常强调所谓的OO思想,但是不是知道是什么OO思想?(个人观点,请勿激动)。 该示例是翻写一老外Observer Pattern的例子,本人觉得该示例是对该模式的非常精典的说明,于是花点功夫来翻写它,因为他的代码没有注释说明该模式的设计思想怎样体现在代码中去,所以,本人结合代码注释了关键代码的中文意义说明作者怎样表示该设计模式的,以方便Java程序员阅读和理解代码,从而理解该设计模式的思想(本人的注释非常详尽)。 目的,本人认为,作为Java程序员--不是指只会使用JBuilder(或者Websphare等)工具拖拽的java操作员--我们指至少能使用Eclipse工具书写Swing代码的Java编程员,或者使用ant工具编程的Java程序员,应该熟练掌握该设计模式!因为该设计模式贯穿Swing框架的始终。 阅读对象:本锦江中心中从S1阶段就想成为一个Java程序员的学员,并且在S1阶段已经非常认真的学习Java编程的学员。 注:不过该代码应该是本锦江中心Y2阶段Java方向学习的学员的参考代码,因为,该代码是由Y2阶段本教员会讲解。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值