Java设计模式----观察者模式

交通信号灯相信大家都不陌生,红灯停、绿灯行,见了黄灯等一等。交通信号灯、海边的信号塔等等,都伫立在那里,随时准备给行人/货船发送信号,以提醒行人/货船该采取相应行动了。信号灯就是发布信号的对象,行人是接收信号并采取具体行动的观察者。这一过程,在程序设计中也常常用到,即本文介绍的观察者模式。


1.观察者模式

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

在事件驱动的GUI开发中观察者模式十分常用。另外,著名的MVC软件设计思想也应用了观察者模式:

  • Model 业务模型,相当于观察者模式中的主题对象
  • View 视图层,相当于观察者
  • Controller 控制器,协调M和V

当Model数据发生变化时,会通知View更新视图。所以,观察者模式又叫做模型-视图模式,或者发布-订阅模式。


2. 代码实现

2.1 紧耦合形式

 Observer抽象类,有三个子类:HexObserver十六进制, OctObserver八进制,BinObserver二进制,注意观察着Observer持有主题Subject的引用。

Subject主题对象持有Observer的引用,并提供添加add观察者的方法, 当Subject状态改变时setState, 会调用execute方法,execute方法中遍历所有观察者对象并调用每个观察者对象的update方法更新,观察者对象的update方法中,通过回调主题对象的getState()方法获取主题的状态。

这种方式Subject持有Observer引用, Observer也持有Subject的引用,就是紧耦合的实现方式。

abstract class Observer {
    protected Subject subject;
    public abstract void update();
}

class Subject {
    private List<Observer> observers = new ArrayList<>();
    private int state;

    public void add(Observer o) {
        observers.add(o);
    }

    public int getState() {
        return state;
    }

    public void setState(int value) {
        this.state = value;
        execute();
    }

    private void execute() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

class HexObserver extends Observer {
    public HexObserver(Subject subject) {
        this.subject = subject;
        this.subject.add(this);
    }

    public void update() {
        System.out.print(" " + Integer.toHexString(subject.getState()));
    }
}

class OctObserver extends Observer {
    public OctObserver(Subject subject) {
        this.subject = subject;
        this.subject.add( this );
    }

    public void update() {
        System.out.print(" " + Integer.toOctalString(subject.getState()));
    }
}

class BinObserver extends Observer {
    public BinObserver(Subject subject) {
        this.subject = subject;
        this.subject.add(this);
    }

    public void update() {
        System.out.print(" " + Integer.toBinaryString(subject.getState()));
    }
}

public class ObserverDemo {
    public static void main( String[] args ) {
        Subject sub = new Subject();
        // Client configures the number and type of Observers
        new HexObserver(sub);
        new OctObserver(sub);
        new BinObserver(sub);
        Scanner scan = new Scanner(System.in);
        for (int i = 0; i < 5; i++) {
            System.out.print("\nEnter a number: ");
            sub.setState(scan.nextInt());
        }
    }
}

2.2 松耦合形式

以下代码基于jdk1.8以上,定义内部类,可以避免在Observer中定义Subject的引用。

EventSource事件源, scanSystemIn()方法监听控制台输入,当控制台有字符串输入时,EventSource会通知Observer进行update操作。

/**
 * 事件源
 */
class EventSource {
    public interface Observer {
        void update(String event);
    }

    private final List<Observer> observers = new ArrayList<>();

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void scanSystemIn() {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            notifyObservers(line);
        }
    }

    private void notifyObservers(String event) {
        observers.forEach(observer -> observer.update(event));
    }
}

调用

public class ObserverDemo {
    public static void main(String[] args) {
        EventSource eventSource = new EventSource();
        for (int i = 0; i < 3; i++) {
            eventSource.addObserver(event -> {
                System.out.println("Received response: " + event);
            });
        }
        eventSource.scanSystemIn();
    }
}

控制台输入"click",然后事件源监听到,通知Observer,Observer将消息打印出来:

click
Received response: click
Received response: click
Received response: click

3.总结

其实为了避免紧耦合,实际开发中往往在Subject和Observer之间加一个中间对象,这个中间对像本质上就是一个队列,当Subject状态发生变化时,这个变化信息传到中间对象队列中,由中间对象对这条消息进行处理,包装成Observer统一的一种接收形式,然后根据某种策略发给Observer。消息中间件就是这样一种实现,

另外,注意主题对象由于持有观察着对象集合,如果观察者数量庞大时,要注意内存的占用,避免发生内存泄漏,通常,可以考虑弱引用weak reference来避免内存占用过高。

猜你喜欢

转载自www.cnblogs.com/yxlaisj/p/10539000.html