浅谈设计模式-组合模式

书接上回,本篇讲一下结构型模式-组合设计模式

组合设计模式

定义: 将对象组合成树状结构以表示 “部分-整体” 的层次结构。组合模式使客户端对单个对象和组合对象保持一致的方式处理

这里需要解释几个概念,单个对象与组合对象,部分与整体:

部分与整体:这里理解要区分语境,不同语境下部分与整体表示意思都有一定差异,但都有包含与被包含的意思的。组合模式的部分与整体表示树状接口中叶子节点(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

总结

组合模式本质:统一叶子对象与组合对象,树状结构设计就是经典。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浪飞yes

我对钱没兴趣~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值