设计模式——1_2 组合(Composite)

君子和而不同,小人同而不和

——《论语》

定义

将对象组合成树状结构以表示“部分-整体”的层次结构。组合模式使得用户对单体对象和组合对象的使用具有一致性


图纸

UML



一个例子:折算产品的成本

工厂的一个产品的成本应该怎么算呢?

我尽量举个简单的例子,准备好了吗?例子开始了:


假定现有 工厂甲 ,他对于产品的成本有如下计算公式:

产品成本 = 工艺成本 + 上一级所有半成品或原材料成本和 产品成本 = 工艺成本 + 上一级所有半成品或原材料成本和 产品成本=工艺成本+上一级所有半成品或原材料成本和

工艺成本可以简单理解成开机器所需要的各项成本,比如 定向维护费用电费耗材以及 工人的工资

关键是这个:上一级所有半成品或原材料成本和。这个要怎么理解?


我们假定一个产品他一定是由材料加工后得到的,但并不是所有的材料都是直接从外部购买的原材料,也有可能是其他产线上生产出来的半成品,就像这样:

在这里插入图片描述

bom

像这种把一个成品的构成解析而形成的物料清单,行话管这个叫bom,或者工厂bom

无论如何,先把物料的bean类创建出来,于是我们有了这样的类树:

在这里插入图片描述

那你会问了,为什么不在 Product 类里面添加一个 List<Parts> 这样的内容来存放这个产品的下一级构成呢?这样的话在调用的时候一个递归不就全出来了吗?

想象很美好,但是这是不合理的

除了构成元素以外,bom记录 还要存储更多的内容,比如是谁维护的这条记录,什么时候维护的,这是第几个版本的构成等等。而且没有人规定每种构成的用量一定是1,A成品可能用到了N个,甚至N.几个B原料,用这种方式是很难表示和计算的


BomMessage

所以我们需要一个 专门用来表示成品构成 的工具类簇,这个类簇可以同时协同 MaterialProduct 的对象,把他们组合在一起。而且他应该给我们提供诸如 getCost() 这样的方法方便我们 直接获取 当前成品计算后的成本,就像这样:

在这里插入图片描述

//一个BomMessage的对象可以用于表示一组上下级关系
public abstract class BomMessage {
   

    //当前构成中的上级部件
    private final Product parent;

    private final int version;

    protected BomMessage(Product parent, int version) {
   
        this.parent = parent;
        this.version = version;
    }

    public abstract double getCost();

    public Product getParent() {
   
        return parent;
    }

    public int getVersion() {
   
        return version;
    }
}

//这个类用于表示到 产品/半成品-原材料 的关系
public class BomItem extends BomMessage {
   

    //当前构成中的下级部件
    private Material parts;
    //用量
    private double dosage;

    private BomItem(Product parent, int version) {
   
        super(parent, version);
    }

    //静态工厂创建 只接收原材料作为parts
    public static BomItem createBomItem(Product parent, Material material, int version, double dosage) {
   
        BomItem bomItem = new BomItem(parent, version);
        bomItem.parts = material;
        bomItem.dosage = dosage;
        return bomItem;
    }

    @Override
    public double getCost() {
   
        return parts.getCost() * dosage;
    }
    
    ……
}

//这个类用于表示 产品/半成品-产品/半成品-下级列表 之间的关系
public class BomList extends BomMessage {
   

    //当前组件
    private Product parts;
    //当前部件的构成列表
    private final List<BomMessage> childList;

    private BomList(Product parent, int version) {
   
        super(parent, version);
        this.childList = new ArrayList<>();
    }

    //静态工厂方法构筑,只接收产品作为Parts
    public static BomList createBomList(Product parent, Product product, int version) {
   
        BomList bomList = new BomList(parent, version);
        bomList.parts = product;
        return bomList;
    }

    @Override
    public double getCost() {
   
        double cost = 0d;

        for (BomMessage child : childList) {
   
            cost += child.getCost();
        }

        return cost;
    }

    public void addChild(BomMessage child) {
   
        childList.add(child);
    }

    public List<BomMessage> getChildList() {
   
        return new ArrayList<>(childList);//复制,防止不可控修改
    }
    
    ……
}

我们定义了一个抽象类 BomMessage,同时还为他创建了两个分别对应不同类型产品的子类:BomItem 对应 MaterBomList 对应 Product。现在我们回顾一下最开始的成品W,如果那棵树要用 BomMessage 来表示的话,他应该是这样的:

在这里插入图片描述

那你会问了,就算我创建除了这样一个对象树,实际操作的时候,我还不是要把他们一个一个填充进去?我能操作的也还是最顶端的 BomList-W 对象啊?那我还是要自己去访问树 BomItemBomList 对象啊

是的,因为这里还少一级抽象,我们还需要一个类似 BomTree 这样的类对象来帮助我们集成对这颗树的操作,就像这样:


BomTree

在这里插入图片描述

public class BomTree {
   

    private BomMessage root;

    public Double getCost(){
   
        if(root == null){
   
            return null;
        }else {
   
            return root.getCost();
        }
    }

    public boolean addBomMessage(BomMessage bomMessage) {
   
        if (root == null) {
   
            root = bomMessage;
            return true;
        } else {
   
            return addBomMessage(root, bomMessage);
        }
    }

    private boolean addBomMessage(BomMessage parent, BomMessage bomMessage) {
   
        if (parent instanceof BomList) {
   
            BomList bomList = (BomList) parent;
            if (bomList.getParts().equals(bomMessage.getParent())) {
   
                //找到了对应的层级
                bomList.addChild(bomMessage);//加入
                return true;
            } else {
   
                //没找到,往下探寻
                for (BomMessage item : bomList.getChildList()) {
   
                    if(addBomMessage(item, bomMessage)){
   //递归
                        return true;
                    }
                }
            }
        }

        return false;
    }
    
    ……
}

在实际应用中,你应该要有一个业务方法专门用于从 数据库服务器 或者其他什么地方获取到 BomTree 对象的方法

BomTree 对象通过内部存储的 root 变量和 BomMessage 树去交互,就像这样:

在这里插入图片描述

至此,我们实现了对 BomMessage 树的 封装,除特地构筑方法外,client 不会再和他打交道


这种封装的好处是很明显的:

  • 如果要拓展 BomMessage 的类树,那么只需要改动 BomTree 中和他相关的部分就可以了
  • 上例中只是展示了非常简单的功能,在有 BomTree 存在的情况下,我可以非常简单的实现诸如 统计某产品使用了多少种类的原材料某产品具体使用的工艺 等等业务,实现这些业务的时候只需要修改 BomTree 就可以了

总而言之,BomTree 实现了对整个 BomMessage 类树中的对象的协同处理,让他们看起来像一个整体一样去行动

而这正是一个标准的组合模式实现


上面这个例子其实已经简化到有点理想化了,真实生产环境中可能还有币种问题,可回收物损耗计算,直进直出等等很诡异的问题。如果您真的接触到要构筑bom的业务,这个例子只能算是抛砖引玉吧,我相信您一定能做出比本例优秀得多的实现

写在后面的碎碎念

职责分离

树状结构中的节点有一个特点:

每个节点对象自己管理自己的上下文,对外暴露出一个用于操作上下文的对象(上例的BomTree中的root),每个组合内的对象只关注自己上下文的行为,通过递归等方式访问整体

这是一种职责分离思想的体现(最小依赖原则),这种思想在设计模式中很常见,包括之前讲过的 适配器,之后会出现的 装饰器职责链 等等……

依赖越小,也就是耦合越少,就意味着出现修改时动工的地方越小;别小看这点,这会让你少掉很多头发的


非树状的组合模式

狭义上来说组合模式是指那个用来“代理”树状对象结构的那个类,比如说上例中的 BomTree

但是广义上组合模式不一定要用树状结构来展示,他也可以是对多个线性结构的集成表示,比如说:

现在我需要 对象X,而这个对象需要从 A池/B池 中提取出来。如果不集成他,那么每次 client 获取 对象X 的时候都必须同时访问 A池B池 ,如果后续有拓展,改起来就是大工程

这时候你就可以创建一个 对象池操作类 ,这个操作类内的各个方法都会同时对 A池B池 进行操作;这也是一种组合模式思想的体现。不要太拘泥于定义,设计模式更多的是给你提供这种思想


组合和迭代器

组合和迭代器之间是一对亲密无间的战友,他们常常是一起使用的

组合常常是需要遍历访问树结构的,而迭代器可以让调用者在不知晓内部结构的情况下实现对集合内部对象的访问。这就很好的解决了对这种结构复杂的元素内部的访问问题

本专栏的后续文章中,迭代器也会登场,敬请期待



姑妄言之

这个世界是很大的,而我们又太渺小。所以无论你怎么看待这个世界,都会有人和你截然相反,但是当我们拔高维度,又会发现其实我们都组合在某个整体当中。所以我们只能接受这样的差异,就像 BomTree 接受 BomListBomItem 的差异一样

可能这就是孔子说的和而不同吧。我不求你和我一样,所以也请你别要求我和你一样,我们和就可以了

连海边的沙粒都形形色色,又何况芸芸众生

相关推荐

  1. 设计模式10composite 组合模式

    2024-01-09 22:56:01       13 阅读
  2. 设计模式——组合模式Composite

    2024-01-09 22:56:01       11 阅读
  3. 设计模式--组合模式Composite Pattern)

    2024-01-09 22:56:01       29 阅读
  4. 设计模式组合模式Composite Pattern)

    2024-01-09 22:56:01       15 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-09 22:56:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-09 22:56:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-09 22:56:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-09 22:56:01       20 阅读

热门阅读

  1. 【前端面试题】每日一个前端面试专题

    2024-01-09 22:56:01       47 阅读
  2. MySQL中的索引:深入理解与案例解析

    2024-01-09 22:56:01       34 阅读
  3. C++,智能指针详解(面试)

    2024-01-09 22:56:01       27 阅读
  4. Gitee

    Gitee

    2024-01-09 22:56:01      38 阅读
  5. insert into select简单数据迁移-postgresql

    2024-01-09 22:56:01       32 阅读
  6. C++-nullptr-类型推导

    2024-01-09 22:56:01       38 阅读
  7. C++推箱子游戏开发

    2024-01-09 22:56:01       42 阅读
  8. Fixed win size sliding window

    2024-01-09 22:56:01       37 阅读
  9. 【Vue】项目使用px2rem

    2024-01-09 22:56:01       40 阅读
  10. React使用Valtio的hook实现响应式状态管理

    2024-01-09 22:56:01       34 阅读