关于设计模式:设计模式第九篇组合模式解决层级关系结构问题

9次阅读

共计 6068 个字符,预计需要花费 16 分钟才能阅读完成。

阐明:此文章两个示例中,公司构造的示例思路来自于《大话设计模式》,内容以及代码通过了肯定批改,尊重且保护作者版权所有,特此申明。

一 引言

在生活中经常会见到一些具备层级关系的构造,例如学生时代的【大学 - 学院 - 业余】之间的关系就是这样,同样还有例如【总公司 - 分公司 / 部门】、【书包 - 书】,软件开发中也是啊,【文件夹 - 文件】、【容器 - 组件】

然而其实能够发现其共性,都是大范畴包含小范畴这样的模式,例如每一个学院上面都有不同的业余,例如计算机学院下的软件工程业余,咱们能够将其称之为“整体 - 局部”的模式

如果咱们依照最简略能想到的形式,或者就是多层继承,例如 XXX 大学,上面计算机学院等多个学院去继承 XXX 大学,而软件工程,计算机科学与技术等业余又去继承了计算机学院

年轻人,这好吗,这不好。

这种形式其实是依照组织的规模大小来进行划分的,但咱们从理论登程,除了其规模,咱们更偏向于展现其组成构造,例如计算机学院下有多个业余,同时对其进行肯定的保护业务,例如更新遍历等

而应用明天要讲的组合模式就能够实现咱们的想法

二 分公司不就是一部门吗

这个案例的需要是这样的,一家在全国都有分销机构的大公司,想要在全国的分公司中一起应用同一套办公管理系统,例如在北京由总部,全国几大城市有分公司,还有几个省会有办事处。要求总公司的人力资源部没财务部等办公治理性能在所有分公司和办事处都须要有,该如何办?

(一) 剖析

首先捋一下总公司,分公司,办事处,以及各自所属几个部门的关系

依据图能够看出,北京公司总部是最高级别的,其领有两个最间接的部门,即:人力资源部和财务部,而分公司其实和这几个部门是属于同一级的,而人力资源部,财务部这两个治理性能又能够复用于分公司。办事处这个级别在上面一层,也是如此。

剖析完这个图,其实如何去做曾经有了一个简略的思路了,简略的平行治理必定是不适合的,所以咱们将其做成一个树状构造,这样保护治理起来也会十分不便

北京总公司就好比这个根节点,其上司分公司也就是这棵树的分支,像办事处就是更小的分支,无论是总公司还是分公司,亦或办事处的相干职能部门(如财务部)都没有分支了,所以也就是叶子节点

间接解说例子或者会有一些不明朗各个公司,部门之间的关系,咱们先通过演示最简略的组合模式来铺垫一下,下一个点,再来实现下面的公司例子

(二) 组合模式

(1) 什么是组合模式

定义:组合模式有时又叫作“整体 - 局部”模式,它是一种将对象组合成树状的层次结构的模式,用来示意“整体 - 局部”的关系,使用户对单个对象和组合对象具备统一的拜访性

(2) 结构图

(3) 简略代码

Component 为组合中的树枝以及树叶对象申明公共接口

public abstract class Component {
    protected String name;

    public String getName() {return name;}

    public void setName(String name) {this.name = name;}
    // 增加部件
    public abstract void add(Component component);

    // 删除部件
    public abstract void remove(Component component);

    // 遍历所有子部件内容
    public abstract void traverseChild();}

Leaf 即示意叶节点,起没有子节点,所有 add 和 remove 办法均没有具体业务,只是抛一个异样,只单纯实现遍历办法

public class Leaf extends Component{
    @Override
    public void add(Component component) {throw new UnsupportedOperationException();
    }

    @Override
    public void remove(Component component) {throw new UnsupportedOperationException();
    }

    @Override
    public void traverseChild() {System.out.println("执行:" + super.getName());
    }
}

Composite 代表树枝,用来存储子部件,次要作用来存储和治理子部件

public class Composite extends Component {

    // 用来保留组合的部件
    List<Component> list = new ArrayList<Component>();

    @Override
    public void add(Component component) {list.add(component);
    }

    @Override
    public void remove(Component component) {list.remove(component);
    }

    @Override
    public void traverseChild() {System.out.println("执行:" + name);
        for (Component c : list) {c.traverseChild();
        }
    }
}

测试一下

public class Test {public static void main(String[] args) {
        // 设置根节点
        Composite root = new Composite();
        root.name = "根节点";

        // 增加叶子节点
        Leaf leafA = new Leaf();
        leafA.setName("叶子节点 A");
        Leaf leafB = new Leaf();
        leafB.setName("叶子节点 B");
        // 将 A 和 B 增加到 root 下
        root.add(leafA);
        root.add(leafB);
        // 遍历
        root.traverseChild();}
}

后果:

执行:根节点
执行:叶子节点 A
执行:叶子节点 B

(4) 通明形式和平安形式

在下面的代码中,咱们在形象的 Component 中定义了 add 和 remove 办法

// 增加部件
public abstract void add(Component component);
// 删除部件
public abstract void remove(Component component);

这也就意味着,即便在叶子节点 Leaf 类中也须要实现 add 以及 remove 办法,(这里咱们做了抛异样解决,还能够抉择空实现)其实这种形式就叫做通明形式,也就是在 Component 中申明所有用来治理子对象的办法,其中包含 add 以及 remove 办法,这样凡是继承或者实现这个类的子类都具备了 这些办法,这样的行为益处在于,使得叶节点,枝节点这一件具备了完全一致的行为接口,毛病就是,叶节点中的空实现等并无意义

而平安形式就是不在 Component 中申明 add 以及 remove 办法,而是在 Composite 申明所有用来治理子类对象的办法,这样就不会有方才的问题了,其毛病是叶节点,枝节点不再具备雷同的构造,客户端调用须要减少一些判断

(5) 优缺点

长处:

  • 相似例子中职能部门这种根本对象以及分公司办事处等组合对象之间能够组合成更简单的组合对象,而组合对象又能够被组合
  • 客户端代码能够统一地解决单个对象和组合对象,毋庸关怀本人解决的是单个对象,还是组合对象,客户端调用不便
  • 组合体中退出新内容,不须要批改源代码,满足开闭准则

毛病:

  • 设计较简单,类与类之间的档次关系须要捋分明

(三) 公司示例代码实现

上面咱们再联合下面具体的例子来利用一下组合模式(通明形式)

公司的抽象类,相当于下面的 Component

/**
 * 公司抽象类
 */
public abstract class Company {
    protected String name;

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

    // 减少
    public abstract void add(Company company);

    // 删除
    public abstract void remove(Company company);

    // 显示
    public abstract void display(int depth);

    // 履行职责
    public abstract void lineOfDuty();}

具体公司类,相当于 Composite,为了在控制台输入时能看出其层级构造,我依据以后节点的深度,减少了一下等于符号 MyStringUtil.repeatString("=", depth) 就是本人封装了一个办法,依据 depth 的值来屡次输入一个字符串

/**
 * 具体公司类,树枝节点
 */
public class ConcreteCompany extends Company {
    // 用来存储其下厨枝节点和叶节点
    private List<Company> children = new ArrayList<>();

    public ConcreteCompany(String name) {super(name);
    }

    @Override
    public void add(Company company) {children.add(company);
    }

    @Override
    public void remove(Company company) {children.remove(company);
    }

    @Override
    public void display(int depth) {
        // 显示其枝节点名称,并对其上级进行遍历
        System.out.println(MyStringUtil.repeatString("=", depth) + name);
        for (Company c : children) {c.display(depth + 4);
        }
    }

    @Override
    public void lineOfDuty() {
        // 遍历每一个孩子的节点
        for (Company c : children) {c.lineOfDuty();
        }
    }
}

下面用到的工具类就这样

public class MyStringUtil {public static String repeatString(String repeatStr, int repeatNum) {StringBuilder stringBuilder = new StringBuilder();
        while (repeatNum-- > 0) {stringBuilder.append(repeatStr);
        }
        return stringBuilder.toString();}
}

上面是两个具体的部门,也就是叶子节点

/**
 * 人力资源部
 */
public class HRDepartment extends Company {public HRDepartment(String name) {super(name);
    }

    @Override
    public void add(Company company) {throw new UnsupportedOperationException();
    }

    @Override
    public void remove(Company company) {throw new UnsupportedOperationException();
    }

    @Override
    public void display(int depth) {

        // 显示其枝节点名称,并对其上级进行遍历
        System.out.println(MyStringUtil.repeatString("=", depth) + name);
    }

    @Override
    public void lineOfDuty() {System.out.println("【" + name + "】员工培训治理");
    }
}
/**
 * 财务部
 */
public class FinanceDepartment extends Company {public FinanceDepartment(String name) {super(name);
    }

    @Override
    public void add(Company company) {throw new UnsupportedOperationException();
    }

    @Override
    public void remove(Company company) {throw new UnsupportedOperationException();
    }

    @Override
    public void display(int depth) {


        // 显示其枝节点名称,并对其上级进行遍历
        System.out.println(MyStringUtil.repeatString("=", depth) + name);
    }


    @Override
    public void lineOfDuty() {System.out.println("【" + name + "】公司财务收支治理");
    }
}

测试类,咱们别离创立了 root、comp、comp1 等多个 ConcreteCompany,通过 add 办法先给每局部都增加了这几个职能部门,在通过像 root.add(comp); 这样的代码示意了其层级

public class Test {public static void main(String[] args) {
        // 创立根节点
        ConcreteCompany root = new ConcreteCompany("北京总公司");
        root.add(new HRDepartment("总公司人力资源部"));
        root.add(new FinanceDepartment("总公司财务部"));

        ConcreteCompany comp = new ConcreteCompany("上海华东分公司");
        comp.add(new HRDepartment("华东分公司人力资源部"));
        comp.add(new FinanceDepartment("华东分公司财务部"));

        root.add(comp);
        
        ConcreteCompany comp = new ConcreteCompany("南京办事处");
        comp1.add(new HRDepartment("南京办事处人力资源部"));
        comp1.add(new FinanceDepartment("南京办事处财务部"));
        comp.add(comp1);

        System.out.println("结构图:");
        root.display(1);

        System.out.println("职责:");
        root.lineOfDuty();}
}

运行后果:

结构图:
= 北京总公司
===== 总公司人力资源部
===== 总公司财务部
===== 上海华东分公司
========= 华东分公司人力资源部
========= 华东分公司财务部
========= 南京办事处
============= 南京办事处人力资源部
============= 南京办事处财务部
职责:【总公司人力资源部】员工培训治理【总公司财务部】公司财务收支治理【华东分公司人力资源部】员工培训治理【华东分公司财务部】公司财务收支治理【南京办事处人力资源部】员工培训治理【南京办事处财务部】公司财务收支治理
正文完
 0