五、观察者模式Observer(行为型模式)

Observer模式美在哪里?

Observer缔造互换性。提升互换性的途径:降低依赖性。

定义

定义对象之间的一对多依赖关系,当一个对象改变状态时,它的所有依赖对象都会自动获得通知。

观察者模式定义了一个一对多的依赖关系,让一个或多个观察者对象监听一个主题对象。这样一来,当被观察者状态发生改变时,需要通知相应的观察者,使这些观察者对象能够自动更新。

关键要素

1.主题

主题是观察者观察的对象,一个主题必须具备下面三个特征。

  • 持有监听的观察者的引用
  • 支持增加和删除观察者
  • 主题状态改变,通知观察者

2.观察者

      当主题发生变化,收到通知进行具体的处理是观察者必须具备的特征。

为什么要用这种模式

      这里举一个例子来说明,牛奶送奶站就是主题,订奶客户为监听者,客户从送奶站订阅牛奶后,会每天收到牛奶。如果客户不想订阅了,可以取消,以后就不会收到牛奶。

松耦合

  • 观察者增加或删除无需修改主题的代码,只需调用主题对应的增加或者删除的方法即可。
  • 主题只负责通知观察者,但无需了解观察者如何处理通知。举个例子,送奶站只负责送递牛奶,不关心客户是喝掉还是洗脸。
  • 观察者只需等待主题通知,无需观察主题相关的细节。还是那个例子,客户只需关心送奶站送到牛奶,不关心牛奶由哪个快递人员,使用何种交通工具送达。

通知不错过

      由于被动接受,正常情况下不会错过主题的改变通知。而主动获取的话,由于时机选取问题,可能导致错过某些状态。

是一种委托式的IOC


抽象主题(Subject):它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。

具体主题(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。

抽象观察者(Observer):为所有的具体观察者定义一个接口,在得到主题通知时更新自己。

具体观察者(ConcreteObserver):实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调。

自己代码实现:
1、抽象主题subject,定义一个通知所有观察者方法;
public interface Subject {
    void notifyFighter();
}
2、 具体主题将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。
public class FangxueLe implements Subject {
    //存放所有观察者对象,也可以是一个观察者对象
        private int msg;
        public FangxueLe(int msg){
            this.msg = msg;
        }
        public void setMsg(int msg){
            this.msg = msg;
        }
        List<RoleObserver> list = new ArrayList<>();
        public void addRoleObserver(RoleObserver o){
            list.add(o);
        }
        public void removeRoleObser(RoleObserver o){
            list.remove(o);
        }
        public void notifyFighter(){
            for (int i = 0; i < list.size(); i++) {
                list.get(i).update(msg);
            }
        }
}
3、抽象观察者(Observer):为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
public interface RoleObserver {
    void update(int msg);
}
4、具体观察者(RoleA与RoleB ):
public class RoleA implements RoleObserver{
    @Override
    public void update(int msg) {
        if (msg == 0) {
            System.out.println("A : 再等等 ,他还没下课");
        } else if (msg == 1) {
            System.out.println("A : 小贱下课了 抄家伙");
        }
    }
}
public class RoleB implements RoleObserver{
    @Override
    public void update(int msg) {
        if (msg == 0) {
            System.out.println("B : 快点走 他快要下课了");
        } else if (msg == 1) {
            System.out.println("B : 快 他下课了 要挨揍。去帮忙");
        }
    }
}
测试类代码如下:
public static void main(String[] args) {
        FangxueLe f = new FangxueLe(0);
        RoleObserver a = new RoleA();
        RoleObserver b = new RoleB();
        f.addRoleObserver(a);
        f.addRoleObserver(b);
        f.notifyFighter();    //msg为0时的反应
        
        f.setMsg(1);    //msg为1时的反应
        f.notifyFighter();
    }
通过对msg的变化,两个观察者会做出不同的反应

Java实现

Java中有观察者模式使用的API

  • java.util.Observable 这是一个类,而非接口,主题需要继承这个类。
  • java.util.Observer   这是一个接口,监听者需要实现这个接口。

示例代码

import java.util.Observable;
import java.util.Observer;
public class MainRoot {
  public static void main(String[] args) {
      Observer consumer = new Consumer();
      MilkProvider provider = new MilkProvider();
      provider.addObserver(consumer);
      provider.milkProduced();
  }
    static class MilkProvider extends Observable {
      public void milkProduced() {
          setChanged();//状态改变,必须调用
          notifyObservers();
      }
  }
    static class Consumer implements Observer {
      @Override
      public void update(Observable arg0, Object arg1) {
          System.out.println("Consumer update..." + arg0 + ";arg1=" + arg1);
      }
  }
}

上述代码完成了

  • 将consumer加入主题provider的观察者行列
  • provider设置状态变化,通知持有的观察者
  • 观察者consumer收到通知,打印日志处理

setChanged为何物

      其实上述代码中存在这样一处代码setChanged();,如果在通知之前没有调用这个方法,观察者是收不到通知的,这是为什么呢?

这里我们看一下setChanged的源码

protected synchronized void setChanged() {
    changed = true;
}

很简单,然后找一下谁使用changed这个值

public synchronized boolean hasChanged() {
    return changed;
}

notifyObservers的代码

public void notifyObservers(Object data) {
    int size = 0;
    Observer[] arrays = null;
    synchronized (this) {
      if (hasChanged()) {
          clearChanged();
          size = observers.size();
          arrays = new Observer[size];
          observers.toArray(arrays);
      }
    }
    if (arrays != null) {
      for (Observer observer : arrays) {
          observer.update(this, data);
        }
    }
}

但是为什么要加入这样一个开关呢?可能原因大致有三点


1.筛选有效通知,只有有效通知可以调用setChanged。比如,我的微信朋友圈一条状态,好友A点赞,后续该状态的点赞和评论并不是每条都通知A,只有A的好友触发的操作才会通知A。

2.便于撤销通知操作,在主题中,我们可以设置很多次setChanged,但是在最后由于某种原因需要取消通知,我们可以使用clearChanged轻松解决问题。

3.主动权控制,由于setChanged为protected,而notifyObservers方法为public,这就导致存在外部随意调用notifyObservers的可能,但是外部无法调用setChanged,因此真正的控制权应该在主题这里。


主动获取

      观察者模式即所谓的推送方式,然而推送并非完美无缺。比如主题变化会推送大量的数据,而其中的一些观察者只需要某项数据,此时观察者就需要在具体实现中花费时间筛选数据。

      这确实是个问题,想要解决也不难,需要主题为某些数据提供getter方法,观察者只需调用getter取数据处理即可。


 static class MilkProvider extends Observable {
    public void milkProduced() {
      setChanged();//状态改变,必须调用
      notifyObservers();
    }
    public float getPrice() {
      return 2.5f;
    }
  }
  static class Consumer implements Observer {
    @Override
    public void update(Observable arg0, Object arg1) {
        MilkProvider provider = (MilkProvider)arg0;
        System.out.println("milk price =" + provider.getPrice());
    }
  }

不足与隐患

主要的问题表现在内存管理上,主要由以下两点

  • 主题持有观察者的引用,如果未正常处理从主题中删除观察者,会导致观察者无法被回收。
  • 如果观察者具体实现代码有问题,会导致主题和观察者对象形成循环引用,在某些采用引用计数的垃圾回收器可能导致无法回收。


加入反射的观察者:
interface WakenUpListener {
   void action2WakenUp(WakenUpEvent wakenUpEvent);
}
class WakenUpEvent {
   private long time;

   private String loc;

   private Child source;

   public WakenUpEvent(long time, String loc, Child source) {

      super();

      this.time = time;

      this.loc = loc;

      this.source = source;

   }

}
class Child implements Runnable {

   private List<WakenUpListener> wakenUpLinteners = new LinkedList<>();

   public void addWakenUpListener(WakenUpListener l) {
      wakenUpLinteners.add(l);
   } 

   void wakeUp() {
      for (WakenUpListener listener : wakenUpLinteners) {
        listener.action2WakenUp(new WakenUpEvent(
              System.currentTimeMillis(), "bed", this));
      }
   }

   @Override
   publicvoid run() {
      try {
        Thread.sleep(1000);
      } catch (Exception e) {
        e.printStackTrace();
      }
      wakeUp();
   }
}
class GrandFather implements WakenUpListener {

   public void action2WakenUp(WakenUpEvent wakenUpEvent) {

      System.out.println("hug child");

   }

}
class Mom implements WakenUpListener {

   publicvoid action2WakenUp(WakenUpEvent wakenUpEvent) {

      System.out.println("kiss child");

   }

}
class Dad implements WakenUpListener {

   publicvoid action2WakenUp(WakenUpEvent wakenUpEvent) {

      System.out.println("feed child");

   }

}


observer.properties:
    observers=test.Dad,test.GrandFather,test.Mom
 

public class Test {

   publicstaticvoid main(String[] args) {

      Child c = new Child();

      Properties properties = new Properties();

      try {

        properties.load(Test.class.getClassLoader().getResourceAsStream(

              "test/observer.properties"));

      } catch (IOException e) {

        e.printStackTrace();

      }

      String[] observers=properties.getProperty("observers").split(",");

     

      for(String s:observers){

        try {

           c.addWakenUpListener((WakenUpListener)Class.forName(s).newInstance());

        } catch (InstantiationException e) {

           e.printStackTrace();

        } catch (IllegalAccessException e) {

           e.printStackTrace();

        } catch (ClassNotFoundException e) {

           e.printStackTrace();

        }

      }

      new Thread(c).start();

   }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值