学习任何设计模式前,建议先了解一波设计模式的六大设计原则。在学习了某一设计模式的用法后,再从6大原则出发去分析如此设计的好处,可以尽可能做到“知其然并知其所以然”。
Java系列的23种设计模式,都离不开6大设计原则的指导,各个模式只是使用的场景不同。核心目标都是:在满足业务需求的前提下,保证模块间的低耦合,以及代码的可维护,高扩展。
单一职责原则:类和接口(甚至包括方法)里面只做一类事情,即只能有一个原因能引起类的变化。反着想,如果一个接口定义的方法不仅仅做一类事情,一旦接口发生变化,会导致更多的实现类发生变化。
依赖倒置原则:模块间依赖关系由抽象产生,实现类之间不发生直接依赖关系。通俗来讲,实现类A与实现类B为了保持依赖关系(所谓依赖,类A因持有类B的引用,便可以调用类B的方法),不可以直接持有实现类,而是持有对方的抽象(接口,抽象类)。反着想,如果类A持有类B的引用,一旦A需要持有类B1,B2(B1,B2和类B一样都继承自一个抽象)。那么就需要在类中定义类B1,B2的引用,这样修改了类A的代码,不符合“总方针”开闭原则(后面会讲到)。
接口隔离原则:模块之间的依赖关系,应该建立最小接口上。依赖倒置原则定义:模块间的依赖通过抽象产生。接口隔离原则:用于指导接口里方法的粒度粗细。一个接口里方法过多,涉及不同的业务场景,那么将一个接口可以分为几个接口。这样接口的粒度更细,需要哪些方法就实现哪个接口,需要所有方法就多实现所有接口。
迪米特法则:模块间的依赖关系,应该以成员变量或方法输入参数形式产生,强调的是如何注入依赖。
里氏替换原则:基类出现的地方,都可以用子类替换,并且不会出现任何异常和错误。
开闭原则:对扩展开放,对修改关闭。在业务需求增加的情况下,应该做到扩展新模块,但不修改原模块。开闭原则是前面五种设计原则的总方针,也就是说,其他设计原则目的就是为了对扩展开放,对修改关闭。
网上包括书上,有关观察者模式的定义很多,这里就不再重复那些权威的语言了(ps:挺讨厌那些生涩难懂的术语,权威二字默默体会啦~)。观察者模式里有两个角色:被观察者Observable,观察者Observer。设计步骤如下:
代码如下:
//定义观察者
public abstract class AbstractObserver {
public abstract void update(String data);
}
//定义被观察者
public abstract class AbstractObservable {
//定义数据结构,存储观察者们
private ArrayList observers = new ArrayList();
//注册观察者
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 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