观察者模式

本文深入讲解了观察者模式的设计理念及实现方式,包括接口定义、主题与观察者交互过程,并探讨了模式在Java中的具体应用案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考:

(1)如何让孩子爱上设计模式 ——15.观察者模式(Observer Pattern):https://blog.csdn.net/coder_pig/article/details/60763259

我:

(1)Java设计模式

Java设计模式,又称为java管理模式,一切的设计都只是为了更好地管理某些事物(使得代码不重复累赘)。

注:
(1)Vector 是在 java 中可以实现自动增长的对象数组。
(2)在面向对象的概念里推荐的一条就是面向接口编程,所以在实际使用的时候好的编程习惯就应该针对接口去写实现。例如:动物有:天上飞,陆上跑,水里游。
对于陆地上的动物,实现“路上跑”接口,但是两栖动物就是实现2个接口。
使用的时候就通过水里游或者路上跑这样的接口去调用实际的方法。
引用: IA a = new A();
强制类型转换:A = (A)a;
(3)attribute和property在英语里有什么区别?
Property在英语里有财产的含义,一般指对象的组成部分,可以是简单数据也可以是对象或对象集合. Attribute多指一个对象的特征,绝大的数情况下是一个描述性的数据。
(4)comment,计算机专业术语,表示 HTML 或 XML 文档中的注释节点 的内容。
(5)Map集合中的entry是什么?
Map是’<key,value>'的集合,
而entry是【记录】的意思,在计算机里是【一对<key,value>】的意思。

(2)观察者模式

观察者模式图

(2.1)代码示例【one-to-many】 【一个主题对象-管理-多个观察者对象】的【创建和更新数据】

1)观察者接口IObserver
public interface IObserver{
     void receive(String data);  //更新一下通知的内容
}2)主题接口ISubject
public interface ISubject{
     void register(IObserver obr);  //注册观察者
     void unregister(IObserver obr); //注销观察者
     void notifyObservers();        //通知所有观察者
}3)主题实现类
public class Subject implements ISubject {
     private Vector<IObserver> vec = new Vector();  //观察者对象的维护向量(一个可以自增的观察者数组)
     private String data;     //主题的中心数据
     
     public String getData(){return data;}
     public void setData(String data){this.data = data;}

     public void register(IObserver obr){
           vec.add(obr);    //主题注册(添加)观察者
     }
     
     public void unregister(IObserver obr){
           if(vec.contains(obr))   
                 vec.remove(obr);  //主题注销(删除)观察者
     }

     public void notifyObservers(){
           for(int i = 0; i < vec.size(); i++){
                  IObserver obr = vec.get(i);   //遍历一下观察者数组
                  obr.receive(data);            //【主题通知所有观察者进行数据的接收和响应】
           }
     }
}4)一个具体观察者类Observer
public class Observer implements IObserver {
     public void receive(String data){
          System.out.println("我接收到主题的数据:"+data);
     }
}5)一个简单的测试类Test
public class Test{
     public static void main (String[] args){
          IObserver obr = new Observer();  //定义观察者对象
          ISubject sub = new Subject();  //定义主题对象
          sub.register(obr);             //主题添加观察者
          sub.setData("hello");         //主题中心数据发生变动
          sub.notifyObservers();        //通知所有观察者接收数据并进行数据响应
     }
}
泛型接口

注意:上文的data是String类型的,若改为其他类型,则IObserver接口等相关代码都需要进行修改。其实,只要把ISubject、IObserver接口改为泛型接口就可以了。
这样参数T就必须是类类型,不能是基本数据类型,如不能是int,但可以是Integer。

改为:
(1)观察者泛型接口IObserver<T>
public interface IObserver<T>{
      void receive(T data);
}2)主题泛型接口ISubject<T>
public interface ISubject<T>{
      void register(IObserver<T> obr);
      void unregister(IOservser<T> obr);
      void notifyObservsers();
}


“拉”数据【一个观察者对象 管理 一个主题对象】

注意:上文中观察者的data是主题对象直接“推”送给观察者的;但是
我们需要的是观察者自己主动去向主题对象“拉”数据。

改为:
(1)观察者【主动型】接口IObserver
public interface IObserver{
     void receive(ISubject obj);  //【为了防止耦合,此处参数类型设为主题接口】
}2)主题接口ISubject
public interface ISubject{
     void register(IObserver obr);  
     void unregister(IObserver obr);
     void notifyObservers();       
}3)主题实现类
public class Subject implements ISubject {
     //。。。
     public void notifyObservers(){
           for(int i = 0; i < vec.size(); i++){
                  IObserver obr = vec.get(i);   
                  obr.receive(this);        //【this指当前subject对象,代替原来的data】
           }
     }
}4)一个具体观察者类Observer
public class Observer implements IObserver {
     public void receive(ISubject obj){       //主题接口obj是通过【参数引用】的方式引用了主题对象
          Subject subject = (Subject)obj;      //必须进行强制类型转换
          System.out.println("我接收到主题的数据:"+subject.getData());
     }
}

增加父类层AbstractSubject

假设有多个主题类,且每个主题类的方法都是相同的,那么为了不重复register、unregister和notifyObsertvers方法,
我们用父类层(封装了这三个方法)来解决代码重复问题。

改为:
(1)增加的父类层AbstractSubject【封装了这三个方法,然后子类Subject在继承了此类的同时也继承了这三个方法】
public class AbstractSubject implements ISubject{
     Vector<IOserver> vec = new Vector();
     public void register(IObserver obr){
          vec.add(obr);
     }
     public void unregister(IObserver obr){
          if(vec.contains(obr))
               vec.remove(obr);
     }
     public void notifyObserver(){
          for(int i=0; i < vec.size(); i++){
               IObserver obr = vec.get(i);
               obr.receive(this);
          }
     }
}2)派生主题类Subject
public class Subject extends AbstractSubject{
     private Integer data;
     public Integer getData(return data;}
     public void setData(Integer data){this.data = data;}
}
避免重复添加同一类型的观察者对象
IObserver  obr1 = new Observer();
IObserver  obr2 = new Observer();
Subject sub = new Subject();
sub.register(obr1);
sub.register(obr2);

其中obr1和obr2是同一类型的观察者对象,但它们的物理地址是不同的,
最重要的是Vector类的contains()方法默认是物理查询,因此它们仍然都添加到了vec中,怎么办?
解决办法是【我们需要一个【MARK标志常量】来标记各自的观察者类型】,以便通过逻辑运算符【==】来判断MARK是否相等。

改为:
(1)添加了【getMark()多态方法】的观察者接口IObserver
public interface IObserver{
     int getMark();
     //...
}2)添加了【MARK标志常量】的观察者类
第一个类型的观察者类:
public class Observer1 implements IObserver{
     private final int MARK = 1;

     public int getMark(){return MARK;}

     public boolean equals(Object arg0){
            Observer obr = (Observer)arg0;
            return  obr.getMark() == MARK;
     }
     //...
}

第二个类型的观察者类:
public class Observer2 implements IObserver{
     private final int MARK = 2;

     public int getMark(){return MARK;}

     public boolean equals(Object arg0){
            Observer obr = (Observer)arg0;
            return  obr.getMark() == MARK;
     }
     //...
}

注解:为什么在观察者接口IObserver中必须添加一个多态方法getMark()?
因为register()方法的参数是IObserver类型,所以在IObserver中必须有一个getMark()方法来获取MARK值。

反射技术的应用

将观察者类信息封装在xml配置文件中,从而利用反射技术可以动态加载观察者对象。配置文件采用键值配对形式,值对应的是具体观察者的类名称。由于键是关键字,不能重复,为了编程方便,键采用“统一前缀+流水号”的形式,配置文件示例如下表:

说明:
键前缀“observer” 和 键流水号“1,2,3,...”
xml配置文件:                                                                           
<?xml version="1.0" encoding = "utf-8" standalone="no"?>                          
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">             
<properties>
     <comment>Observer</comment>
     <entry key="observer1">Observer1</entry>
     <entry key="observer2">Observer2</entry>
</properties>

具体程序代码如下:

public interface IObserver{//同上文}

public interface ISubject{
     void register(String strXMLPath); //表明从配置文件加载观察者对象
     void unregister(IObserver obr);
     void notifyObservers();
}
//主体类Subject
public class Subject implements ISubject{
     //其他代码同上文
     public void register(String strXMLPath){
           String prefix = "observer";
           String observerClassName = null;
           Properties p = Properties();
           try{
                FileInputStream in = new FileInputStream(strXMLPath);
                p.loadFormXML(in);
                int n = 1;
                while((observerClassName = p.getProperty(prefix+n))!=null){
                      Constructor c = Class.forName(observerClassName).getConstructor();
                      IObserver obr = (IObserver)Class.forName(observerClassName).newInstance();
                      vec.add(obr);
                      n++;
                }
                in.close();  
           }catch(Exception e){
                e.printStackTrace();
           }
     }
}

//一个具体观察者类Observer
public class Observer1 implements IObserver{/**同上文**/}
public class Observer2 implements IObserver{/**同上文**/}

//一个简单的测试类
public class Test{
      public static void main(String[] args) throws Exception{
              Subject sub = new Subject();         //定义主题对象
              sub.register("d:/config/info5.xml"); //主题通过配置文件加载观察者对象
              sub.setData("hello");                //主题数据变化了
              sub.notifyObservers();               //通知各个观察者对象进行数据响应
      }
}

JDK中的观察者设计模式

JDK的java.until包提供了系统的主题类Observable以及观察者接口Observer

应用探究

机房温度监测仿真功能。
分析:监测功能是以温度为中心的,因此用观察者设计模式实现程序架构是比较方便的。
总体思想是:温度作为主题类,两个观察者类,一个观察者负责记录数据,另一个观察者负责异常处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值