组合模式详解及案例分析

组合模式

组合(Composite)模式的定义:有时又叫作部分-整体模式,它是一种将对象组合成树状的层次结构的模式,用来表示“部分-整体”的关系,使用户对单个对象和组合对象具有一致的访问性 属于结构型模式

  • 组合模式在生活中的场景应用

    公司组织架构、操作系统文件管理等

适用场景

  • 希望客户端可以忽略组合对象与单个对象的差异时;
  • 对象层次具备整体和部分,呈树形结构(比如树形菜单,公司组织架构等)
  • 有一个或多个共同特点。它们有一个主线。

案例

假设我们有这样一个需求:设计一个类来表示文件系统中的目录,能方便地实现下面这些功能:

  • 动态地添加、删除某个目录下的子目录或文件;
  • 统计指定目录下的文件个数;
  • 统计指定目录下的文件总大小。
package com.warape.designpattern.composite.v1;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class FileSystemNode {

    private String path;
    private boolean isFile;
    private List<FileSystemNode> subNodes = new ArrayList<>();

    public FileSystemNode(String path, boolean isFile) {
        this.path = path;
        this.isFile = isFile;
    }

    public int countNumOfFiles() {
        if (isFile) {
            return 1;
        }
        int numOfFiles = 0;
        for (FileSystemNode fileOrDir : subNodes) {
            numOfFiles += fileOrDir.countNumOfFiles();
        }
        return numOfFiles;
    }

    public long countSizeOfFiles() {
        if (isFile) {
            File file = new File(path);
            if (!file.exists()) {
                return 0;
            }
            return file.length();
        }
        long sizeofFiles = 0;
        for (FileSystemNode fileOrDir : subNodes) {
            sizeofFiles += fileOrDir.countSizeOfFiles();
        }
        return sizeofFiles;
    }

    public String getPath() {
        return path;
    }

    public void addSubNode(FileSystemNode fileOrDir) {
        subNodes.add(fileOrDir);
    }

    public void removeSubNode(FileSystemNode fileOrDir) {
        int size = subNodes.size();
        int i = 0;
        for (; i < size; ++i) {
            if (subNodes.get(i).getPath().equalsIgnoreCase(fileOrDir.getPath())) {
                break;
            }
        }
        if (i < size) {
            subNodes.remove(i);
        }
    }

    public static void main(String[] args) {
        String root = System.getProperty("user.dir");
        FileSystemNode fileSystemNode = new FileSystemNode(root + "/eg", false);
        FileSystemNode eg_a = new FileSystemNode(fileSystemNode.getPath()+ "/a", false);
        fileSystemNode.addSubNode(eg_a);
        FileSystemNode eg_a_image = new FileSystemNode(eg_a.getPath()+ "/image", false);
        eg_a.addSubNode(eg_a_image);
        FileSystemNode eg_a_image_png = new FileSystemNode(eg_a_image.getPath()+ "/设计模式.png", true);
        eg_a_image.addSubNode(eg_a_image_png);
        System.out.println(fileSystemNode.countNumOfFiles());
        System.out.println(fileSystemNode.countSizeOfFiles());
    }
}

单纯从功能实现角度来说,上面的代码没有问题,已经实现了我们想要的功能。但是,如果 我们开发的是一个大型系统,从扩展性(文件或目录可能会对应不同的操作)、业务建模 (文件和目录从业务上是两个概念)、代码的可读性(文件和目录区分对待更加符合人们对 业务的认知)的角度来说,我们最好对文件和目录进行区分设计,定义为 File 和 Directory 两个类。

按照这个设计思路,我们对代码进行重构。重构之后的代码如下所示:

// 抽象
public abstract class FileSystemNode {
    protected String path;

    public FileSystemNode(String path) {
        this.path = path;
    }

    public abstract int countNumOfFiles();

    public abstract long countSizeOfFiles();

    public String getPath() {
        return path;
    }
}
// 文件夹
public class Directory extends FileSystemNode {
    private List<FileSystemNode> subNodes = new ArrayList<>();

    public Directory(String path) {
        super(path);
    }

    @Override
    public int countNumOfFiles() {
        int numOfFiles = 0;
        for (FileSystemNode fileOrDir : subNodes) {
            numOfFiles += fileOrDir.countNumOfFiles();
        }
        return numOfFiles;
    }

    @Override
    public long countSizeOfFiles() {
        long sizeofFiles = 0;
        for (FileSystemNode fileOrDir : subNodes) {
            sizeofFiles += fileOrDir.countSizeOfFiles();
        }
        return sizeofFiles;
    }

    public void addSubNode(FileSystemNode fileOrDir) {
        subNodes.add(fileOrDir);
    }

    public void removeSubNode(FileSystemNode fileOrDir) {
        int size = subNodes.size();
        int i = 0;
        for (; i < size; ++i) {
            if (subNodes.get(i).getPath().equalsIgnoreCase(fileOrDir.getPath())) {
                break;
            }
        }
        if (i < size) {
            subNodes.remove(i);
        }
    }

}

// 文件
public class File extends FileSystemNode {
    public File(String path) {
        super(path);
    }

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

    @Override
    public long countSizeOfFiles() {
        java.io.File file = new java.io.File(path);
        if (!file.exists()) {
            return 0;
        }
        return file.length();
    }
}

// main
public static void main(String[] args) {
//Users/wan/workspace/study/design-pattern
    String root = System.getProperty("user.dir");
    Directory fileSystemNode = new Directory(root + "/eg/");

    Directory eg_a_directory = new Directory(fileSystemNode.getPath() + "/a/");
    fileSystemNode.addSubNode(eg_a_directory);
    Directory eg_a_image_directory = newDirectory(eg_a_directory.getPath() + "/image/");
    Directory eg_a_txt_directory = newDirectory(eg_a_directory.getPath() + "/txt/");
    eg_a_directory.addSubNode(eg_a_image_directory);
    eg_a_directory.addSubNode(eg_a_txt_directory);

    File image_file = new File(eg_a_image_directory.getPath() + "/设计模式.png");
    eg_a_image_directory.addSubNode(image_file);

    File txt_file_1 = new File(eg_a_txt_directory.getPath() + "/test1.txt");
    File txt_file_2 = new File(eg_a_txt_directory.getPath() + "/test2.txt");
    eg_a_txt_directory.addSubNode(txt_file_1);
    eg_a_txt_directory.addSubNode(txt_file_2);

    //b
    Directory eg_b_directory = new Directory(fileSystemNode.getPath() + "/b/");
    fileSystemNode.addSubNode(eg_b_directory);
    File txt_file_demo = new File(eg_b_directory.getPath() + "/demo1.txt");
    eg_b_directory.addSubNode(txt_file_demo);

    System.out.println(fileSystemNode.countNumOfFiles());
    System.out.println(fileSystemNode.countSizeOfFiles());
}

案例

在这里插入图片描述

  • 我们希望在内存中构建整个公司的人员架构图(部门、子部门、员工的隶属关系),并且提
    供接口计算出部门的薪资成本(隶属于这个部门的所有员工的薪资和)。
// 抽象
public abstract class HumanResource {

    protected Long id;
    protected double salary;

    public HumanResource(Long id) {
        this.id = id;
    }

    /**
     * 计算工资
     * @return double
     */
    public abstract double calculateSalary();

    public Long getId() {
        return id;
    }

    public double getSalary() {
        return salary;
    }
}
// 部门
public class Department extends HumanResource {

    private List<HumanResource> subNodes = new ArrayList<>();

    public Department(Long id) {
        super(id);
    }


    @Override
    public double calculateSalary() {
        double calculateSalary = 0;
        for (HumanResource subNode : subNodes) {
            calculateSalary += subNode.calculateSalary();
        }
        return calculateSalary;
    }

    public void addSubNode(HumanResource humanResource){
        subNodes.add(humanResource);
    }
}

// 员工
public class Dmployee extends HumanResource {

    public Dmployee(Long id, double salary) {
        super(id);
        this.salary = salary;
    }

    @Override
    public double calculateSalary() {
        return salary;
    }
}

// 构建
public class Test {

    private static final long ORGANIZATION_ROOT_ID = 0;
    private static final Map<Long, List<Long>> DEPARTMENT_ID_MAP = MapUtil.<Long, List<Long>>builder()
            .put(0L, ListUtil.toList(1L, 2L, 3L))
            .build();

    private static final Map<Long, List<Long>> EMPLOYEE_EMPLOYEE_MAP = MapUtil.<Long, List<Long>>builder()
            .put(1L, ListUtil.toList(4L, 5L, 6L))
            .put(2L, ListUtil.toList(7L, 8L, 9L))
            .put(3L, ListUtil.toList(10L))
            .build();

    private static final Map<Long, Double> EMPLOYEE_INFO_MAP = MapUtil.<Long, Double>builder()
            .put(4L, 10.00)
            .put(5L, 10.00)
            .put(6L, 10.00)
            .put(7L, 10.00)
            .put(8L, 10.00)
            .put(9L, 10.00)
            .put(10L, 10.00)
            .build();


    public static void main(String[] args) {
        Department department = buildOrganization();
        System.out.println("公司员工工资总和为: "+department.calculateSalary()+"元");
    }

    private static Department buildOrganization() {
        Department department = new Department(ORGANIZATION_ROOT_ID);
        buildOrganization(department);
        return department;
    }

    private static void buildOrganization(Department department) {
        //构建部门
        Long id = department.getId();
        List<Long> departmentIds = DEPARTMENT_ID_MAP.get(id);
        if (departmentIds != null) {
            departmentIds.forEach((subDepartmentId) -> {
                Department subDepartment = new Department(subDepartmentId);
                department.addSubNode(subDepartment);
                buildOrganization(subDepartment);
            });
        }

        // 构建员工
        List<Long> employeeList = EMPLOYEE_EMPLOYEE_MAP.get(department.getId());
        if (employeeList != null) {
            employeeList.forEach(employeeId -> {
                Double salary = EMPLOYEE_INFO_MAP.get(employeeId);
                department.addSubNode(new Dmployee(employeeId, salary));
            });
        }

    }
}

组合模式的优点

  • 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
  • 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;

组合模式的缺点

  • 不容易限制容器中的构件;
  • 使设计变得更加抽象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

猿道apeto

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

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

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

打赏作者

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

抵扣说明:

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

余额充值