这篇文章着重介绍的是观察者模式,很多使用场景纯属个人在撸码过程中一些习惯性的处理方式,当然也是感觉使用起来比较顺手和美观的一些方式,此外,观察者模式绝对是一个万金油(如果不懂设计模式,想解耦,一个观察者模式就够了----如果没有更高的追求的话,很多解耦的问题都可以通过观察者模式得到很好的解决)。各种设计模式的使用场景上总是仁者见仁智者见智,熟读设计模式,总会有一款你所喜欢的。
1.背景介绍
场景设定:善变的业务方总是喜欢改变产品信息,一会要改变产品名称,一会要改变产品的安全的等级,一会又要更改产品的上线时间,等等,一些列类似的需求,功能的需求还不是一次性提的,最开始约定的是产品发布后重要的属性是不允许更改的。那就只能加功能了,加逻辑判断。改个产品名称好说,改产品安全等级一类的属性就要发短信告知用户,还要修改可购买该产品的用户群组。慢慢的就成了产品上线后所有的属性都可以改了,逻辑判断也加的也来越多,代码也越来越复杂。
针对上述的问题就涉及到了“解耦”,怎么解耦,就是不要把不同业务场景的代码堆砌到一大堆逻辑里,要剥离共性,封装变化,增强系统的可维护性和扩展性。最简的描述可能就是要减少if...else的判断了业务场景:当代码中只有一处业务场景的if...else判断时,那你是幸运的,当有新的场景要添加或有一种场景要做修改时,你只需要在一处增加或者修改,但是如果存在多处业务场景的if...else的判断时,那将悲剧了,要翻代码一处一处的改了,当换了人员维护代码时,面对这么一坨代码,眼花缭乱,要通过findAll来查找哪里有业务场景的判断,然后再一处一处的改
2.解决方案
- 命令模式:封装请求为对象
- 策略模式:封装可以互换的行为,并且使用委托来决定要使用哪个
- 工厂模式:由子类决定要创建的具体类是哪个
命令模式:可以将每个产品属性的变更作为一个命令关键字,为其实现相应的命令执行逻辑,注册到控制器中,控制器维护一个命令集合,当有产品的属性变更请求过来时,控制器取出对应的Command的实现类,执行相应的逻辑。当有新需求来时,只要实现新的Command,添加到控制器。
策略模式:在这个简单的场景里,策略模式和命令模式的区分度不是很大,可以简单的理解为命令模式整个的属性的变更是一个整体,而在策略模式下,变更是一个对象(或者是多个对象,实现给定执行的Action),根据传入的参数,动态设置Action,这里封装了每一个最终的执行行为,当然这里就要维护一个算法簇(actions)。此场景下使用策略模式虽然也达到了解耦(新需求来的时候,变更方式很固定),但是执行算法Action的对象模型过于简单,有点大材小用,明显命令模式逻辑更清晰。
工厂模式:此场景中,工厂作为一个生产者,负责提供对外提供一个能够处理所需逻辑的处理器,这样的话处理逻辑的选取都有工厂来负责,传入相应的变更字段,工厂提供逻辑。
3.使用观察者模式解决问题
通过对上述三种设计模式的描述,有一个共同点:每个字段变更的处理逻辑进行单独封装后,注册到一个统一的地方,由一个统一的地方进行选择(我们咱叫它为控制器)。只是每种设计模式下的注册机制不一样,处理逻辑担当的角色也不一样,有的是主动注册,有的是被动注册,有的控制器是最终的执行者,有的控制器只是起到了一个路由的功能。
我们再结合一下场景说以下:不管使用了那种模式,场景最终要实现的是我有个属性要变更的请求过来了,我要找到具体的执行逻辑的handler来处理这个请求,所以我要从一个控制器里去找,找到的话,我就让这个handler来处理。要是以后有新的需求添加进来,就在控制器里注册新的handler。换种描述方法就成了作为控制器对外提供服务,当收到请求时,就要通知相应的handler来处理这逻辑,这个handler要提前注册,或者说要提前订阅控制器的服务,控制器收到消息才能通知到该handler,这已经很接近观察者模式的定义了。我们把产品的属性变更作为一个主题,各handler作为观察者,订阅他们感兴趣的主题变更。
下面我们来看一下代码实现。
我们定义了下面几个类
- ProductObservable:产品变更被订阅的主题
- ProductChangeObserver:一个观察者的抽象类
- ProductNameChange:产品名称变更的观察者
- RiskLevelChange:安全等级变更的观察者
- ObserverTest:测试类
/**
* 产品主题
* Created by ipolaris on 2017/1/9.
*/
public class ProductObservable {
/**
* 观察者集合,观察者订阅关心的产品属性变更的消息
*/
private Map<String, List<ProductChangeObserver>> observersMap =
new HashMap<String, List<ProductChangeObserver>>();
/**
* 属性变更
* @param propertyName
* @param newValue
*/
public void propertyChange(String propertyName, String newValue){
notifyObservers(propertyName, newValue);
}
/**
* 通知观察者
* @param objectType
* @param arg
*/
private void notifyObservers(String objectType, String arg) {
List<ProductChangeObserver> observers =
observersMap.get(objectType);
if (null != observers && !observers.isEmpty()){
for (ProductChangeObserver observer : observers){
observer.update(this, arg);
}
}
}
/**
* 添加监听者
* @param observer 监听者
*/
public synchronized void addObserver(
ProductChangeObserver observer){
List<ProductChangeObserver> observers =
observersMap.get(observer.propertyName());
if (null == observers){
observers = new ArrayList<ProductChangeObserver>();
observersMap.put(observer.propertyName(), observers);
}
if (!observers.contains(observer)){
observers.add(observer);
}
}
}
public abstract class ProductChangeObserver {
/**
* 属性名
* @return
*/
abstract String propertyName();
/**
* 处理逻辑
* @param newValue 新值
*/
abstract void process(String newValue);
/**
* 观察者接收消息,执行变更
* @param o
* @param arg
*/
public void update(ProductObservable o, String arg) {
process(arg);
}
}
public class ProductNameChange extends ProductChangeObserver {
public ProductNameChange(ProductObservable observable){
observable.addObserver(this);
}
@Override
public String propertyName() {
return "productName";
}
@Override
public void process(String newValue) {
System.out.println("new product name is " + newValue);
}
}
public class RiskLevelChange extends ProductChangeObserver {
public RiskLevelChange(ProductObservable observable){
observable.addObserver(this);
}
@Override
public String propertyName() {
return "riskLevel";
}
@Override
public void process(String newValue) {
System.out.println("new risk level is " + newValue);
}
}
public class ObserverTest {
private ProductObservable productObservable =
new ProductObservable();
@Before
public void init(){
new ProductNameChange(productObservable);
new RiskLevelChange(productObservable);
}
@Test
public void changeTest(){
productObservable.propertyChange("riskLevel","test");
System.out.println("-------------------------------");
productObservable.propertyChange("productName", "ipolaris");
}
}
当我们跑单元测试 changeTest 执行的结果如下,符合预期
结果中我们可以发现当产品有字段变更时,只有相应的观察者执行了逻辑。在上述代码中,订阅的主题关注了观察者的好恶,也就是说产品变更是一个主题,但是主题下还有各种子类别的主题,观察者在订阅的时候其实是要告诉被订阅的主题自己所关注的的子类别是什么,然后主题会通知到观察者相应的子类别的变更。这里可以也可以换一种模式,被观察的主题只需要对外通知自己的变更,至于是不是观察者所关注的变更,由观察者自行判断处理。这样被订阅的主题将会有更少的逻辑,观察者的处理也会更自由。
观察者模式不见得在各场景下最优的选择方案,但却是在解耦方面错不了的一种解决方案,是一把王能钥匙。在异步处理的情形下,基于这种订阅消费的观察者模式就更常见了,比如MQ。
做个预告,下面会推出一个有限状态机的实现的专题