设计模式之——组合模式 Composite Pattern

引例

在介绍之前我们先来看一个例子,是关于公司的人事管理系统的:

人事管理
这是一个典型的树形结构,那么我们怎么才能用代码表示他呢?

分析:

总共有两种不同性质的节点:

  • 有分支的节点:
    • 根节点:总经理
    • 树枝节点:研发部经理
  • 无分支的节点:
    • 叶子节点:开发人员

那这么说,定义三个类不就行了,如下图:

组织架构类图
那我们就按照这个类图实现一下代码:

首先是 根节点接口

/**
 * 根节点接口
 *
 * @author wang suo
 * @version 1.0
 * @date 2020/12/21 0021 22:36
 */
public interface IRoot {
    /**
     * 得到总经理的信息
     *
     * @return 返回信息
     */
    String getInfo();

    /**
     * 总经理下边要有小兵-要能增加小兵-比如研发经理-这是树枝节点
     *
     * @param iBranch 有分支的节点
     */
    void add(IBranch iBranch);

    /**
     * 增加树叶节点的方法
     *
     * @param iLeaf 叶子节点
     */
    void add(ILeaf iLeaf);

    /**
     * 还要能进行遍历-不可能总经理不知道他收下有多少人
     *
     * @return 返回所有手下的[集合]
     */
    List getSubordinateInfo();
}

根节点接口的 实现类 ,就是我们这里的总经理:

/**
 * 根节点的实现类
 *
 * @Author wang suo
 * @Date 2020/12/22 0022 11:03
 * @Version 1.0
 */
public class Root implements IRoot {
    /**
     * 保存根节点下的所有树枝节点和树叶节点
     */
    private List<Object> subordinateList = new ArrayList<>();

    /**
     * 根节点的名称
     */
    private String name = "";

    /**
     * 根节点的职位
     */
    private String position = "";

    /**
     * 根节点的薪水
     */
    private int salary = 0;

    public Root(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    @Override
    public String getInfo() {
        String info = "";
        info = "名称: " + this.name;
        info = info + "\t职位: " + this.position;
        info = info + "\t薪水: " + this.salary;
        return info;
    }

    /**
     * 增加树叶节点
     *
     * @param iBranch 有分支的节点
     */
    @Override
    public void add(IBranch iBranch) {
        subordinateList.add(iBranch);
    }

    /**
     * 增加叶子节点-直属于总经理的-比如秘书
     *
     * @param iLeaf 叶子节点
     */
    @Override
    public void add(ILeaf iLeaf) {
        subordinateList.add(iLeaf);
    }

    @Override
    public List getSubordinateInfo() {
        return this.subordinateList;
    }
}

有分支的节点 接口:

/**
 * 有分支的节点接口
 *
 * @author wang suo
 * @version 1.0
 * @date 2020/12/21 0021 22:38
 */
public interface IBranch {
    /**
     * 得到自己的信息
     *
     * @return 返回信息
     */
    String getInfo();

    /**
     * 增加数据节点-例如研发部下面的研发一组
     *
     * @param iBranch 有分支的节点
     */
    void add(IBranch iBranch);

    /**
     * 增加树叶节点的方法
     *
     * @param iLeaf 叶子节点
     */
    void add(ILeaf iLeaf);

    /**
     * 获得下级信息
     *
     * @return 返回所有手下的[集合]
     */
    List getSubordinateInfo();
}

有分支的节点实现:

/**
 * 分支的节点实现
 *
 * @Author wang suo
 * @Date 2020/12/22 0022 11:16
 * @Version 1.0
 */
public class Branch implements IBranch {
    /**
     * 存储子节点的信息
     */
    private List<Object> subordinateList = new ArrayList<>();

    private String name = "";

    private String position = "";

    private int salary = 0;

    public Branch(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    @Override
    public String getInfo() {
        String info = "";
        info = "名称: " + this.name;
        info = info + "\t职位: " + this.position;
        info = info + "\t薪水: " + this.salary;
        return info;
    }

    @Override
    public void add(IBranch iBranch) {
        this.subordinateList.add(iBranch);
    }

    @Override
    public void add(ILeaf iLeaf) {
        this.subordinateList.add(iLeaf);
    }

    @Override
    public List getSubordinateInfo() {
        return this.subordinateList;
    }
}

叶子节点 的接口:

/**
 * 叶子节点的接口
 *
 * @author wang suo
 * @version 1.0
 * @date 2020/12/21 0021 22:38
 */
public interface ILeaf {
    /**
     * 获取信息
     *
     * @return 返回信息
     */
    String getInfo();
}

叶子节点的实现:

/**
 * 叶子节点的实现
 *
 * @Author wang suo
 * @Date 2020/12/22 0022 11:20
 * @Version 1.0
 */
public class Leaf implements ILeaf {
    /**
     * 叶子叫什么名字
      */
    private String name = "";

    /**
     * 叶子的职位
     */
    private String position = "";

    /**
     * 叶子的薪水
     */
    private int salary = 0;

    public Leaf(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    @Override
    public String getInfo() {
        String info = "";
        info = "名称: " + this.name;
        info = info + "\t职位: " + this.position;
        info = info + "\t薪水: " + this.salary;
        return info;
    }
}

所有的根节点,树枝节点和叶子节点都已经实现了,随后我们实现一下 环境类

/**
 * 场景类
 *
 * @Author wang suo
 * @Date 2020/12/22 0022 11:22
 * @Version 1.0
 */
public class Client {
    public static void main(String[] args) {
        // 首先产生一个根节点
        IRoot ceo = new Root("王大麻子", "总经理", 100000);
        // 产生 3 个部门经理
        Branch developDep = new Branch("刘大瘸子", "研发部门经理", 10000);
        Branch salesDep = new Branch("马二拐子", "销售部门经理", 20000);
        Branch financeDep = new Branch("赵三驼子", "财务部门经理", 30000);
        // 开发部门的3个小组长
        IBranch firstDevGroup = new Branch("杨三乜斜", "开发一组组长", 5000);
        IBranch secondDevGroup = new Branch("吴大棒槌", "开发二组组长", 6000);

        // 剩下的就是我们这些小兵了,就是路人甲,路人乙
        ILeaf a = new Leaf("a", "开发人员", 2000);
        ILeaf b = new Leaf("b", "开发人员", 2000);
        ILeaf c = new Leaf("c", "开发人员", 2000);
        ILeaf d = new Leaf("d", "开发人员", 2000);
        ILeaf e = new Leaf("e", "开发人员", 2000);
        ILeaf f = new Leaf("f", "开发人员", 2000);
        ILeaf g = new Leaf("g", "开发人员", 2000);
        ILeaf h = new Leaf("h", "销售人员", 5000);
        ILeaf i = new Leaf("i", "销售人员", 4000);
        ILeaf j = new Leaf("j", "财务人员", 5000);
        ILeaf k = new Leaf("k", "CEO秘书", 8000);
        ILeaf zhengLaoLiu = new Leaf("郑老六", "研发部副总", 20000);

        // 该产生的人都产生出来了,然后我们怎么组装这棵树
        // 首先是定义总经理下有三个部门经理
        ceo.add(developDep);
        ceo.add(salesDep);
        ceo.add(financeDep);
        // 总经理下还有一个秘书
        ceo.add(k);

        // 定义研发部门下的结构
        developDep.add(firstDevGroup);
        developDep.add(secondDevGroup);
        // 研发部经理下还有一个副总
        developDep.add(zhengLaoLiu);

        // 看看开发两个开发小组下有什么
        firstDevGroup.add(a);
        firstDevGroup.add(b);
        firstDevGroup.add(c);
        secondDevGroup.add(d);
        secondDevGroup.add(e);
        secondDevGroup.add(f);

        // 再看销售部下的人员情况
        salesDep.add(h);
        salesDep.add(i);

        //最后一个财务
        financeDep.add(j);

        //树状结构写完毕,然后我们打印出来
        System.out.println(ceo.getInfo());

        //打印出来整个树形
        getAllSubordinateInfo(ceo.getSubordinateInfo());
    }

    /**
     * 遍历并打印整个树
     *
     * @param subordinateList 树或子树
     */
    private static void getAllSubordinateInfo(List subordinateList) {
        for (Object o : subordinateList) {
            if (o instanceof Leaf) {
                System.out.println(((Leaf) o).getInfo());
            } else {
                IBranch branch = (IBranch) o;
                System.out.println(branch.getInfo());
                // 递归调用
                getAllSubordinateInfo(branch.getSubordinateInfo());
            }
        }
    }
}

执行结果:

名称: 王大麻子	职位: 总经理			薪水: 100000
名称: k			职位: CEO秘书		薪水: 8000
名称: 刘大瘸子	职位: 研发部门经理		薪水: 10000
名称: 郑老六		职位: 研发部副经理		薪水: 20000
名称: 杨三乜斜	职位: 开发一组组长		薪水: 5000
名称: a			职位: 开发人员		薪水: 2000
名称: b			职位: 开发人员		薪水: 2000
名称: c			职位: 开发人员		薪水: 2000
名称: 吴大棒槌	职位: 开发二组组长		薪水: 6000
名称: d			职位: 开发人员		薪水: 2000
名称: e			职位: 开发人员		薪水: 2000
名称: f			职位: 开发人员		薪水: 2000
名称: 马二拐子	职位: 销售部门经理		薪水: 20000
名称: h			职位: 销售人员		薪水: 5000
名称: i			职位: 销售人员		薪水: 4000
名称: 赵三驼子	职位: 财务部经理		薪水: 30000
名称: j			职位: 财务人员		薪水: 5000

存在的问题

现在我们来分析一下上面的代码和架构出现的问题:

  • 我们发现 Root 类和 Branch 类几乎一摸一样,所以这两个可以合并;
  • 另外每个节点都有 getInfo 方法,所以我们可以提取出一个抽象类;

修改之后的类图

精简的类图
精简之后的代码显得格外简洁,而且环境类只需要直接和抽象类 Corp 关联就可以了,代码如下:

抽象 公司职员类

/**
 * 抽象公司职员类
 *
 * @Author wang suo
 * @Date 2020/12/22 0022 19:11
 * @Version 1.0
 */
public abstract class AbstractCorp {
    /**
     * 公司每个人都有名称
     */
    private String name = "";
    /**
     * 公司每个人都有职位
     */
    private String position = "";
    /**
     * 公司每个人都有薪水
     */
    private int salary = 0;

    /**
     * 构造方法
     *
     * @param name     名称
     * @param position 职位
     * @param salary   薪水
     */
    public AbstractCorp(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    /**
     * 获取员工信息
     *
     * @return 信息
     */
    public String getInfo() {
        String info = "";
        info = "名称: " + this.name;
        info = info + "\t职位: " + this.position;
        info = info + "\t薪水: " + this.salary;
        return info;
    }
}

树枝节点

/**
 * 树枝节点
 *
 * @Author wang suo
 * @Date 2020/12/22 0022 20:19
 * @Version 1.0
 */
public class Branch extends AbstractCorp {
    /**
     * 领导下边有哪些下级领导和小兵
     */
    private List<AbstractCorp> subordinateList = new ArrayList<>();

    /**
     * 构造方法
     *
     * @param name     名称
     * @param position 职位
     * @param salary   薪水
     */
    public Branch(String name, String position, int salary) {
        super(name, position, salary);
    }

    /**
     * 增加一个下属
     *
     * @param corp 树枝
     */
    public void addSubordinate(AbstractCorp corp) {
        subordinateList.add(corp);
    }

    /**
     * 我有哪些下属
     *
     * @return 返回下属
     */
    public List<AbstractCorp> getSubordinate() {
        return subordinateList;
    }
}

树叶节点:

/**
 * 叶子
 *
 * @Author wang suo
 * @Date 2020/12/22 0022 20:29
 * @Version 1.0
 */
public class Leaf extends AbstractCorp {
    /**
     * 构造方法
     *
     * @param name     名称
     * @param position 职位
     * @param salary   薪水
     */
    public Leaf(String name, String position, int salary) {
        super(name, position, salary);
    }
}

修改之后的场景类:

/**
 * 修改之后的场景类
 *
 * @Author wang suo
 * @Date 2020/12/22 0022 20:26
 * @Version 1.0
 */
public class Client {

    public static void main(String[] args) {
        //首先是组装一个组织结构出来
        Branch ceo = compositeCorpTree();

        //首先把CEO的信息打印出来:
        System.out.println(ceo.getInfo());

        //然后是所有员工信息
        System.out.println(getTreeInfo(ceo));
    }

    //把整个树组装出来
    public static Branch compositeCorpTree() {
        //首先产生总经理CEO
        Branch root = new Branch("王大麻子", "总经理", 100000);
        //把三个部门经理产生出来
        Branch developDep = new Branch("刘大瘸子", "研发部门经理", 10000);
        Branch salesDep = new Branch("马二拐子", "销售部门经理", 20000);
        Branch financeDep = new Branch("赵三驼子", "财务部经理", 30000);

        //再把三个小组长产生出来
        Branch firstDevGroup = new Branch("杨三乜斜", "开发一组组长", 5000);
        Branch secondDevGroup = new Branch("吴大棒槌", "开发二组组长", 6000);

        //把所有的小兵都产生出来
        Leaf a = new Leaf("a", "开发人员", 2000);
        Leaf b = new Leaf("b", "开发人员", 2000);
        Leaf c = new Leaf("c", "开发人员", 2000);
        Leaf d = new Leaf("d", "开发人员", 2000);
        Leaf e = new Leaf("e", "开发人员", 2000);
        Leaf f = new Leaf("f", "开发人员", 2000);
        Leaf g = new Leaf("g", "开发人员", 2000);
        Leaf h = new Leaf("h", "销售人员", 5000);
        Leaf i = new Leaf("i", "销售人员", 4000);
        Leaf j = new Leaf("j", "财务人员", 5000);
        Leaf k = new Leaf("k", "CEO秘书", 8000);
        Leaf zhengLaoLiu = new Leaf("郑老六", "研发部副经理", 20000);

        //开始组装
        //CEO下有三个部门经理和一个秘书
        root.addSubordinate(k);
        root.addSubordinate(developDep);
        root.addSubordinate(salesDep);
        root.addSubordinate(financeDep);

        //研发部经理
        developDep.addSubordinate(zhengLaoLiu);
        developDep.addSubordinate(firstDevGroup);
        developDep.addSubordinate(secondDevGroup);

        //看看开发两个开发小组下有什么
        firstDevGroup.addSubordinate(a);
        firstDevGroup.addSubordinate(b);
        firstDevGroup.addSubordinate(c);
        secondDevGroup.addSubordinate(d);
        secondDevGroup.addSubordinate(e);
        secondDevGroup.addSubordinate(f);

        //再看销售部下的人员情况
        salesDep.addSubordinate(h);
        salesDep.addSubordinate(i);

        //最后一个财务
        financeDep.addSubordinate(j);

        return root;
    }

    public static String getTreeInfo(Branch root) {
        List<AbstractCorp> subordinateList = root.getSubordinate();
        String info = "";
        for (AbstractCorp corp : subordinateList) {
            if (corp instanceof Leaf) {
                info = info + corp.getInfo() + "\n";
            } else {
                // 是个小头目
                info = info + corp.getInfo() + "\n" + getTreeInfo((Branch) corp);
            }
        }
        return info;
    }
}

这就是 组合模式

组合模式

组合模式也叫合成模式,有事又叫做部分-整体模式,主要是用来描述整体和部分之间的关系。

组合模式的通用类图:
组合模式的类图
抽象组件

public abstract class Component {
	
	//个体和整体都具有的共享
	public void doSomething(){
		//编写业务逻辑
	}
}

树枝构件

public class Composite extends Component {
	//构件容器
	private ArrayList<Component> componentArrayList = new ArrayList<Component>();
	
	//增加一个叶子构件或树枝构件
	public void add(Component component){
		this.componentArrayList.add(component);
	}
	
	//删除一个叶子构件或树枝构件
	public void remove(Component component){
		this.componentArrayList.remove(component);
	}
	
	//获得分支下的所有叶子构件和树枝构件
	public ArrayList<Component> getChildren(){
		return this.componentArrayList;
	}
}

树叶构件

public class Leaf extends Component {
	/*
	 * 可以覆写父类方法
	 * public void doSomething(){
	 * 
	 * }
	 */
}

环境类

public class Client {

	public static void main(String[] args) {
		//创建一个根节点
		Composite root = new Composite();
		root.doSomething();
		
		//创建一个树枝构件
		Composite branch = new Composite();
		//创建一个叶子节点
		Leaf leaf = new Leaf();
		
		//建立整体
		root.add(branch);
		branch.add(leaf);		
	}
	
	//通过递归遍历树
	public static void display(Composite root){

		for(Component c:root.getChildren()){
			if(c instanceof Leaf){ //叶子节点
				c.doSomething();
			}else{ //树枝节点
				display((Composite)c);
			}
		}
		
	}
}

我们发现在环境类中直接使用

Composite root = new Composite();

这个了,违反了依赖倒置原则。


我们再来分析一下组合模式的优点和缺点:

  • 优点
    • 高层模块调用简单:局部和整体对调用者来说没有区别;
    • 节点自由增加:非常容易扩展,符合开闭原则;
  • 缺点
    • 违反了依赖倒置原则;

组合模式的两种实现

其实组合模式是有两种实现的,分别是 透明模式安全模式 ,我们上面用到的是安全模式,那么透明模式是怎么样的?

类图如下:

透明模式的通用类图
我们与安全模式对比一下就非常清楚了,透明模式是把所有的方法都放在抽象类中,这样的话对于树叶节点来说是不安全的,因为在运行期间会遇到问题。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

硕子鸽

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值