通过监听与过滤器模式使用来体会java中对象关系

作为面向对象的语句,java的三大基本特性:封装、继承、多态。但随着做的系统越来越复杂,发现组织对象之间的关系是更重要的技能。spring我认为IOC容器的好处之一就是方便的组织系统中的对象关系。
感觉用java后,有了一个模仿现实世界,来组织软件系统的方式。越来越感觉软件设计如同生活场景设计有相似之处。想到一句话,人的本质是一切社会关系的总和。正好也体会到,组织好系统中对象的各种关系是非常重要的,甚至我认为一个系统中,关系是最主要的。了解了对象的关系,系统就明白了。

先从简单的监听模式,来引入对象之间的关系。再介绍一下最近改造一个系统,所使用到的filterchain模式来,进一步介绍我是如何思考对象之间的关系的。

[size=large][b]一、监听器模式[/b][/size]
为什么说监听?监听很好体现了关系这一点,很多书介绍模式都比较简单。

这是非常常见的设计模式。比如Web开发,可以监听web容器启动,可以监听session事件等等。事件源注册监听器之后(可以是多个,放在自己的容器中),当事件源触发事件,每个监听器就可以得到消息对象进行自己的处理;更形象地说,监听者模式是基于:注册-回调的事件/消息通知处理模式,就是被监控者将消息通知给所有监控者。


[img]http://dl2.iteye.com/upload/attachment/0130/2179/a92357ce-7357-36b8-a65c-865c294ba259.jpg[/img]

如上图所示:
会议作为被监听者,一般是单例模式,想监听的就可以方便的找到它的实例。
会议内有一个容器,放着监听者(它们都实现相同的接口),接口由会议提供。
会议发生了事件,就循环容器,按接口调用一个个监听者的接口方法即可。

监听者都实现了公共的接口。

这个模式很简单,接下来扩展一下,有对象产生监听器,来稍微复杂一点的。

[size=large][b]二、带有复杂的引用关系监听[/b][/size]
来一个复杂的场景吧,比如:局长安排秘书监听一个会议,会议结束了秘书直接通知处长办理(秘书不认识处长,局长给秘书一个电话号码),处长办理好了汇报给局长。

1.会议是被监听的对象,类似于服务端,不依赖监听者。比如你自己的代码会监听spring容器的启动,比如监听TCP通讯的事件。被监听的对象,比如会议,通常提供这个接口,另外会提供一个容器,放置监听者。被监听的对象通常是一个单例,用静态的getInstance就可以找到。监听者可以方便的找到被监听的对象。监听者可以受人指使去监听,可以自己就想去听。


[img]http://dl2.iteye.com/upload/attachment/0130/2181/b50b884a-570b-3118-bec8-0d070db5bae4.jpg[/img]

[list]
[*]1.new秘书设置给自己引用
[*]2.new处长设置给自己引用,由于把this传入,并让处长记住自己,内部再调用处长的设置局长的方法。
[*]3.派秘书参会,由于秘书并没有自己的引用,这样参加好,回调时,秘书无法汇报给局长。
[*]4.派秘书参会,把自己引用的处长设置进去,这样,秘书开好会,可以转达处长,处长由于前面设置了局长的引用,处长办好事可以汇报给局长了。
[/list]

2.局长new一个秘书,再new一个处长同时把自己this注入给处长,并调用处长的设置局长的方法。这样会相互持有,new秘书时没有注入this,只是单向引用。关系见上图右下角中圈中的表示。

3.秘书对象必须实现一个会议的接口,同时局长一定有一个方法,把处长设置让秘书持有,这样秘书监听到事件通知处长。局长先知道有这个会,所以会调用会议的方法,把秘书放置到会议容器中去。

4.一旦会议结束了,会议从自己的容器中取出秘书,并调用秘书实现的接口方法。秘书持有处长引用,在接口方法中调用处长的方法,处长肯定有一个汇报方法,向自己持有的局长对象汇报工作。

特意没有让秘书持有局长的引用。这样场景类似于局长刚有了一个秘书,通知他去开个会,只告诉了处长的联系方式(设置处长给秘书),让他开好会后,把会议要求告诉处长。但局长没给秘书自己的手机号。假如生活中,秘书手机丢了,会开好发现找不到处长了,处长引用是空值,而且又没有持有局长的引用,这下监听接口就调用失败了。


[color=red][size=large][b]三、filterchain中对象及引用关系设计[/b][/size][/color]

过滤链模式正好最近在项目中使用。也因为Web应用中大家用filter很常见,所以我叫此模式为过滤链。另外在druid的源码中也看到过这个模式,druid是阿里面向监控的连接池工具。它包装了所有的数据库连接中的对象,内部引用代理对象,这样有了代理,就在原来所有的数据库对象操作中,都强行插入自己的过滤器,过滤后再执行真正引用的原始对象。

另外,我设计系统中对象时,对象基本上分为两种:[b]“配置对象”[/b]与[b]“业务对象”[/b]。当然,这是相对的,配置对象有时也可能是业务对象,特别是在后台管理系统中。“配置对象”之间的引用是设计的重点。

用“铁打的营盘流水的兵”来比喻,铁打的营盘就是“配置对象”,没业务之前我先建一个营盘,内部有各机关,部门。兵就是“业务对象”,在营盘中不断接收、使用、送别士兵。与软件系统对应的是,营盘对象一盘都是singleton对象,营盘中的对象很多都是相互引用的,不过为了不太乱,还是要分类别,加容器中管理。如果职能部门的人员要组织在不同的部门一样。软件的设计与生活中的设计是多少有相似性啊。
而兵都是一个个new出来的,是被营盘处理的对象,通常来自外部的请求。营盘对象一般一直存在于内存中(从配置表或者配置文件加载),速度快,而发生变化的情况少。兵是从外部产生的,在内存中处理好后,就消失(比如持久化到数据库)。生活中,学校里的机构与老师都是配置对象,学生是业务对象。医院里的机构与医生是配置对象,病人是业务对象。

回到过滤链模式中,先用生活中这样的模式举例。比如体检,给每一个受检人员的单据就是过滤链filterChain实例(兵),每个检查的科室就是过滤器filter(营盘),到一个科室,你把单据给医生处理,处理好,你就做下一项。可以看到单据是轻量的业务对象,科室是重量级的配置对象。科室不持有单据对象,而是每次收到进行处理,处理好就结束了。

再回到web应用中的filter,doFilter就是过滤器中要实现的方法。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) 从中可以看到,这三个参数都是外部传入的业务对象,每一个外部的请求,这三个都是不同的实例。配置对象一般不持有业务对象,而是传过来,处理好就结束了。在doFilter中,会调用传入的业务对象filterChain的doFilter方法。实现就是让单据做下一个过滤,也就是让单据做下一个体检。filterChain.doFilter(request, response);

我们知道,单据是不可能自己做体检的,doFilter中的方法只是找到这个单据中记录的下一个科室,让这个科室真正再做doFilter。科室虽然不会持有单据,但每一个单据必须引用科室对象。通常的做法是单据引用一个医院对象,医院对象中必然有所有科室的容器。每个单据记录下自己项目的科室的索引,这样每个单据从医院对象中的科室容器中找到科室,相当于你在医院的大楼科室布局图上找到科室一样。每个单据都是一个业务对象,各不相关,也许包含的科室不一样,也许项目一样,但当前已经完成的项目不一样。比如大家一起去体检,表格都一样,有的人检查的快,有的人慢。


[img]http://dl2.iteye.com/upload/attachment/0130/2183/79212cd1-c35c-3cfb-8936-2537678b0b7a.jpg[/img]

图中:厂房、工具间、5个标号的设备都是单例,都直接或者间接的引用着。每个业务单据上的记录都不一样,没处理过的都是00000,中间处理的可能是11000,后面都处理好的是11111。

具体说一下我设计的系统中的对象:
首先有一个厂房,里面有容纳过滤器的MAP,都是spring加载的单例(默认都是单例),还有一个数组对应过滤器通过afterPropertiesSet加载,方便通过索引找到过滤器。

//厂房对象
@Component
public class TaskContainer implements InitializingBean {

public Processer[] porcesser=new Processer[8];//过滤器索引-设备编号

@Autowired
private Map<String,Processer> processerMap;//所有的过滤器-厂房中的设备

@Override
public void afterPropertiesSet() throws Exception {
//顺序排序
porcesser[0]=processerMap.get("processerA");
porcesser[1]=processerMap.get("processerB");
porcesser[2]=processerMap.get("processerC");
porcesser[3]=processerMap.get("processerD");
porcesser[4]=processerMap.get("processerE");
...
...
...//后面继续加,与数组个数一样
}

dealBizInfo(BizInfo bizInfo){
ProcesserChain processerChain = new ProcesserChainImpl(TaskContainer.this, bizInfo);
processerChain.setStartIndex(0,5);//从0开始,到5号。对应过滤器的索引号
processerChain.processOrder();
}



再就是厂房里的设置:过滤器。它也是单例对象,实现相同的接口,加载处理数据需要的工具对象。接口的实现中两个参数,一个是要加工的数据对象,一个是过滤链对象,这两个都是业务对象。
厂房既然要加工数据,就有这个一个方法, 给过来的数据生成一个单据-过滤链条,主要语句有:

@Component
public class ProcesserA implements Processer{

@Autowired
Tools tools;//过滤器自己有的工具。各种刀枪剑戟 斧钺钩叉工具。

@Override
process(BizInfo bizInfo,ProcesserChain processerChain) {
//进行自己的处理

tools.deal1(bizInfo);
tools.deal2(bizInfo);
tools.deal3(bizInfo);
...
...
...
...
processerChain.processOrder();//最后交给过滤链,它会用下一个过滤器处理
}



接下就是过滤链对象,不让spring管理了,或者是prototype也可。来一个业务new一个。
它内部要持有厂房对象,内部要有一个int的索引,记录当前处理就什么位置。当然内部还要持有业务数据。

public class ProcesserChainImpl implements ProcesserChain {
private TaskContainer taskContainer;
private BizInfo bizInfo;
private int processIndex=0;//
private int processIndexEnd=4;//

@Override
processOrder() {
next().processOrder(bizInfo,this);//获取当前索引的过滤器进行处理
}
private OrderProcesser next(){
return taskContainer.porcesser[processIndex++];//从厂房中拿到对应索引的过滤器,索引号+1.
}

//设置开始与结束索引
@Override
public void setIndex(int startIndex,int endIndex) {
// TODO Auto-generated method stub
processIndex=startIndex;
processIndexEnd=endIndex;
}


processerChain.processOrder()方法就是开始处理数据了,过滤链(业务对象)肯定要引用厂房对象(直接引用很多工具对象,会显的有些乱。如果有工具容器,可以引用它),这样从厂房中拿到索引对应的设备(配置对象)进行处理,索引号加1,设备处理中会收到数据与过滤链两个参数,处理好,再调用传入的过滤链的方法,又会找下一个设备,再把数据与自己传进入...链条就这样循环启动起来了。

与传统的filter不同,我进行了改进。我也配置了多个过滤器,但业务处理的过滤链根据实际并不都是从头到尾,有开始与结束的索引。如果中间出错了再启动,可以从断掉的地方再配置。一部分业务用前面几个过滤器,后一部分异步任务用另外几个过滤器。如果想更灵活,可以配置,这个业务使用13567过滤器,那个业务使用24678号过滤器。

[color=red][size=large][b]四.开发微核心结构,对象外部接口实现的加载不同的实现[/b][/size][/color]

最近做的一批小项目,因为核心功能是一样的,不同的部分都用接口。简化的说,就是抽象化开发了接收数据,外部持久化接口,处理数据接口,结果汇总,再次结果持久化接口这样的一个过程。

这种样子的工具非常多,比如JBPM工作流工具,比如shiro,spring security权限控制,他们都实现了一个基本的公共的功能,但有些需要使用者开发接口的实现类。

首先我这个微核心模块与spring没有半毛钱关系,但接口的实现,由于多数是web项目,就与spring有密切关系了,就拿持久化来说,通常service、dao由spring管理。

如何对接这样的微功能模块呢?还记得有一个原则,高内聚,低耦合。我在使用这样开发好的模块时,会专门设计一个spring的类,来持有(或者叫对接、管理、holder)这个微功能模块中的厂房,如同你的公司买了另一个公司,会安排一个高级人员专门去管理那个公司。这个微系统通常自己有一个静态的态来管理它的启动,管理它要传入参数与实现类,还要调用静态方法init(),让这个工厂运转起来。这个静态类如同那个公司的负责人,派来的人员,只要对接公司目前的负责人就可以了。

通常我的做法是这样:spring的对接类(高级人员),会注解引用一些需要的接口的实现类。它管理静态类时(通常是afterpropetyset方法中),会把接口实现类通过模块静态类的方法传进去。如同你自己带来的几个人去接管,通过原负责人安排这些人到关键的、缺少的岗位上去。这样,接手的公司就可以正常运转下去了。

[color=red][size=x-large][b]五、总结[/b][/size][/color]

上面介绍了对象的引用、相互引用、配置类的多边引用与业务类的关系。更进一步分析了类组成的模块之间如何进行关联的经验。

回顾起来,我设计系统,貌似总喜欢拿出生活中的场景来对应,这样让系统中的对象之间的关系更合理。我认为生活中的制度设计、组织设计可以用软件来模拟,也可以给软件设计以启发。


[size=large][b]注:[/b][/size]目前对过滤器模式又进行了改进,除了之前配置开始结果索引外,考虑到失败情况描述,在filterChain中增加处理信息字段。当某一个处理器处理失败,会写入失败码与描述,成功也会。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值