一个优秀的android开源框架中往往会体现出很多Java设计模式的影子,了解设计模式有助于理解开源框架中的程序设计之美接下来我会将自己整理的对一些设计模式的理解记录在这里
若您对我的分享感兴趣可以访问:java设计模式专栏
本篇记录:过滤器模式 、组合模式、外观模式
传送门: java设计模式分析及在android中的应用一
java设计模式之组合模式
从真实项目中抠出来的设计模式:过滤器模式
过滤器模式(Filter Pattern)或标准模式(Criteria Pattern)是一种结构型模式,这种模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来,它可以结合多个标准来获得单一标准
方法:
我们可以定义一系列规则。这些规则可以针对目标进行满足自身规则的过滤
使用场景:
我们在给用户做订单催付通知的时候,会有这样的一种场景,用户在系统后台设置一组可以催付的规则,比如说订单金额大于xx元,非黑名单用户,来自哪个地区,已购买过某个商品等等这样的条件,如果这时用户下了一个订单,那程序要判断的就是看一下此订单是否满足这些规则中的某一个,如果满足,我们给他发送催付通知,这种场景是很多做CRM的同学都会遇到的问题,那针对这种场景,如何更好的规划业务逻辑呢?
如果是普通代码实现:
我们可能会定义出一个删选类,由其负责对各种规则的筛选,那么很多不清楚设计模式的人可能会秀出如下代码:
var regulars = new List<Regulars>(); regulars.Add(new Regulars() { RegularID = 1, RegularName = "规则1", AnalysisConditons = "xxxx" }); regulars.Add(new Regulars() { RegularID = 1, RegularName = "规则2", AnalysisConditons = "xxxx" }); regulars.Add(new Regulars() { RegularID = 1, RegularName = "规则3", AnalysisConditons = "xxxx" }); regulars.Add(new Regulars() { RegularID = 1, RegularName = "规则4", AnalysisConditons = "xxxx" }); var filters = FilterRegularID(regulars); filters = FilterRegularName(filters); filters = FilterCondtions(filters); //... 后续逻辑 } static List<Regulars> FilterRegularID(List<Regulars> persons){ //过滤 “姓名” 的逻辑 return null; } static List<Regulars> FilterRegularName(List<Regulars> persons) { //过滤 “age” 的逻辑 return null; }
///}/// 各种催付规则 ///
这种写法简单粗暴,维护起来也是一样的简单粗暴,上万行的代码就是这样出来的,暂时看来已经实现了必须的功能,但是一旦后续一旦有规则的变更,相信即便是当时写出这些代码的人也会头疼
设计模式告诉我们一个简单的“开闭原则”,那就是追求最小化的修改代码,这种场景有更好的优化策略吗?
当然有对应到设计模式上就是“过滤器模式”,专门针对这种场景的解决方案,一个维度一个类,然后通过逻辑运算类将他们进行组合
让我们看看使用过滤器模式优化之后的代码结构:
从上面这张图纸中可以看到,我们抽象出了一个IFilter接口,然后提取成了三个子类每一个子类负责一个维度的过滤方法,然后实现了两个逻辑运算类AND和OR子类Filter,用于动态的对上面的三个维度的过滤方法进行AND,OR逻辑运算
最后在我们的调用程序里面,我们需要做的工作就简单了,只需要将每个维度的过滤条件,追加到逻辑运算类里面就可以说笑呢功能了
and和or逻辑运算类的实现也比较简单,两个类里面都维护了一个过滤器列表
使用泛型我们可以增加对多种过滤对象的支持
有没有发现,如果后续有需求变更,比如说增加筛选的维度,我只需要新增一个继承IFilter的子类就搞定了,客户端在调用的时候只要在Filters集合中追加该筛选维度,是不是就OK了
所以这种模式几乎达到了无代码修改的地步,这就是设计模式给我们带来的便捷
2、组合模式
可能我们看到组合这个词,会下意识的和Java对象里面的对象的组合联系起来,但其实这两者之间没有联系
java对象的组合指的是对象之间的协同工作,例如:
一个人可以由大脑、躯干、四肢等等对象组成那么在People这个类中将head、trunk、limb等作为字段组合进来,让这些对象协同完成一个人对象和以完成的工作,这就是java对象的组合
而组合模式则主要是使用面向对象的思想来实现树形结构的构建与处理,其中心思想是把一组相似的对象当做一个单一对象来进行处理
举个例子:
windows系统中对于文件夹管理就是典型的组合模式对于树形结构的处理
再比如应用软件中的菜单,办公系统中的公司组织结构等等
组合模式的优点:
组合模式通过一种巧妙的设计方案使得用户可以一致性地处理整个树形结构或者树形结构的一部分,也可以一致性地处理树形结构中的叶子节点(不包含子节点的节点)和容器节点(包含子节点的节点)
接下来我们看看组合模式在实际场景中的应用
设计杀毒软件:
该软件既可以对某个文件夹(Folder)杀毒,也可以对某个指定的文件(File)进行杀毒。该杀毒软件还可以根据各类文件的特点,为不同类型的文件提供不同的杀毒方式,例如图像文件(ImageFile)和文本文件(TextFile)的杀毒方式就有所差异。现需要提供该杀毒软件的整体框架设计方案。
对于树形结构,当容器对象(如文件夹)的某一个方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员对象(可以是容器对象,也可以是叶子对象)并调用执行,牵一而动百,其中使用了递归调用的机制来对整个结构进行处理。
由于容器对象和叶子对象在功能上的区别,在使用这些对象的代码中必须有区别地对待容器对象和叶子对象,而实际上大多数情况下我们希望一致地处理它们,因为对于这些对象的区别对待将会使得程序非常复杂。
组合模式为解决此类问题而诞生,它可以让叶子对象和容器对象的使用具有一致性
例如我们对杀毒软件的架构设计如下:
AbstractFile充当抽象构件类,Folder充当容器构件类,ImageFile、TextFile和VideoFile充当叶子构件类
叶子类不支持文件的增删操作,但是因为要进行统一封装,保持对调用这的透明,所以也实现了增删操作,不过可以提供对应的错误提示和异常处理
由于在本实例中使用了组合模式,在抽象构件类中声明了所有方法,包括用于管理和访问子构件的方法,如add()方法和remove()方法等,因此在ImageFile等叶子构件类中实现这些方法时必须进行相应的异常处理或错误提示
在容器构件类Folder类的killVirus()方法中,Folder将递归调用其成员对象的killVirus()方法,从而实现对整个树形结构的遍历。
如果需要更换操作节点,例如只需对文件夹“文本文件”进行杀毒,客户端代码只需进行简单的修改即可
客户端无须关心节点的层次结构,可以对所选节点进行统一处理,提高系统的灵活性
当然如果不想让叶子节点实现不必要的方法,可以采用安全组合模式:
安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件
在实际应用中,安全组合模式的使用频率也非常高,在Java AWT中使用的组合模式就是安全组合模式。
原理图如下:
ShapeMaker就是外观模式提供的接待员,这个接待员熟悉左边复杂的shape系统的内部结构,然后隐藏了其中的各种实现,而仅仅只向我们提供了简单那的调用方法