书接上回,本篇讲一下结构型模式-组合设计模式
组合设计模式
定义: 将对象组合成树状结构以表示 “部分-整体” 的层次结构。组合模式使客户端对单个对象和组合对象保持一致的方式处理
这里需要解释几个概念,单个对象与组合对象,部分与整体:
部分与整体:这里理解要区分语境,不同语境下部分与整体表示意思都有一定差异,但都有包含与被包含的意思的。组合模式的部分与整体表示树状接口中叶子节点(leaf)与树干节点(composite)/组合节点。树干节点包含叶子节点。
单个对象:叶子节点对象--Leaf,它单个节点对象
组合对象:树干节点对象-组合节点--composite,它也是组合节点对象,可以包含0或多个leaf节点。
UML
Comopent:抽象组件对象,定制公共的操作方法。Leaf跟Composite的共同父类,后续客户端借助继承多态方式实现对Leaf/Composite统一处理。【组合模式的精髓】
Leaf:叶子节点对象,单节点,不再包含其他节点对象。Comopent父类为了统一操作,会定义很多方法,包括Leaf跟Composite独有的,Leaf节点继承之后,需要根据需要重写自己方法。
Composite:组合对象,通常会包含其他子组件,可以使Leaf节点,也可以使其他子Composite,也继承了Comopent父类,需要重写属于自己的方法。
Component
/**
* 抽象父节点--组件
* 作用:定制叶子节点跟组合节点方法
*/
public abstract class Component {
//抽象方法,叶子节点与组合节点都可以实现
public abstract void operation();
//添加子节点:默认实现
//叶子节点没有此操作,不需要重写,真调用时,抛异常
//组合节点有此操作,需要重写
public void addChild(Component child){
throw new UnsupportedOperationException("对象不支持添加子节点操作");
}
//删除子节点:默认实现
//操作原因同:addChild
public void removeChild(Component child){
throw new UnsupportedOperationException("对象不支持删除子节点操作");
}
//获取子节点:默认实现
//操作原因同:addChild
public Component getChild(int index){
throw new UnsupportedOperationException("对象不支持获取子节点操作");
}
}
Leaf
/**
* 叶子节点,不包含其他子节点
* 只需要重写leaf方法
*/
public class Leaf extends Component{
@Override
public void operation() {
System.out.println("我是叶子节点");
}
}
Composite
/**
* 组合节点,包含其他叶子节点/子组合节点
* 只实现Composite 方法
*/
public class Composite extends Component{
private List<Component> children = new ArrayList<>();
@Override
public void operation() {
System.out.println("我是组合节点");
for (Component child : children) {
//需要时需要递归执行
System.out.print(" ");
child.operation();
}
}
//需要重写组合节点自己方法
@Override
public void addChild(Component child) {
if(child != null){
children.add(child);
}
}
@Override
public void removeChild(Component child) {
if(child != null){
children.remove(child);
}
}
@Override
public Component getChild(int index) {
if(index >= 0 && index < children.size()){
return children.get(index);
}
return null;
}
}
测试
public class App {
public static void main(String[] args) {
Component leaf1 = new Leaf();
Component leaf2 = new Leaf();
Component composite = new Composite();
composite.addChild(leaf1);
composite.addChild(leaf2);
composite.operation();
}
}
我是组合节点
我是叶子节点
我是叶子节点
解析
上面的概念已经解释差不多了,这里总结一下:
1>组合定义Component抽象组件类,统一Leaf跟Composite的操作,客户端不需要区分处理
2>Leaf子节点继承Component组件,重写属于Leaf节点的方法
3>Composite节点继承Component组件,重写属于Composite节点的方法
4>Leaf子节点不包含其他子节点,Composite节点可以包含Leaf节点跟其他子Composite节点
案例分析
需求:设计一个动态菜单,如下图:
分析:案例构建对象结构如下
Compoment -抽象菜单
/**
* 抽象父菜单--组件
* 作用:定制叶子菜单跟组合菜单方法
*/
public abstract class Component {
//抽象方法,叶子菜单与组合菜单都可以实现
public abstract void print();
//是否leaf菜单
public abstract boolean isLeaf();
public void addChild(Component child){
throw new UnsupportedOperationException("对象不支持添加子菜单操作");
}
public void removeChild(Component child){
throw new UnsupportedOperationException("对象不支持删除子菜单操作");
}
public Component getChild(int index){
throw new UnsupportedOperationException("对象不支持获取子菜单操作");
}
}
MenuLeaf-叶子菜单
/**
* 叶子菜单,不包含其他子菜单
* 只需要重写leaf方法
*/
public class MenuLeaf extends Component {
private String name;
public MenuLeaf(String name) {
this.name = name;
}
@Override
public void print() {
System.out.println("leaf:" + name);
}
@Override
public boolean isLeaf() {
return true;
}
}
MenuComposite-组合菜单
/**
* 组合菜单,包含其他叶子菜单/子组合菜单
* 只实现Composite 方法
*/
public class MenuComposite extends Component {
private int level;
private String name;
private List<Component> children = new ArrayList<>();
public MenuComposite(String name, int level) {
this.name = name;
this.level = level;
}
@Override
public void print() {
System.out.println("Composite:" + name);
for (Component child : children) {
for (int i = 0; i < this.level; i++) {
System.out.print("--");
}
child.print();
}
}
@Override
public boolean isLeaf() {
return false;
}
//需要重写组合菜单自己方法
@Override
public void addChild(Component child) {
if(child != null){
children.add(child);
}
}
@Override
public void removeChild(Component child) {
if(child != null){
children.remove(child);
}
}
@Override
public Component getChild(int index) {
if(index >= 0 && index < children.size()){
return children.get(index);
}
return null;
}
}
测试
public class App {
public static void main(String[] args) {
Component systemC = new MenuComposite("系统管理", 1);
Component customerC = new MenuComposite("客户管理", 2);
Component onlineLeaf = new MenuLeaf("线上客户");
Component latentLeaf= new MenuLeaf("潜在客户");
Component siteLeaf = new MenuLeaf("公司官网");
Component publicLeaf = new MenuLeaf("公共管理");
customerC.addChild(onlineLeaf);
customerC.addChild(latentLeaf);
systemC.addChild(customerC);
systemC.addChild(siteLeaf);
systemC.addChild(publicLeaf);
systemC.print();
}
}
结果
Composite:系统管理
--Composite:客户管理
----leaf:线上客户
----leaf:潜在客户
--leaf:公司官网
--leaf:公共管理
解析
没有解析了,一切就在代码中。
安全性VS透明性
组合模式代码设计上存在一定争议:抽象组件设计时需要考虑操作的安全性还是考虑操作透明性呢?
操作安全性
以Component抽象组件的addChild方法为例子。按组合模式规则设计,addChild方法很明显是给Composite子组件设计的,但是Leaf节点也继承Component类, addChild自然也可以调用。此时就存在安全问题了。比如:
Leaf leaf1 = new Leaf();
leaf1.addChild(...);
客户端误操作,就是组合模式操作安全隐藏。那怎么办?组合模式提出解决方案:Component抽象类只定义Composite子组件与Leaf节点共有的方法,其他方法由它们自己按照需要定义。
/**
* 抽象父节点--组件
* 作用:定制叶子节点跟组合节点方法
*/
public abstract class Component {
//抽象方法,叶子节点与组合节点都可以实现
public abstract void operation();
}
操作透明性
上面操作看是确实是解决了安全隐患问题,但是又引出新的问题,操作透明性问题。
Component composite = .....
Composite cp = (Composite) composite;
cp.addChild(...);
看上面代码, 客户端无法对Component组件今天统一处理了,需要明确的强转成对应的具体composite/leaf之后才能执行下一步操作。那Component对客户端透明操作性就没啦。
最终选择
对于组合模式来说,安全性与透明性,一般设计更看重透明性,毕竟组合模式核心:让客户端能统一操作composite跟leaf对象。
所以,大部分设计方案就是在component类上加上isLeaf 或者 getComposite 方法用于判断composite对象还是leaf对象。同时让需要重写方法加上默认实现:抛异常。
适用场景
客户端希望忽略组合对象与单个对象的差异时,可用
对处理一个树状结构时,可用
优缺点
优点
清楚地定义分层次的复杂对象,表示对象的全部或部分层次
让客户端忽略了层次的差异,方便对整个层次结构进行控制
缺点
使用抽象类限制,跟统一子类类型和行为,类复杂
代码设计偏抽象
开发案例
JDK中-XML/HTML DOM
public interface Node {
public Node appendChild(Node newChild) throws DOMException;
}
Node子接口Text,Element,Document,Attr等,基本上具有个体与组合整体特点。
JDK中-ArrayList/HashMap
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
public boolean addAll(Collection<? extends E> c) {...}
}
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true);
}
}
单个元素是-leaf, 多个元素-Composite
Mybatis-SqlNode
public interface SqlNode {
boolean apply(DynamicContext context);
}
public class ChooseSqlNode implements SqlNode {
private final List<SqlNode> ifSqlNodes;
public ChooseSqlNode(List<SqlNode> ifSqlNodes, SqlNode defaultSqlNode) {
this.ifSqlNodes = ifSqlNodes;
this.defaultSqlNode = defaultSqlNode;
}
}
public class WhereSqlNode extends TrimSqlNode {
private static List<String> prefixList = Arrays.asList("AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");
public WhereSqlNode(Configuration configuration, SqlNode contents) {
super(configuration, contents, "WHERE", prefixList, null, null);
}
}
单个SQL语句片段是-leaf, 多个SQL语句片段-Composite
总结
组合模式本质:统一叶子对象与组合对象,树状结构设计就是经典。