标准 专业
多元 极客

设计模式实验室(3)——组合模式

什么是组合模式?

组合模式是一种对象结构型模式。

组合模式是组合多个对象形成树形结构以表示具有整体——部分关系的层次结构,组合模式对单个对象(叶子对象)和组合对象(容器对象)的使用具有一致性,所以也可称之为整体——部分(Part-Whole)模式

组合模式在日常coding中也很常见,比如在笔者在后台设计中,遇到过这样的问题,左侧导航栏根据用户权限进行动态修改。左侧导航栏纵深可能会无限大,相当于拥有无限高度的树结构。如果按照常规的写法,我们需要不断进行嵌套循环遍历,如果树的高度是2,那么这个算法写起来可能会比较容易,但是如果树的高度是8,我相信你已经要摔键盘了。但是组合模式可以很方便的解决这个问题,我们可以将菜单按钮注册到菜单中,当菜单进行改变后,调用菜单声明的方法,注册者也会收到通知。

组合模式关键点是定义了一个统一的抽象构建类,它既可以代表叶子对象,保证了对外部调用的一致性,外部调用方便进行统一处理。

核心结构

  • Component。抽象构件,主要负责为叶子构件和容器构件声明公共方法,同时还可以声明为容器添加、获取和删除叶子构件的方法,所以它基本上声明和实现了子类中共有的方法,它既可以是接口,也可以是抽象类。
  • Leaf。叶子构件,表现为叶子对象,它没有子节点,它实现了抽象构件中声明的业务实现,对于那些容器管理方法,可以通过逻辑或者异常进行处理。
  • Composite。容器构件,表现为容器对象,它提供一个集合对象用于存储子节点,对于容器中的子节点,可以通过递归或者迭代调用其业务实现,同时自身也实现了抽象构件中声明的业务实现。

类图展示

组合模式又分为透明组合模式和安全组合模式,我们分别对两中情况做逐一的介绍。

透明组合模式

在透明组合模式中,Component(抽象构件)声明了所有管理对象的方法,但是叶子构件不会拥有子节点,这些方法对叶子构件来说是理应不存在的,所以在每声明一个叶子节点时,都需要对这些方法进行适当的处理。但是在外部调用看来,叶子构件和容器构件所提供的方法是一致的,也就说,对于外部调用来说,是透明的。

根据透明组合模式的定义,我们可以构建透明组合模式的类图。

透明组合模式

安全组合模式

安全组合模式相对于透明组合模式来说更加安全,因为Component(抽象构件)中没有声明任何用于管理对象的方法,而是在Composite(容器构件)中声明并实现这些方法,所以Leaf(叶子构件)中不会存在这些不需要的方法,这种做法是安全的,因为客户端永远不会调用到不适合的方法。

根据安全组合模式的定义,我们可以构建安全组合模式的类图。

安全组合模式

代码分析

在等级分明的企业中,经常会有大大小小的各种部门,各个部门又拥有数量不一的员工,当需要事件通知时,将这种等级架构设计为组合模式可以使行为更加便捷,我们根据两种组合模式分别给出不同的架构设计。

透明组合模式实现

首先,我们需要定义一个抽象构件,也就是通知管理系统:

/**
 * Created by <sunshine> mysunshinedreams@163.com on 2017/1/19 0019.
 * 组合模式——透明组合模式——抽象构件
 */
public abstract class Notification {

    public abstract void notification();

    public abstract Notification getChild(int i);

    public abstract void add(Notification notification);

    public abstract void remove(Notification notification);
}

接着我们定义叶子构件,也就是员工:

/**
 * Created by <sunshine> mysunshinedreams@163.com on 2017/1/19 0019.
 * 组合模式——透明组合模式——叶子构件
 */
public class Ant extends Notification {

    private String name;

    public Ant(String name) {
        this.name = name;
    }

    @Override

    public void notification() {
        System.out.println("notifying employees".concat(this.name).concat(" in transparency"));
    }

    @Override
    public Notification getChild(int i) {
        System.out.println("invalid");
        return null;
    }

    @Override
    public void add(Notification notification) {
        System.out.println("invalid");
    }

    @Override
    public void remove(Notification notification) {
        System.out.println("invalid");
    }
}

接着我们定义一个容器构件,也就是部门:

/**
 * Created by <sunshine> mysunshinedreams@163.com on 2017/1/19 0019.
 * 组合模式——透明组合模式——容器构件
 */
public class Department extends Notification { 

    private String name;

    private List<Notification> antList = new ArrayList<Notification>();

    public Department(String name) {
        this.name = name;
    }

    @Override
    public void notification() {
        System.out.println("Notifying each employees of Department ".concat(this.name).concat(" in transparency"));
        for(Notification notification : antList) {
            notification.notification();
        }
    }

    @Override
    public Notification getChild(int i) {
        return antList.get(i);
    }

    @Override
    public void add(Notification notification) {
        antList.add(notification);
    }

    @Override
    public void remove(Notification notification) {
        antList.remove(notification);
    }
}

安全组合模式

跟透明组合模式一样,定义通知管理系统:

/**
 * Created by <sunshine> mysunshinedreams@163.com on 2017/1/19 0019.
 * 组合模式——安全组合模式——抽象构件
 */
public abstract class Notification {

    public abstract void notification();
}

定义员工:

/**
 * Created by <sunshine> mysunshinedreams@163.com on 2017/1/19 0019.
 * 组合模式——安全组合模式——叶子构件
 */
public class Ant extends Notification {

    private String name;

    public Ant(String name) {

        this.name = name;

    }

    @Override
    public void notification() {
        System.out.println("has notified ".concat(this.name).concat(" safe"));
    }
}

定义部门:

/**
 * Created by <sunshine> mysunshinedreams@163.com on 2017/1/19 0019.
 * 组合模式——安全组合模式——容器构件
 */
public class Department extends Notification {

    private String name;

    private List<Notification> antList;

    public Department(String name) {
        this.name = name;
        antList = new ArrayList<Notification>();
    }

    @Override
    public void notification() {
        System.out.println("Notifying each employee of Department ".concat(this.name).concat(" safe"));
        for (Notification notification : antList) {
            notification.notification();
        }
    }

    public void add(Notification notification) {
        antList.add(notification);
    }

    public void remove(Notification notification) {
        antList.remove(notification);
    }

    public Notification getChild(int i) {
        return antList.get(i);
    }
}

测试用例

为了能形成对比,笔者将测试用例写在了一起。

/**
 * Created by <sunshine> mysunshinedreams@163.com on 2017/1/19 0019.
 * 组合模式测试
 */
public class CombineTest {

    public static void main(String[] args) {
        transparentCombineTest();
        safeCombineTest();
    }

    private static void transparentCombineTest() {
        try {
            Notification employeeOne = new Ant("sunshine_6324");
            Notification employeeTwo = new Ant("sunshine_443");
            Notification employeeThree = new Ant("sunshine_4396");
            Notification department = new Department("Director");
            department.add(employeeOne);
            department.add(employeeTwo);
            department.notification();
            employeeThree.notification();
            department.add(employeeThree);
            department.notification();
            department.remove(employeeOne);
            department.notification();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void safeCombineTest() {
        try {
            com.sunshine.patterns.combine.safety.Ant employeeOne = new com.sunshine.patterns.combine.safety.Ant("sunshine_6324");
            com.sunshine.patterns.combine.safety.Ant employeeTwo = new com.sunshine.patterns.combine.safety.Ant("sunshine_443");
            com.sunshine.patterns.combine.safety.Ant employeeThree = new com.sunshine.patterns.combine.safety.Ant("sunshine_4396");
            com.sunshine.patterns.combine.safety.Department department = new com.sunshine.patterns.combine.safety.Department("Director");
            department.add(employeeOne);
            department.add(employeeTwo);
            department.notification();
            employeeThree.notification();
            department.add(employeeThree);
            department.notification();
            department.remove(employeeOne);
            department.notification();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

模式总结

优点

  • 组合模式可以清楚的定义分层次的复杂对象,让外部调用忽略层次的差异,方便对整个层次结构进行控制。
  • 外部调用不必在意处理的对象是容器对象还是单个对象,简化了代码。
  • 符合开闭原则,扩展容器构件和叶子构件时,只需要实现抽象构件即可。
  • 组合模式为树形结构的面向对象编程提供了一种灵活的解决方案,通过容器构件和叶子构件的递归组合,可以形成复杂的树形结构,但是对树形结构的控制确非常简单。

缺点

  • 组合模式很难对容器构件中,叶子构件的类型进行限制,因为它们都来自于同一个抽象层,在一定程度上增加了系统的复杂性和逻辑性。

适用场景

  • 在群体和单个对象的层次中,希望用一种方式忽略群体和单个对象之间的差异。
  • 树形结构的面向对象编程。
  • 系统中容器构件和叶子对象很明确,并且需要对容器构件和叶子构件进行扩充。
赞(2) 投币

评论 抢沙发

慕勋的实验室慕勋的研究院

码字不容易,路过请投币

支付宝扫一扫

微信扫一扫