您的位置:首页 > 编程语言

设计模式讲解与代码实践(二十)——观察者

2017-10-30 16:56 711 查看
本文来自李明子csdn博客(http://blog.csdn.net/free1985),商业转载请联系博主获得授权,非商业转载请注明出处!

1 目的

观察者(Observer)模式用于描述这样一种依赖关系:当一个对象的状态发生改变时,与它相关的对象能够获得这个对象已经改变的通知。

观察者模式在使用时十分灵活,变种众多。目标与观察者间的关系既可以是一对一也可以是一对多甚至可以是多对多。观察者对目标的状态可以是只读的也可以是读写的,一切都随实际应用场景而定。

对于观察者模式最常见和易于理解的应用莫过于MVC。我们可以将其具象化为一组统计结果的UI展示。统计结果是唯一的,而UI可能提供了饼图、柱状图及表格等多种展示形式。当数据被修改时(可能通过计算操作,也可能直接在某个图表中修改值)各图表均将刷新以保持与统计结果的一致。

观察者模式的另一个被我们所熟知的使用场景与系统间协同有关。当某系统中的数据改变时,可能需要通过API或webservice等接口通知与其相关的第三方系统。

2 基本形态

观察者的基本形态如类图2-1所示。



图2-1 观察者类图

观察者¬各参与者的交互如图2-2所示。



图2-2 观察者交互图

从上面的交互图可以看出,有时观察者对目标并不一定是“只读”的,即观察者可能直修改目标,而目标修改的消息将被通知到其他所有观察者。

3 参与者

结合图2-1,下面介绍各类在观察者设计模式中扮演的角色。

3.1 Subject

Subject是目标接口,实现了对观察者的绑定方法(Attach)和解绑方法(Detach)及自身改变时对各绑定观察者的通知方法(Notify)。Subject内部维护了绑定的观察者对象集合。

3.2 ConcreteSubject

ConcreteSubject是具体目标,实现了目标接口Subject。ConcreteSubject声明了对内部状态的读写方法(GetState和SetState)。

3.3 Observer

Observer是观察者接口,声明了被观察目标改变时的通知接口方法Update。

3.4 ConcreteObserver

ConcreteObserver是具体观察者,实现了观察者接口Observer。如果一个观察者需要观察多个目标甚至观察不同类型的目标,ConcreteObserver需要在实现Update接口方法时对目标对象加以区分。

4 代码实践

下面我们用一个业务场景实例来进一步讲解观察者的使用。

4.1 场景介绍

某电商平台包含存在质量问题的产品清单、产品管理、广告(电视广告、平面广告)管理等多个模块。当有质量问题的产品加入问题清单时,平台需要发布产品召回通知并撤下该产品的所有广告。当存在质量问题的产品过检后需要恢复其广告的投放。

以下各节将介绍该场景各类的具体实现及其在观察者设计模式中所对应的参与者角色。

4.2 AbstractListSubject

AbstractListSubject是清单目标抽象类。对应于观察者模式的参与者,AbstractListSubject是目标接口Subject。下面的代码给出了AbstractListSubject的声明。

package demo.designpattern.observer;

import java.util.ArrayList;
import java.util.List;

/**
* 清单目标抽象类
* Created by LiMingzi on 2017/10/28.
*/
public abstract class AbstractListSubject {
/**
* 清单观察者集合
*/
private List<IListObserver> listObservers = new ArrayList<IListObserver>();

/**
* 绑定观察者
* @param listObserver 清单观察者
*/
public void attach(IListObserver listObserver){
listObservers.add(listObserver);
}

/**
* 解绑观察者
* @param listObserver 清单观察者
*/
public void detach(IListObserver listObserver){
listObservers.remove(listObserver);
}

/**
* 通知观察者
*/
void notifyObservers(){
for (IListObserver listObserver : listObservers) {
listObserver.listHasChanged(this);
}
}
}


上述代码中,14行,成员变量listObservers维护了清单观察者集合;20行声明并实现了绑定观察者方法attach;28行声明并实现了解绑观察者方法detach;35行声明并实现了通知观察者方法notifyObservers,遍历其绑定的各观察者并调用观察者的listHasChanged方法。

4.3 DefectedGoodsList

DefectedGoodsList是问题产品清单类,实现了问题产品清单的管理。对应于观察者模式的参与者,DefectedGoodsList是具体目标ConcreteSubject。下面的代码给出了DefectedGoodsList的声明。

package demo.designpattern.observer;

import java.util.ArrayList;
import java.util.List;

/**
* 问题产品清单
* Created by LiMingzi on 2017/10/28.
*/
public class DefectedGoodsList extends AbstractListSubject {
/**
* 商品集合
*/
private List<String> goodsList = new ArrayList<String>();

/**
* 添加商品
* @param goods 商品
*/
public void addGoods(String goods){
goodsList.add(goods);
notifyObservers();
}

/**
* 删除商品
* @param goods 商品
*/
public void removeGoods(String goods){
goodsList.remove(goods);
notifyObservers();
}

/**
* 获取商品清单
* @return 商品清单
*/
public List<String> getGoodsList() {
return goodsList;
}
}


上述代码中,14行声明了成员变量goodsList用来表示问题产品集合;20行声明了向名单添加问题商品方法addGoods;29行声明了从名单删除商品方法removeGoods;38行声明了获取商品清单方法getGoodsList。

4.4 IListObserver

IListObserver是清单观察者接口。对应于观察者模式的参与者,IListObserver是观察者接口Observer。下面的代码给出了IListObserver的声明。

package demo.designpattern.observer;

/**
* 清单观察者接口
* Created by LiMingzi on 2017/10/28.
*/
public interface IListObserver {
/**
* 清单改变
* @param listSubject 清单
*/
public void listHasChanged(AbstractListSubject listSubject);
}


上述代码中,12行,声明了清单改变方法listHasChanged。该方法包含AbstractListSubject类型的参数listSubject以便观察者知晓是哪个被观察清单发生了改变。

4.5 GoodsMgmt

GoodsMgmt是商品管理类,实现了对存在质量问题的产品的召回。对应于观察者模式的参与者,GoodsMgmt是具体观察者ConcreteObserver。下面的代码给出了GoodsMgmt的声明。

package demo.designpattern.observer;

import java.util.ArrayList;
import java.util.List;

/**
* 商品管理类
* Created by LiMingzi on 2017/10/28.
*/
public class GoodsMgmt implements IListObserver{
/**
* 已召回商品集合
*/
private List<String> recalledGoodsList = new ArrayList<String>();
/**
* 清单改变
*
* @param listSubject 清单
*/
@Override
public void listHasChanged(AbstractListSubject listSubject) {
// 仅处理问题产品清单的改变
if(listSubject instanceof DefectedGoodsList){
recall(((DefectedGoodsList)listSubject).getGoodsList());
}
}

/**
* 召回商品
* @param goodsList 商品集合
*/
private void recall(List<String> goodsList){
for (String goods : goodsList) {
if(!recalledGoodsList.contains(goods)){
System.out.println("发布商品“"+goods+"”召回通知");
recalledGoodsList.add(goods);
}
}
}
}


上述代码中,14行,成员变量recalledGoodsList维护了已召回的商品集合;21行,实现了清单改变接口方法listHasChanged;23行,当改变的目标是存在质量问题的清单时,发布召回通知召回清单中的商品;32行,召回商品方法recall为未发布过召回商品通知的问题商品发布召回通知(在实际应用中,该召回通知是周期性发布的)。

4.6 ADMgmt

ADMgmt是广告管理类,实现了对商品广告的管理。对应于观察者模式的参与者,ADMgmt是具体观察者ConcreteObserver。下面的代码给出了ADMgmt的声明。

package demo.designpattern.observer;

import java.util.ArrayList;
import java.util.List;

/**
* 广告管理类
* Created by LiMingzi on 2017/10/28.
*/
public class ADMgmt implements IListObserver{
/**
* 广告类型
*/
private String adType;
/**
* 广告清单
*/
private List<String> ads = new ArrayList<String>();
/**
* 广告黑名单
*/
private List<String> blacklist = new ArrayList<String>();
/**
* 添加广告
* @param goods 商品名称
*/
public void addAd(String goods){
ads.add(goods);
}

/**
* 构造方法
* @param adType 广告类型
*/
public ADMgmt(String adType) {
this.adType = adType;
}

/**
* 清单改变
*
* @param listSubject 清单
*/
@Override
public void listHasChanged(AbstractListSubject listSubject) {
// 仅处理问题产品清单的改变
if(listSubject instanceof DefectedGoodsList){
// 同步广告黑名单
blacklist = new ArrayList<String>(((DefectedGoodsList)listSubject).getGoodsList());
}
}

/**
* 播放广告
*/
public void showAds(){
System.out.println("播放"+adType+"广告:");
for (String ad : ads) {
if(!blacklist.contains(ad)){
System.out.println(ad+adType+"广告");
}
}
}
}


上述代码中,14行,声明了表示广告类型的成员变量adType,该变量在构造方法中初始化;18行声明了表示广告清单的成员变量ads;22行声明了表示广告黑名单的成员变量blacklist;27行声明了添加广告方法addAd;45实现了清单改变方法listHasChanged,当改变的目标是存在质量问题的清单时,用问题产品清单同步广告黑名单;56行,播放广告方法showAds播放广告清单中不在黑名单中的广告。

4.7 测试代码

为了测试本文中的代码,我们可以编写如下测试代码。测试代码中我们创建被观察目标问题产品清单对象,分别将商品管理对象、电视广告管理对象、平面广告管理对象作为观察者绑定到问题产品清单对象上。之后通过向问题产品清单中加入、减少商品及对观察者的解绑来观察目标对观察者的影响。

/**
* 观察者测试
*/
public static void observerTest() {
// 问题产品清单
DefectedGoodsList defectedGoodsList = new DefectedGoodsList();
// 商品管理对象
GoodsMgmt goodsMgmt = new GoodsMgmt();
defectedGoodsList.attach(goodsMgmt);
// 电视广告管理对象
ADMgmt tvADMgmt = new ADMgmt("电视");
defectedGoodsList.attach(tvADMgmt);
// 平面广告管理对象
ADMgmt printADMgmt = new ADMgmt("平面");
defectedGoodsList.attach(printADMgmt);
tvADMgmt.addAd("小花猫纸尿裤");
tvADMgmt.addAd("斑点狗婴儿车");
printADMgmt.addAd("小花猫纸尿裤");
printADMgmt.addAd("斑点狗婴儿车");
tvADMgmt.showAds();
printADMgmt.showAds();
System.out.println("向问题产品清单中加入小花猫纸尿裤");
defectedGoodsList.addGoods("小花猫纸尿裤");
tvADMgmt.showAds();
printADMgmt.showAds();
// 电视广告解除绑定
defectedGoodsList.detach(tvADMgmt);
System.out.println("从问题产品清单中将小花猫纸尿裤移除");
defectedGoodsList.removeGoods("小花猫纸尿裤");
tvADMgmt.showAds();
printADMgmt.showAds();
}


编译运行后,得到如下测试结果:

播放电视广告:

小花猫纸尿裤电视广告

斑点狗婴儿车电视广告

播放平面广告:

小花猫纸尿裤平面广告

斑点狗婴儿车平面广告

向问题产品清单中加入小花猫纸尿裤

发布商品“小花猫纸尿裤”召回通知

播放电视广告:

斑点狗婴儿车电视广告

播放平面广告:

斑点狗婴儿车平面广告

从问题产品清单中将小花猫纸尿裤移除

播放电视广告:

斑点狗婴儿车电视广告

播放平面广告:

小花猫纸尿裤平面广告

斑点狗婴儿车平面广告
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息