Java系列之观察者模式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/pihailailou/article/details/82557174

一,写在前面

    学习任何设计模式前,建议先了解一波设计模式的六大设计原则。在学习了某一设计模式的用法后,再从6大原则出发去分析如此设计的好处,可以尽可能做到“知其然并知其所以然”。

    Java系列的23种设计模式,都离不开6大设计原则的指导,各个模式只是使用的场景不同。核心目标都是:在满足业务需求的前提下,保证模块间的低耦合,以及代码的可维护,高扩展。


二,设计6大原则

单一职责原则:类和接口(甚至包括方法)里面只做一类事情,即只能有一个原因能引起类的变化。反着想,如果一个接口定义的方法不仅仅做一类事情,一旦接口发生变化,会导致更多的实现类发生变化。

依赖倒置原则:模块间依赖关系由抽象产生,实现类之间不发生直接依赖关系。通俗来讲,实现类A与实现类B为了保持依赖关系(所谓依赖,类A因持有类B的引用,便可以调用类B的方法),不可以直接持有实现类,而是持有对方的抽象(接口,抽象类)。反着想,如果类A持有类B的引用,一旦A需要持有类B1,B2(B1,B2和类B一样都继承自一个抽象)。那么就需要在类中定义类B1,B2的引用,这样修改了类A的代码,不符合“总方针”开闭原则(后面会讲到)。

接口隔离原则:模块之间的依赖关系,应该建立最小接口上。依赖倒置原则定义:模块间的依赖通过抽象产生。接口隔离原则:用于指导接口里方法的粒度粗细。一个接口里方法过多,涉及不同的业务场景,那么将一个接口可以分为几个接口。这样接口的粒度更细,需要哪些方法就实现哪个接口,需要所有方法就多实现所有接口。

迪米特法则:模块间的依赖关系,应该以成员变量或方法输入参数形式产生,强调的是如何注入依赖。

里氏替换原则:基类出现的地方,都可以用子类替换,并且不会出现任何异常和错误。

开闭原则:对扩展开放,对修改关闭。在业务需求增加的情况下,应该做到扩展新模块,但不修改原模块。开闭原则是前面五种设计原则的总方针,也就是说,其他设计原则目的就是为了对扩展开放,对修改关闭。


三,主角“观察者模式”

网上包括书上,有关观察者模式的定义很多,这里就不再重复那些权威的语言了(ps:挺讨厌那些生涩难懂的术语,权威二字默默体会啦~)。观察者模式里有两个角色:被观察者Observable,观察者Observer。设计步骤如下:

  1. 被观察者里一般会定义一个集合,用于存放观察者的引用,这样被观察者和观察者之间就产生了依赖关系。
  2. 需要定义观察者的抽象类,让被观察者持有观察者的抽象引用。因为依赖倒置原则指导咱们:模块间的依赖应该通过抽象产生。
  3. 那么被观察者类里存放一些什么方法呢?上面讲到用集合存储观察者,涉及到数据的处理自然是添加/删除观察者,也叫注册/注销。
  4. 同时,被观察者需要提供一个notifyObservers方法,通知观察者并传递一些数据过去。

代码如下

//定义观察者
public abstract class AbstractObserver {

	public abstract void update(String data);
}

//定义被观察者
public abstract class AbstractObservable {
	
	//定义数据结构,存储观察者们
	private ArrayList<AbstractObserver> observers = new ArrayList<AbstractObserver>();
	
	//注册观察者
	public void registerObserver(AbstractObserver o) {
		if (o != null) {
			observers.add(o);
		}
	}
	
	//注销观察者
	public void unRegisterObserver(AbstractObserver o) {
		if (o != null && observers.contains(o)) {
			observers.remove(o);
		}
	}
	
	//通知观察者们
	public void notifyObservers() {
		if (observers.size() <= 0)
			return;
		
		Iterator<AbstractObserver> it = observers.iterator();
		while (it.hasNext()) {
			AbstractObserver observer = it.next();
                        //调用观察者的update方法,通知观察者并传递一些数据
			observer.update("send message from Observable");
		}
	}

        //触发通知的方法
	public void start() {
		doSomething();
		
		notifyObservers();
	}

	//提供抽象方法供子类扩展
	public abstract void doSomething();
	
}

需要注意的是,若需扩展为多个被观察者,就需要定义一个被观察者的抽象类AbstractObservable。本篇文章会以“一个被观察者,多个观察者”为例进行介绍。该抽象的地方还是得抽象,万一业务需求增加,需要多个被观察者呢。

registerObserver:      注册观察者,将观察者添加到集合;

unRegisterObserver:注销观察者,将观察者从集合中移除;

notifyObservers:       通知所有的观察者,并调用观察者的update方法;

start:                           由高层模块调用;

doSomething:            若有多个被观察者,供其子类自由扩展;

实现类,代码如下

//观察者
public class ObserverImpl1 extends AbstractObserver {

	@Override
	public void update(String data) {
		System.out.println("ObserverImpl1 : " + data);
		
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

//another java file ,观察者
public class ObserverImpl2 extends AbstractObserver{

	@Override
	public void update(String data) {
		System.out.println("ObserverImpl2 : " + data);
	}
}

//another java file , 被观察者
public class ObservableImpl extends AbstractObservable{

	@Override
	public void doSomething() {
		System.out.println("do something ...");
	}
}

在类ObserverImpl1中,调用Thread$sleep方法模拟了一个耗时操作。

高层模块Test类,代码如下

public class Test {

	public static void main(String[] args) {
		//创建一个被观察者
		AbstractObservable observable = new ObservableImpl();
		
		//创建两个观察者
		AbstractObserver observer1 = new ObserverImpl1();
		AbstractObserver observer2 = new ObserverImpl2();
		
		//注册两个观察者
		observable.registerObserver(observer1);
		observable.registerObserver(observer2);
		
		//调用触发方法start
		observable.start();
	}
}

打印结果
do something ...
ObserverImpl1 : send message from Observable

//等待大约3秒左右,继续打印log
ObserverImpl2 : send message from Observable

现象分析:观察者ObserverImpl1,ObserverImpl2都收到通知,并获取了被观察者传递的数据“send message from Observable”。但是,观察者ObserverImpl2是过了大约3秒收到的通知,也就说前面一个观察者卡住,后面的观察者都无法接受到通知。此时,可以在类AbstractObservable的第36行调用update方法时,开启多线程异步处理,一个观察者对应一个线程。


四,使用场景及优缺点

常见使用场景:

Android中的OnClickListener,ContentObserver,EventBus等...

优点

观察者和被观察者易扩展,依赖抽象模块间耦合较低;

建立了一套触发机制;

缺点

开发效率较低:一个被观察者,多个观察者时,开发和调试的过程比较复杂,并且需要异步处理观察者卡住的问题

最后,本篇文章到此结束啦~

                                                                                                    O(∩_∩)O

猜你喜欢

转载自blog.csdn.net/pihailailou/article/details/82557174