观察者模式

观察者模式在软件幵发中用得最多的是窗体程序设计中的事件处理,窗体中的所有组件都是“事件源”,也就是目标对象,而事件处理程序类的对象是具体观察者对象。下面以一个学校铃声的事件处理程序为例,介绍 Windows 中的“事件处理模型”的工作原理。

观察者模式简述

观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

观察者模式结构与实现

 实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。

观察者模式主要结构如下:

  • 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  • 具体主题(Concrete    Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
  • 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  • 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

uml图如下:

 

 观察者模式代码实现如下:

/**
 * 抽象主题(被观察角色)
 */
public abstract class Subject{
    //观察者list集合(聚集对象)
    protected List<Observer> observerList=new ArrayList<Observer>();
    //注册观察者
    public void addObserver(Observer observer){
        observerList.add(observer);
    }
    //删除观察者
    public void removeObserver(Observer observer){
        if(observerList.contains(observer)){
            observerList.remove(observer);

        }
    }
    //通知观察者
    public abstract void notifyObserver();
}

/**
 * 具体主题角色类(被观察的类)
 */
public class ConcreteSubject extends Subject{
    @Override
    public void notifyObserver() {
        System.out.println("被观察者发现变化了...");
        for(Observer observer:super.observerList){
            observer.update();
        }

    }
}

/**
 * 抽象观察者
 */
public interface Observer {
    public void update();
}


/**
 * 具体观察者类
 */
public class ConcreteObserver1 implements Observer {
    @Override
    public void update() {
        System.out.println("observe1 更新了");
    }
}

/**
 * 具体观察者
 */
public class ConcreteObserver2 implements Observer {
    @Override
    public void update() {
        System.out.println("observer2 发生变更");
    }
}


/**
 * 客户端测试类
 */
public class ObserverClient {
    public static void main(String[] args) {

        Subject subject=new ConcreteSubject();
        Observer observer1=new ConcreteObserver1();
        Observer observer2=new ConcreteObserver2();
        subject.addObserver(observer1);
        subject.addObserver(observer2);
        //具体主题发生变化通知观察者。
        subject.notifyObserver();

    }
}

观察者模式案例

1.利用观察者模式设计一个程序,分析“人民币汇率”的升值或贬值对进口公司的进口产品成本或出口公司的出口产品收入以及公司的利润率的影响。

分析:。被观察的抽象主题对象就是汇率Rate,以人民币汇率RMBRate为具体主题。以公司Company为抽象观察者,出口公司ExportCompany为具体的观察者,进口公司ImportCompany为具体的观察者,人民币汇率(具体主题角色)升值时,进口公司(具体观察者对象)的进口产品成本降低且利润率提升,出口公司(具体观察者对象)的出口产品收入降低且利润率降低;当人民币汇率贬值时,进口公司的进口产品成本提升且利润率降低,出口公司的出口产品收入提升且利润率提升。总而言之当主题角色汇率发生变化时,观察者Company的利润也相应发生变化。

uml图如下:

 

 代码实现如下:

/**
 * 抽象观察者
 */
public interface Company {
    void update(int changeRate);
}

/**
 * 具体观察者进口公司ExportCompany
 */
public class ExportCompany implements Company {
    @Override
    public void update(int changeRate) {
        if(changeRate>=0){
            System.out.println("人民币汇率升值"+changeRate+"个基点,降低了进口产品成本,提升了进口公司利润率");

        } else {
            System.out.println("人民币汇率贬值"+(0-changeRate)+"个基点,提升了进口产品成本,降低了进口公司利润率。");

        }
    }
}


/**
 * 具体观察者进口公司importCompany
 */
public class ImportCompany implements Company {
    @Override
    public void update(int changeRate) {
        if(changeRate>=0){
            System.out.println("人民币汇率升值"+changeRate+"个基点,降低了进口产品成本,提升了进口公司利润率");

        } else {
            System.out.println("人民币汇率贬值"+(0-changeRate)+"个基点,提升了进口产品成本,降低了进口公司利润率。");

        }
    }
}

/**
 * 抽象主题角色
 */
public abstract class Rate {
    //观察者company对象list集合
    protected List<Company> companyList=new ArrayList<Company>();
    //增加观察者company对象
    public void addObserver(Company company){
        companyList.add(company);
    }
    //删除观察者company对象
    public void removeObserver(Company company){
        companyList.remove(company);
    }
    public abstract void change(int rateChange);
}

/**
 * 具体主题对象人民币汇率RMBRate
 */
public class RMBRate extends Rate {

    @Override
    public void change(int rateChange) {
        for (Company company:super.companyList){
            company.update(rateChange);
        }
    }
}
/**
 * 汇率变化观察者模式实现客户端类
 */
public class RateClient {
    public static void main(String[] args) {
        //具体观察对像进口公司
        Company importCompany=new ImportCompany();
        //具体观察者对象出口公司
        Company exportCompany=new ExportCompany();
        //具体主题角色人民币对象
        Rate rmbRate=new RMBRate();
        //增添观察者对象
        rmbRate.addObserver(importCompany);
        rmbRate.addObserver(exportCompany);
        //汇率提升
        rmbRate.change(2);
        //汇率降低
        rmbRate.change(-3);
    }
}

2.事件监听模式就是利用观察者模式实现的,java的awt窗体程序就是用观察者模式实现的,在软件幵发中窗体程序,界面程序都是基于事件监听模式实现。现在以awt的Button的点击事件为例,来了解一下事件监听模式。首先创建一个按钮Button类(事件源对象,被观察者对象),将按钮点击监听器注册在Button上,当用户点击按钮时,会产生一个按钮点击事件 ButtonClickEvent,将点击事件传给按钮点击监听器 ButtonClickListener,监听器做出相应的动作。

uml图如下:

 代码实现如下:

/**
 * 自定义按钮类,事件源对象
 */
public class Button {
    private String buttonName;

    protected List<Listener> listenerlist = new ArrayList<Listener>();
    //注册监听器
    public void addListener(Listener listener){
        listenerlist.add(listener);
    }
    //移除监听器
    public void removeListener(Listener listener){
        listenerlist.remove(listener);
    }
    //点击方法
    public void press(){
        ButtonClickEvent event=new ButtonClickEvent(this);
        for (Listener listener:listenerlist){
            listener.actionPerformed(event);
        }
    }

    public String getButtonName() {
        return buttonName;
    }

    public void setButtonName(String buttonName) {
        this.buttonName = buttonName;
    }
}

package cn.ck.observer.v4;


import java.util.EventObject;

/**
 * 按钮点击事件
 */

public class ButtonClickEvent extends EventObject {
   private Object source;
public ButtonClickEvent(Object source){ this.source=source; } } /** * 抽象监听器 */ public interface Listener { public void actionPerformed(ButtonClickEvent event); } /** * 按钮点击监听器 具体观察者 */ public class ButtonClickListener implements Listener { @Override public void actionPerformed(ButtonClickEvent event) { Button button= (Button) event.getSource(); System.out.println(button.getButtonName()+"按钮被点击了..."); } } /** * 按钮事件测试类 */ public class ButtonClient { public static void main(String[] args) { Button b1=new Button(); Listener listener=new ButtonClickListener(); b1.setButtonName("button1"); Button b2=new Button(); b2.setButtonName("button2"); b1.addListener(listener); b2.addListener(listener); b1.press(); b2.press(); } }

jdk的观察模式通过 java.util.Observable 类和 java.util.Observer 接口定义的,只要实现它们的子类就可以编写观察者模式实例。

1. Observable类

Observable 类是抽象目标类,它有一个 Vector 集合,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。

  1. void addObserver(Observer o) 方法:用于将新的观察者对象添加到向中Vector集合(线程安全)。
  2. void notifyObservers(Object arg) 方法:调用Vector集合中的所有观察者对象的 update。方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。
  3. void setChange() 方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真时,notifyObservers() 才会通知观察者。

2. Observer 接口

Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 void update(Observable o,Object arg) 方法,进行相应的工作。

还是以上面汇率的例子为例,用jdk的抽象观察者,抽象目标类实现观察者模式,具体代码如下:

/**
 * 具体主题对象人民币汇率RMBRate
 */
public class RMBRate extends Observable{

    public void change (int rateChange) {
        //changed变成true
        super.setChanged();
        super.notifyObservers(rateChange);
    }
}


/**
 * 具体观察者进口公司ExportCompany
 */
public class ExportCompany implements Observer{

    @Override
    public void update(Observable o, Object arg) {
        int changeRate= (int) arg;
        if(changeRate>=0){
            System.out.println("人民币汇率升值"+changeRate+"个基点,降低了进口产品成本,提升了进口公司利润率");

        } else {
            System.out.println("人民币汇率贬值"+(0-changeRate)+"个基点,提升了进口产品成本,降低了进口公司利润率。");

        }

    }
}

/**
 * 具体观察者进口公司importCompany
 */
public class ImportCompany implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        int changeRate= (int) arg;
        if(changeRate>=0){
            System.out.println("人民币汇率升值"+changeRate+"个基点,降低了进口产品成本,提升了进口公司利润率");

        } else {
            System.out.println("人民币汇率贬值"+(0-changeRate)+"个基点,提升了进口产品成本,降低了进口公司利润率。");

        }

    }
}


/**
 * 汇率变化观察者模式实现客户端类
 */
public class RateClient {
    public static void main(String[] args) {
        //具体观察对像进口公司
        Observer importCompany=new ImportCompany();
        //具体观察者对象出口公司
        Observer exportCompany=new ExportCompany();
        //具体主题角色人民币对象
        RMBRate rmbRate=new RMBRate();
        //增添观察者对象
        rmbRate.addObserver(importCompany);
        rmbRate.addObserver(exportCompany);
        //汇率提升
        rmbRate.change(2);
        //汇率降低
        rmbRate.change(-3);

    }
}

观察者模式优点与缺点

1.观察者模式优点

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  • 目标与观察者之间建立了一套触发机制。


2.观察者模式缺点

  • 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用(可以通过事件类引用一个目标对象解决这个问题)。
  • 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。