引言
控制流反转1指将通常的控制流从调用者代码反转到被调用者代码,以便更好的分离关注点和松耦合。观察者模式是这一准则的主要实现形式之一。
文章为了更好的讲解,下面介绍一个设计场景。允许用户以不同的格式选择和显示数字的小程序,即幸运数小程序。
动机
大量有状态的对象需要保持一致。例如幸运数小程序,其支持用户选取一个1~10的幸运数,用户可以用不同的方法选取幸运数,如输入数字的数码、输入数字的名称和用滑动条选取数字。
要求1:每个方法的修改要体现在其他方法上
要求2:能扩展其他的方法,如输入罗马数字和输入二进制数字
新手一般会使用两两依赖的方式,如类图所示,此设计至少有两条制约。
制约1:紧耦合
制约2:低可扩展性,增删一个方法会修改其他所有方法
n个方法的同步复杂度是n*(n-1)。
MVC分解
避免两两依赖而实现数据同步的方法是将抽象分离为保存数据、显示数据、修改数据。MVC的解决方案模板不计其数,例如视图和控制器可以融为一体。MVC的不明确性使它在软件设计中不易掌握,而有一种更为具体的思想——观察者模式。
观察者模式
模型和观察者间的控制流
观察者不是通过调用模型的方法来了解模型的信息,而是等待模型反向调用它们,这种想法称为“好莱坞原则”。观察者上定义的方法称为“回调”。
public interface Observer{
void newNumber(int pNumber); //模型有个新的值,是这个数
}
public class Model{
private int aNumber = 5;
private ArrayList<Observer> aObservers = new ArrayList<>();
public void addObserver(Observer pObserver){
aObservers.add(pObserver);
}
public void removeObserver(Observer pObserver){
aObservers.remove(pObserver);
}
private void notifyObservers(){
for(Observer observer:aObservers){
observer.newNumber(aNumber);
}
}
public void setNumber(int pNumber){
if(pNumber<=0){aNumber=1;}
else if(pNumber>10){aNumber=10;}
else{aNumber=pNumber;}
notifyObservers();
}
}
模型和观察者间的数据流
策略1:通过回调方法的参数来提供,称为推(push)数据流策略,模型将数据推给观察者
策略2:观察者拉取模型上的信息,称为拉(pull)数据流策略
事件驱动
另一种看待回调方法的思路是将其看作事件,模型是事件源,观察者是事件处理程序。
参考Martin P.Robillard《Introduction to Software Design with Java》 ↩︎