设计模式-组合模式

设计模式-组合模式

一般我们提到组合,其实常见有三种意义:

  • 组合优于继承,指进行类的实现时,由于继承会导致类的关系复杂,因此尽量使用组合、委托来替代继承实现
  • UML 中类之间的关系,通常与聚合放在一起考察,组合是强依赖关系,部分不能脱离整体存在(公司-部门,公司不在了,部门也销毁了);而聚合是弱依赖,部分可以脱离整体存在(部门-员工,部门被撤销,员工依然存在)
  • 组合设计模式,一般是用来处理树形结构数据,暴露一个统一的方法。
案例分析

把人口区划分为省市区,如果要计算省的人口,其实是省内所有市的人口和,市的人口是市内所有区的人口和,当然还可以继续往下划分...

如果要统计省的人口,最简单的实现思路就是:

  1. 通过省份 id 找到所有的 市
  2. 遍历市列表,通过 市 id 找到所有的区
  3. 挨个累加区的人口总数

当然这只是逻辑实现,如果真的在市区列表中 for 循环查找数据库,可能对数据库压力比较大,可以先查询组装为一个 Map<String,List<>>,这样只需要一次查询即可。

那利用组合模式该怎么实现呢?

  1. 定义一个计算人口数量的接口
java 复制代码
package com.xsdl;

public interface PopulationNode {

    int computePopulation();

}
  1. 定义省、市、区类分别实现这个接口
java 复制代码
package com.xsdl;

import lombok.ToString;

import java.util.ArrayList;
import java.util.List;

@ToString
public class Province implements PopulationNode {

    private String name;

    private List<PopulationNode> cityList = new ArrayList<>();

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

    public void addCity(City city) {
        cityList.add(city);
    }

    @Override
    public int computePopulation() {
        return cityList.stream().mapToInt(item -> item.computePopulation()).sum();
    }

}

注意第 13 行,由于 市 也同样实现了计算人口数量的接口,因此这里可以利用多态(接口引用指向实现对象)接收市。

java 复制代码
package com.xsdl;

import lombok.ToString;

import java.util.ArrayList;
import java.util.List;

@ToString
public class City implements PopulationNode {

    private String name;

    private List<PopulationNode> districtList = new ArrayList<>();

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

    public void addDistrict(District district) {
        districtList.add(district);
    }

    @Override
    public int computePopulation() {
        return districtList.stream().mapToInt(item -> item.computePopulation()).sum();
    }

}

注意第 13 行,由于 区 也同样实现了计算人口数量的接口,因此这里可以利用多态(接口引用指向实现对象)接收区。

java 复制代码
package com.xsdl;

import lombok.ToString;

@ToString
public class District implements PopulationNode {

    private String name;

    private int population;

    public District(String name, int population) {
        this.name = name;
        this.population = population;
    }

    @Override
    public int computePopulation() {
        return population;
    }

}

假设区就是最低级,区拥有 population 属性存放当前区的人数,区实现的计算人口数接口就是返回这个 population 属性,市是累加区的人数,省是累加市的人数。

由于所有的类都实现了计算人口数的接口,所以所有的对象都可以利用多态接收:

java 复制代码
package com.xsdl;

import java.util.ArrayList;
import java.util.List;

public class Main {

    private static final List<PopulationNode> populationNodeList = new ArrayList<>();

    public static void main(String[] args) {

        Province province = new Province("A省");

        City zz = new City("A1市");
        District zyq = new District("中原区", 2000);
        District eqq = new District("二七区", 5000);
        zz.addDistrict(zyq);
        zz.addDistrict(eqq);

        City zk = new City("A2市");
        District gxq = new District("高新区", 1000);
        zk.addDistrict(gxq);
        
        province.addCity(zz);
        province.addCity(zk);

        populationNodeList.add(province);
        
        for (PopulationNode populationNode : populationNodeList) {
            System.out.println(populationNode.computePopulation());
        }
    }

}

组合模式其实有一些递归的思想,本节点的计算结果依赖内部属性的计算结果,但组合模式还具有可以统一接收(利用多态),统一处理的特点。

拓展分析

除了上面的例子,其实还有很多例子也可以利用组合模式实现:

  • 统计某个文件夹中的文件数、目录数、总大小
java 复制代码
package com.xsdl.file;

public interface Node {

    int directoryCount();

    int fileCount();

    int fileSize();

}
java 复制代码
package com.xsdl.file;

public class FileNode implements Node {

    private String fileId;

    private String fileName;

    private int fileSize;

    public FileNode(String fileId, String fileName, int fileSize) {
        this.fileId = fileId;
        this.fileName = fileName;
        this.fileSize = fileSize;
    }

    @Override
    public int directoryCount() {
        return 0;
    }

    @Override
    public int fileCount() {
        return 1;
    }

    @Override
    public int fileSize() {
        return fileSize;
    }

}
java 复制代码
package com.xsdl.file;

import java.util.ArrayList;
import java.util.List;

public class DirectoryNode implements Node {

    private String directoryId;

    private String directoryName;

    private List<Node> nodeList = new ArrayList<>();

    public DirectoryNode(String directoryId, String directoryName) {
        this.directoryId = directoryId;
        this.directoryName = directoryName;
    }

    public void addNode(Node node) {
        nodeList.add(node);
    }

    @Override
    public int directoryCount() {
        return nodeList.stream().mapToInt(item -> item.directoryCount()).sum() + 1;
    }

    @Override
    public int fileCount() {
        return nodeList.stream().mapToInt(item -> item.fileCount()).sum();
    }

    @Override
    public int fileSize() {
        return nodeList.stream().mapToInt(item -> item.fileSize()).sum();
    }

}
java 复制代码
package com.xsdl.file;

public class Main {

    public static void main(String[] args) {
        DirectoryNode root = new DirectoryNode("1", "/root");
        FileNode rootFile = new FileNode("1-1-1", "/root/file2", 150);
        root.addNode(rootFile);

        DirectoryNode dir1 = new DirectoryNode("1-1", "/root/dir1");
        DirectoryNode dir2 = new DirectoryNode("1-2", "/root/dir2");
        FileNode file1 = new FileNode("1-1-1", "/root/dir1/file1", 100);
        root.addNode(dir1);
        root.addNode(dir2);
        dir1.addNode(file1);

        System.out.println(root.directoryCount());
        System.out.println(root.fileCount());
        System.out.println(root.fileSize());
    }

}
  • 实现一个简单的计算器

1+3*(4-2)*(2+3*6)-30 为例,其实可以抽象为 5 种实现,加减乘除和数字本身,加减乘除都有两个节点,数字只有一个节点,共同实现一个计算的接口,加减乘除的实现为 左节点的值 对应操作 右节点的值,数字节点的实现为返回值本身。

首先将中缀表达式转为后缀表达式:[1, 3, 4, 2, -, 2, 3, 6, *, +, *, *, +, 30, -]

利用一个栈来实现,循环遍历这个后缀表达式:

java 复制代码
1 3 4 2 
遇到了 - 号,弹出最外层两个元素封装为 (4-2)并入栈
1 3 (4-2)
 		  2 3 6 
遇到了 * 号,弹出最外层两个元素封装为 (6*3)并入栈
1 3 (4-2) 2 (6*3)
遇到了 + 号,弹出最外层两个元素封装为 ((6*3)+2) 并入栈
......    
......   

注意:栈是一个接口的 List ,例如 List ,共有五个实现: AddExpression、SubExpression、MultiplyExpression、DivisionExpression、NumberExpression

代码实现:组合模式实现一个简单的计算器

总结

组合模式主要用于类树形结构的遍历,所以其实平时用的并不是很多。

相关推荐
星空寻流年2 小时前
设计模式第四章(组合模式)
设计模式·组合模式
yujkss3 小时前
23种设计模式之【抽象工厂模式】-核心原理与 Java实践
java·设计模式·抽象工厂模式
PaoloBanchero5 小时前
Unity 虚拟仿真实验中设计模式的使用 ——命令模式(Command Pattern)
unity·设计模式·命令模式
大飞pkz5 小时前
【设计模式】桥接模式
开发语言·设计模式·c#·桥接模式
BeyondCode程序员8 小时前
设计原则讲解与业务实践
设计模式·架构
青草地溪水旁8 小时前
设计模式(C++)详解——迭代器模式(1)
c++·设计模式·迭代器模式
青草地溪水旁9 小时前
设计模式(C++)详解——迭代器模式(2)
java·c++·设计模式·迭代器模式
苍老流年9 小时前
1. 设计模式--工厂方法模式
设计模式·工厂方法模式
PaoloBanchero9 小时前
Unity 虚拟仿真实验中设计模式的使用 ——策略模式(Strategy Pattern)
unity·设计模式·策略模式