设计模式--组合模式

2019-04-13 13:28发布

一、概述

定义:Compose objects into tree structures to represent part-whole hierarchies.Composite lets clients treat individual objects and compositions of objects uniformly。 角 {MOD}组成:
  • 抽象构件 Component
    定义:各个对象可以提取出来的公共属性和行为。
  • 叶子构件 Leaf
    定义:最小对象单位,其下再无子分支。
  • 容器构件 Composite
    定义:组合对象,包含多个叶子构件。

二、应用场景

具有树形结构的业务模型,例如文件系统、具有嵌套字段的报文、复杂的布尔表达式等。 重要的是将业务模型树形化,通过用抽象构件或者容器构件来抽象各个节点,然后组织各个节点成组合对象的形式。从而用户对单个对象和组合对象的使用具有一致性。

三、实现方式

Composite Pattern实现方式分为两种:
  • 透明式的 将管理子构件方法定义在抽象构件Component中。在接口层次上叶子构件Leaf和容器构件Composite没有区别,即用户通过操作抽象控件,透明的完成叶子构件和容器构件的业务处理。但Leaf会有一些管理子构件的方法,这些方法对于Leaf构件意义不大,需要在运行期进行空处理或者抛出异常,这样会带来一些安全问题。
  • 安全式的 将管理子构件的方法定义在容器控件Composite中。在接口层次上叶子构件Leaf和容器构件Composite有不同的接口方法,即用户通过操作不同的控件,完成不同的功能。这样编译时处理Leaf功能只能用Leaf控件,处理Composite功能只能用Composite控件,又失去了透明性。

四、示例

(一)文件系统
在文件系统中,可能存在很多种格式的文件,如果图片,文本文件、视频文件等等,这些不同的格式文件的浏览方式都不同,同时对文件夹的浏览就是对文件夹中文件的浏览。
我们一般会分别定义了文件节点对象与目录节点对象,这是因为文件与目录之间的操作不同,文件没有下级节点,而目录可以有下级节点。这样操作文件节点和目录节点完成不同的操作。
重要的是将业务模型树形化,通过用抽象构件或者容器构件来抽象各个节点,然后组织各个节点成组合对象的形式。从而用户对单个对象和组合对象的使用具有一致性。
但是如果将文件系统树形结构化,文件与目录都是可以作为一个节点的下级节点而存在,我们可以将这两类抽象成一类对象,例如抽象构件(透明模式)和容器构件(安全模式)。
透明模式的示例:
  1. 抽象构件 Node
/** * 将文件与目录统一看作是一类节点,做一个抽象类来定义这种节点,然后以其实现类来区分文件与目录,在实现类中分别定义各自的具体实现内容 */ public abstract class Node { protected String name;//名称 //构造器赋名 public Node(String name){ this.name = name; } //新增节点:文件节点无此方法,目录节点重写此方法 public void addNode(Node node) throws Exception{ throw new Exception("Invalid exception"); } //显示节点:文件与目录均实现此方法 abstract void display(); }
  1. 叶子构件
/** * 实现文件节点 */ public class Filer extends Node { //通过构造器为文件节点命名 public Filer(String name) { super(name); } //显示文件节点 @Override public void display() { System.out.println(name); } }
  1. 容器构件
import java.util.*; /** * 实现目录节点 */ public class Noder extends Node { List nodeList = new ArrayList();//内部节点列表(包括文件和下级目录) //通过构造器为当前目录节点赋名 public Noder(String name) { super(name); } //新增节点 @Override public void addNode(Node node) throws Exception{ nodeList.add(node); } //递归循环显示下级节点 @Override void display() { System.out.println(name); for(Node node:nodeList){ node.display(); } } }
  1. 测试代码
import java.io.File; public class Clienter { public static void createTree(Node node) throws Exception{ File file = new File(node.name); File[] f = file.listFiles(); for(File fi : f){ if(fi.isFile()){ Filer filer = new Filer(fi.getAbsolutePath()); //可能会出现这种情况:filer.add(fi); //利用Node统一化处理 node.addNode(filer); } if(fi.isDirectory()){ Noder noder = new Noder(fi.getAbsolutePath()); //利用Node统一化处理 node.addNode(noder); createTree(noder);//使用递归生成树结构 } } } public static void main(String[] args) { //利用Node统一化处理 Node noder = new Noder("E://ceshi"); try { createTree(noder); } catch (Exception e) { e.printStackTrace(); } noder.display(); } } 安全模式的实现:
  1. 抽象构件
public abstract class Node { protected String name;//名称 //构造器赋名 public Node(String name){ this.name = name; } //显示节点:文件与目录均实现此方法 abstract void display(); }
  1. 容器构件
import java.util.*; /** * 实现目录节点 */ public class Noder extends Node { List nodeList = new ArrayList();//内部节点列表(包括文件和下级目录) //通过构造器为当前目录节点赋名 public Noder(String name) { super(name); } //新增节点 public void addNode(Node node) throws Exception{ nodeList.add(node); } //递归循环显示下级节点 @Override void display() { System.out.println(name); for(Node node:nodeList){ node.display(); } } } (二)复杂的布尔表达式
  1. 业务模型 solr命令
price >= 10.25 AND ( category : [21 TO 37] OR name : 张三
  1. 抽象层表示 json串
{ "condition": "AND", "rules": [{ "field": "price", "operator": "greater_or_equal", "value": 10.25 }, { "condition": "OR", "rules": [{ "field": "category", "operator": "between", "value": [21,37] }, { "field": "name", "operator": "equal", "value": "张三" } ] } ] }
  1. 对象化
    将布尔表达式结合的两边对象化,没有包含布尔表达式的为最小单位,叶子构件Rule
{ "field": "name", "operator": "equal", "value": "张三" } 包含布尔表达式的是容器构件Group { "condition": "OR", "rules": [{ "field": "category", "operator": "between", "value": [21,37] }, { "field": "name", "operator": "equal", "value": "张三" } ] } 当然整个json串也是个容器构件。
4. 组合模式使用
使用透明模式的组合模式,将所有对象共有的属性和行为定义为抽象构件。这里只将公共行为进行抽象,且分成两个接口。
叶子构件接口IRule public interface IRule { String getField(); void setField(String field); String getOperator(); void setOperator(String operator); Object getValue(); void setValue(Object value); } 容器构件接口IGroup public interface IGroup { String getCondition(); void setCondition(String condition); List getRules(); void setRules(List rules); } 再就是泛化这两个接口的容器构件实现JsonRule public class JsonRule implements IGroup, IRule { // --------------------------- Rule -------------------------------------- private String id; private String field; private String type; private String input; private String operator; private Object value; private Object data; // --------------------------- Group -------------------------------------- private String condition; private Boolean not; private List rules; /** * 判断是否为group * @return */ public boolean isGroup() { return condition != null; } /** * group * @return */ public IGroup toGroup() { return this; } /** * rule * @return */ public IRule toRule() { return this; } /** * to String * @return */ @Override public String toString() { ObjectMapper mapper = new ObjectMapper(); try { return mapper.writeValueAsString(this); } catch (Exception e) { return super.toString(); } } @Override public String getField() { return field; } @Override public void setField(String field) { this.field = field; } @Override public String getOperator() { return operator; } @Override public void setOperator(String operator) { this.operator = operator; } @Override public Object getValue() { return value; } @Override public void setValue(Object value) { this.value = value; } @Override public String getCondition() { return condition; } @Override public void setCondition(String condition) { this.condition = condition; } @Override public List getRules() { return rules; } @Override public void setRules(List rules) { this.rules = rules; } } 最后具体解析的时候,就可以统一按照JsonRule来进行处理 public Object parse(JsonRule jsonRule) { // parse if (jsonRule.isGroup()) { return groupParser.parse(jsonRule, this); } else { for (IRuleParser ruleParser : ruleParsers) { if (ruleParser.canParse(jsonRule)) { return ruleParser.parse(jsonRule, this); } } throw new ParserNotFoundException("Can't found rule parser for:" + jsonRule); } } 其中对于有Condition的情况,用groupParser处理,获取里面的每个Rules元素,再分别用RuleParser.parse处理
(三)嵌套字段报文
银行的请求报文,经常出现一个字段包含几重的多个子字段。这时候报文格式梳理到excel中,就是类似 Key Name IsGroup Count Value field1 文件 0 d: group1 1 3 field2 账号 133 field3 金额 200 field4 时间 0 today 对于这种情况,所有字段都可以抽象成抽象构件,容器构件Group自身引用多个抽象构件(透明模式)
即:
抽象构件IField public abstract class AField{ --------------------------------Field private String key; private String name; private Object value; ----------------------------Group private boolean isGroup; private int counts; ---------------------------- gettingsetting... } 没有嵌套的字段field1field4,Field public class Field extends AField{ public Field(String key, String name,String value){ } } 嵌套字段field2field3 public class Group extends AField{ public Group(String key, String name,boolean isGroup,int count){ } } 后续解析统一用AField处理处理。

五、总结

理解组合模式的使用场景,学会从复杂的业务模型中对象化各个构件,能够统一方式处理各个独立对象和组合对象。