JAVA设计模式详解(一)——观察者模式

一、概述
  观察者模式又叫发布(publish )-订阅(Subscribe)模式、模型-视图(View)模式、源-收听者(Listener)模式或从属者模式。观察者模式定义了一种一对多的依赖关系,多个观察者对象监听同一主题的对象,当主题对象发生改变时就会通知所有的观察者对象来处理主题的改变。
  例如水库的水位监测,当水位发生改变时,要完成如下几个功能:记录水位、显示水位变化曲线、超水位报警。按观察者模式来分析的话,主题对象就是水位,观察者这儿有三个:记录水位观察者、水位变化曲线观察者、水位报警观察者。按我们常规的思维我们应该怎样去实现呢?学过java语言的同学很快就会想到,这不简单,将所有的功能函数放入while循环中,就可以监听水位。
  

while(true){
    记录水位;
    显示水位变化曲线;
    超水位报警; 
}

  如果你没有学过设计模式,将功能集合在同一类或者一函数中这种做法无可厚非,但是以后千万不要这么做了。第一,这种方式叫做死循环,基本上一个线程就处理这一个循环了,有人所,用多线程单独处理这一个循环,这样做是可以稍微改善性能,但是你觉得是水位的变化速度快还是计算机的处理速度快,有可能CPU处理上万次都是处理的同一水位,你觉的有必要将同一的数据反复处理成千上次吗?第二,假如我要修改软件的需求,删除或者增加某一监测功能时,程序就得全局修改,代码的高度耦合的代价就是后期维护的工作量将要几倍甚至十几倍的增加。
  由此可以初步得到观察者模式要解决的问题:
  1.怎眼以最小代价来获取主题对象(水位)的改变?
  2.解决代码的耦合问题,便于后期需求修改?
  
二、观察者模式设计思路
 1.怎眼以最小代价来获取主题对象(水位)的改变?
 while循环首要不足之处就是就算(主题)水位没有发生改变,也会不断地处理循环中的功能模块,这将大大牺牲软件的性能。
 我们是不是可以设置一个标记位来显示主题对象的改变状态。假设主题发生改变时,标记位位true,当主题将改变通知了观察者之后标记位位false,这样不就完美解决该问题。
 2.解决代码的耦合问题,便于后期需求修改?
 如果我们将系统中每一个类的具体功能放在某一具体功能上,一个类只做一件事,这些类就是观察者,这样不就解决了耦合问题。
 由上可知:
 1.观察者模式必有一个主题和多个观察者。
 2.主题要能准确的通知到每一观察者,主题就必须要对观察者进行注册(得到观察者的变量集合)、反注册(删除观察者的变量集合)、通知观察者对象;
 3.既然有观察者的变量集合,观察者就必须是多态的,有共同的父接口;

三、观察者模式
  编写观察者模式要完成以下编写:
  主题接口ISubject编写
  主题类Subject的编写
  观察者接口IObserver编写

  废话少说上demo。
  
1.主题接口ISubj。

public interface ISubject {
    /**
     * 注册观察者对象
     * @param obs
     */
    void register(IObserver obs);
    /**
     * 删除观察者对象
     * @param obs
     */
    void unRegister(IObserver obs);
    /**
     * 通知所有已注册的观察者
     */
    void notifyObserver();
}

2.观察者接口。

public interface IObserver {
    /**
     * 刷新数据
     * @param data
     */
    void refresh(String data);
}

3.主题实现类Subject。

public class Subject implements ISubject{
    //观察者变量集合
    private Vector<IObserver> vec = new Vector<>();
    //主题数据内容
    private String data;
    //标记数据是否改变
    private boolean changed = false;

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
        setChanged();
        notifyObserver();//数据改变之后通知
    }

    @Override
    public void register(IObserver obs) {
         if (obs == null)
                throw new NullPointerException();
            if (!vec.contains(obs)) {
                vec.addElement(obs);
            }
    }

    @Override
    public void unRegister(IObserver obs) {
        vec.removeAllElements();
    }

    @Override
    public void notifyObserver() {
        if(! hasChanged()){
            return;
        }
        Object[] array = vec.toArray();
        for(int i = 0; i < array.length; i++){
            ((IObserver)array[i]).refresh(getData());
        }
        clearChanged();
    }
    protected synchronized void setChanged() {
        changed = true;
    }

    protected synchronized void clearChanged() {
        changed = false;
    }

    public synchronized boolean hasChanged() {
        return changed;
    }

}

4.观察者实现类Observer。

public class Observer implements IObserver{

    @Override
    public void refresh(String data) {
        System.out.println(data);
    }
}

4.测试类。

public class Test {
    public static void main(String[] args) {
        Subject subject = new Subject();//主题对象
        IObserver observer1 = new Observer();//第一个观察者
        IObserver observer2 = new Observer();//第二个观察者
        subject.register(observer1);//注册
        subject.register(observer2);//注册
        subject.setData("Hello Observer");//数据改变
    }
}

5.运行结果。

Hello Observer
Hello Observer

  其实,整个demo就一个意思,当主题的数据发生改变时,主题类会调用notifyObserver()通知所有的观察者接受收数据。
  细心的同学会发现,其实整个demo的逻辑并不严谨,缺少添加或删除多个观察者的功能、主题数据单一、多线程的安全问题等等不足之处,在这里我只是提供了观察者设计模式的基本思路,对部分不足后面有补充,慢慢来,一口吃不出一个胖子。

三、深入理解观察者设计模式

1.解决上面demo的一个不足之处:主题数据单一。
  String数据根本满足不了我,假如我要double、float、List……难道让我一个一个的创建接口与相应的实现类?No!No!如果是这样那还学上面设计模式,所谓设计模式就是能反复使用的模板,假如所用这种方式就是折磨人的“磨板”了。
  其实我们可以将ISubject和IObserver改为泛型接口就可以了。这不失为一个解决方法。
  

/**
 *主题泛型接口
 */
public interface ISubject<T> {
    /**
     * 注册观察者对象
     * @param obs
     */
    void register(IObserver<T> obs);
    /**
     * 删除观察者对象
     * @param obs
     */
    void unRegister(IObserver<T> obs);
    /**
     * 通知所有已注册的观察者
     */
    void notifyObserver();
}
/**
 * 观察者泛型接口
 */
public interface IObserver<T> {
    /**
     * 刷新数据
     * @param data
     */
    void refresh(T data);
}

2.数据获取方式:推数据与拉数据
推数据:主题直接将主题数据推送给观察者;
拉数据:主题直接将“自己”推送给观察者,然后观察者能通过主题对象获取主题数据,一般建议采用拉数据的方式;

void refresh(String data);//推数据
void refresh(ISubj subjet);//拉数据

3.增加抽象层来解决重复问题。
每个主题都要写register、UnRegister、notifyObserver、标志位状态改变,为什么不讲它们设计为抽象类呢?

//抽象主题类
public abstract class AbstractSubject<T> implements ISubject<T>{
    //观察者变量集合
    private Vector<IObserver<T>> vec = new Vector<>();
    //标记数据是否改变
    private boolean changed = false;

    @Override
    public void register(IObserver<T> obs) {
         if (obs == null)
                throw new NullPointerException();
            if (!vec.contains(obs)) {
                vec.addElement(obs);
            }
    }

    @Override
    public void unRegister(IObserver<T> obs) {
        vec.removeAllElements();
    }

    @Override
    public void notifyObserver() {
        if(!hasChanged()){
            return;
        }
        Object[] array = vec.toArray();
        for(int i = 0; i < array.length; i++){
            ((IObserver<T>)array[i]).refresh(this);
        }
        clearChanged();
    }

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

    protected synchronized void clearChanged() {
        changed = false;
    }

    public synchronized boolean hasChanged() {
        return changed;
    }
}
//主题子类
public class Subject<T> extends AbstractSubject<T>{
    //主题数据内容
    private T data;
    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
        setChanged();
        notifyObserver();
    }

}

  怎样,这样主题子类是不是看着很清爽呢!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值