设计模式_组合模式_Composite

案例引入

学校院系展示

编写程序展示一个学校院系结构: 需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系

在这里插入图片描述

【传统方式】

将学院看做是学校的子类,系是学院的子类,小的组织继承大的组织

分析: 在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系, 因此这种方案,不能很好实现管理操作,比如对学院、系的添加,删除,遍历

【组合模式】

把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作

介绍

基本介绍

  • 组合模式,又叫部分整体模式(描述部分和整体的关系),它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系
  • 组合模式依据树形结构(容器中可以放入内容,也可以放入小容器,小容器中又可以放入内容或者更小的容器)来组合对象,用来表示部分以及整体层次。组合模式可以使容器与内容具有一致性,创造出递归结构
  • 组合模式属于结构型模式
  • 组合模式使得用户对单个对象和组合对象的访问具有一致性,组合能让客户以一致的方式处理个别对象以及组合对象

使用场景

组合模式解决这样的问题,当我们的要处理的对象可以生成一个树形结构,而我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子

在这里插入图片描述

登场角色

  • Leaf(树叶):表示“内容”的角色,里面不能放人其他对象,即没有孩子,其定义组合内元素的行为
  • Composite(复合物):表示容器的角色,可以在其中放入Leaf和Composite,有一些对子部件的相关操作(如增加、删除),可能不具有叶子的某种行为
  • Component:使Leaf和Composite具有一致性的角色,Composite是 Leaf和Composite的父类。Compnet是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component子部件,Component 可以是抽象类或者接口
  • Client:使用Composite模式的角色

在这里插入图片描述

案例实现

案例1

类图

在这里插入图片描述

代码实现

【Component:组织】

package com.test.composite;/**
* 组织,如论是系、学院还是学校,都属于组织
*/
public abstract class OrganizationComponent {/**
* 名字
*/
private String name;
/**
* 说明
*/
private String des;/**
* 为什么需要默认实现,而不是写成抽象方法呢?
* 因为叶子节点不需要实现add方法,如果是抽象方法的话,就要实现了,有点多余
* @param organizationComponent
*/
protected void add(OrganizationComponent organizationComponent) {
//默认实现,抛出不支持操作异常
throw new UnsupportedOperationException();
}protected void remove(OrganizationComponent organizationComponent) {
//默认实现
throw new UnsupportedOperationException();
}/**
* 构造器
* @param name
* @param des
*/
public OrganizationComponent(String name, String des) {
super();
this.name = name;
this.des = des;
}public String getName() {
return name;
}public void setName(String name) {
this.name = name;
}public String getDes() {
return des;
}public void setDes(String des) {
this.des = des;
}/**
* 打印方法, 做成抽象的, 子类都需要实现
*/
protected abstract void print();}

【Composite:大学】

package com.test.composite;import java.util.ArrayList;
import java.util.List;/**
* University 就是 Composite角色 , 可以管理College
*/
public class University extends OrganizationComponent {List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();/**
* 构造器
* @param name
* @param des
*/
public University(String name, String des) {
super(name, des);
}/**
* 重写add
* @param organizationComponent
*/
@Override
protected void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}/**
* 重写remove
* @param organizationComponent
*/
@Override
protected void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}@Override
public String getName() {
return super.getName();
}@Override
public String getDes() {
return super.getDes();
}/**
* print方法,就是输出 University 包含的学院
*/
@Override
protected void print() {
// 先输出学校的名字
System.out.println("--------------" + getName() + "--------------");
// 遍历 organizationComponents,其实就是遍历出学校的学院
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}}

【Composite:学院】

package com.test.composite;import java.util.ArrayList;
import java.util.List;public class College extends OrganizationComponent {/**
* 存储系
*/
List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();/**
* 构造器
* @param name
* @param des
*/
public College(String name, String des) {
super(name, des);
}@Override
protected void add(OrganizationComponent organizationComponent) {
// 将来实际业务中,Colleage 的 add 和  University add 不一定完全一样
organizationComponents.add(organizationComponent);
}@Override
protected void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}@Override
public String getName() {
return super.getName();
}@Override
public String getDes() {
return super.getDes();
}/**
* print方法,就是输出学院包含的系
*/
@Override
protected void print() {
System.out.println("--------------" + getName() + "--------------");
// 遍历 organizationComponents
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}}

【Composite:系】

package com.test.composite;public class Department extends OrganizationComponent {//没有子节点,所以不用声明集合public Department(String name, String des) {
super(name, des);
}//add , remove 就不用写了,因为他是叶子节点@Override
public String getName() {
return super.getName();
}@Override
public String getDes() {
return super.getDes();
}@Override
protected void print() {
// 没有子节点,不需要输入其他东西
System.out.println(getName());
}}

【Client】

package com.test.composite;public class Client {public static void main(String[] args) {
//创建大学
OrganizationComponent university = new University("清华大学", " 中国顶级大学 ");//创建大学的各个学院
OrganizationComponent computerCollege = new College("计算机学院", " 计算机学院 ");
OrganizationComponent infoEngineerCollege = new College("信息工程学院", " 信息工程学院 ");//创建各个学院下面的系(专业)
computerCollege.add(new Department("软件工程", " 软件工程不错 "));
computerCollege.add(new Department("网络工程", " 网络工程不错 "));
computerCollege.add(new Department("计算机科学与技术", " 计算机科学与技术是老牌的专业 "));
infoEngineerCollege.add(new Department("通信工程", " 通信工程不好学 "));
infoEngineerCollege.add(new Department("信息工程", " 信息工程好学 "));//将学院加入到 学校
university.add(computerCollege);
university.add(infoEngineerCollege);//输出大学的各个组织
university.print();
}}

【运行】

--------------清华大学--------------
--------------计算机学院--------------
软件工程
网络工程
计算机科学与技术
--------------信息工程学院--------------
通信工程
信息工程Process finished with exit code 0

【只打印某个学院的组织结构】

computerCollege.print();

【运行】

--------------计算机学院--------------
软件工程
网络工程
计算机科学与技术Process finished with exit code 0

案例2

类图

在这里插入图片描述

代码实现

【Component:Entry类】

package com.test.composite.Sample;/**
* 目录条目类 用来实现 File类 和 Directory类 的一致性
*/
public abstract class Entry {/**
* 获取名字
*
* @return
*/
public abstract String getName();/**
* 获取大小
*
* @return
*/
public abstract int getSize();/**
* 加入目录条目,向文件夹中放入文件或者文件夹(Directory类来具体实现)
*
* @param entry
* @return
* @throws FileTreatmentException
*/
public Entry add(Entry entry) throws FileTreatmentException {
throw new FileTreatmentException();
}/**
* 为一览加上前缀并显示目录条目一览
*/
public void printList() {
printList("");
}/**
* 为一览加上前缀
* protected修饰:只能被子类调用
* @param prefix
*/
protected abstract void printList(String prefix);/**
* 显示代表类的文字
*
* @return
*/
public String toString() {
// 将文件名和文件大小一起显示出来
return getName() + " (" + getSize() + ")";
}
}

方法的默认实现是抛异常(一般都是这样做),这样如果子类没有重写该方法的话,就会抛异常

【Composite:文件类】

package com.test.composite.Sample;/**
* 文件类
*/
public class File extends Entry {
private String name;
private int size;/**
* 构造方法 创建文件
*
* @param name
* @param size
*/
public File(String name, int size) {
this.name = name;
this.size = size;
}@Override
public String getName() {
return name;
}@Override
public int getSize() {
return size;
}@Override
protected void printList(String prefix) {
// 直接写this,会自动调用该类的toString()方法的
System.out.println(prefix + "/" + this);
}
}

【Composite:目录类】

package com.test.composite.Sample;import java.util.ArrayList;
import java.util.Iterator;public class Directory extends Entry {
/**
* 文件夹的名字
*/
private String name;
/**
* 文件夹中目录条目的集合
*/
private ArrayList directory = new ArrayList();public Directory(String name) {
this.name = name;
}@Override
public String getName() {
return name;
}/**
* 获取大小:计算子文件或文件夹的大小总和
* @return
*/
@Override
public int getSize() {
int size = 0;
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
// 无论子条目是文件夹还是文件,都可以直接调用其getSize()方法,这就是“容器与内容一致性”的好处
// 如果entry是目录,就会形成递归调用
size += entry.getSize();
}
return size;
}/**
* 增加目录条目
* @param entry
* @return
*/
@Override
public Entry add(Entry entry) {
directory.add(entry);
return this;
}/**
* 显示目录条目一览
* @param prefix
*/
@Override
protected void printList(String prefix) {
System.out.println(prefix + "/" + this);
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
// 也是递归调用
entry.printList(prefix + "/" + name);
}
}
}

【自定义异常类】

package com.test.composite.Sample;/**
* 自定义异常类
*/
public class FileTreatmentException extends RuntimeException {
public FileTreatmentException() {
}public FileTreatmentException(String msg) {
super(msg);
}
}

【主类】

package com.test.composite.Sample;public class Main {
public static void main(String[] args) {
try {
System.out.println("Making root entries...");
Directory rootdir = new Directory("root");
Directory bindir = new Directory("bin");
Directory tmpdir = new Directory("tmp");
Directory usrdir = new Directory("usr");
rootdir.add(bindir);
rootdir.add(tmpdir);
rootdir.add(usrdir);
bindir.add(new File("vi", 10000));
bindir.add(new File("latex", 20000));
rootdir.printList();System.out.println("");
System.out.println("Making user entries...");
Directory yuki = new Directory("yuki");
Directory hanako = new Directory("hanako");
Directory tomura = new Directory("tomura");
usrdir.add(yuki);
usrdir.add(hanako);
usrdir.add(tomura);
yuki.add(new File("diary.html", 100));
yuki.add(new File("Composite.java", 200));
hanako.add(new File("memo.tex", 300));
tomura.add(new File("game.doc", 400));
tomura.add(new File("junk.mail", 500));
rootdir.printList();
} catch (FileTreatmentException e) {
e.printStackTrace();
}
}
}

【运行】

Making root entries...
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0)Making user entries...
/root (31500)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (1500)
/root/usr/yuki (300)
/root/usr/yuki/diary.html (100)
/root/usr/yuki/Composite.java (200)
/root/usr/hanako (300)
/root/usr/hanako/memo.tex (300)
/root/usr/tomura (900)
/root/usr/tomura/game.doc (400)
/root/usr/tomura/junk.mail (500)Process finished with exit code 0
拓展

如何通过修改或者补充上面的代码来增加一个为 文件/目录 获取完整路径的功能,如/root/usr/yuki/Composite.java

【Component】

添加一个记录父条目的变量,和一个公共方法,该方法不需要子类去重写,因为实现逻辑都一样,如果不一样的话,就需要写成抽象方法

package com.test.composite.A1;public abstract class Entry {
protected Entry parent;public abstract String getName();public abstract int getSize();public Entry add(Entry entry) throws FileTreatmentException {
throw new FileTreatmentException();
}public void printList() {
printList("");
}protected abstract void printList(String prefix);public String toString() {
return getName() + " (" + getSize() + ")";
}/**
* 获取条目的完整路径
*
* @return
*/
public String getFullName() {
StringBuffer fullname = new StringBuffer();
Entry entry = this;
do {
//需要将父条目的名字插到前面,而不是append到后面
fullname.insert(0, "/" + entry.getName());
entry = entry.parent;
} while (entry != null);
return fullname.toString();
}
}

【Composite:目录类】

当给目录加入元素时,需要指定元素的父元素,使用entry.parent = this; 来实现

package com.test.composite.A1;import java.util.ArrayList;
import java.util.Iterator;public class Directory extends Entry {
private String name;
private ArrayList directory = new ArrayList();
public Directory(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int getSize() {
int size = 0;
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
size += entry.getSize();
}
return size;
}
public Entry add(Entry entry) {
directory.add(entry);
entry.parent = this;                
return this;
}
protected void printList(String prefix) {
System.out.println(prefix + "/" + this);
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
entry.printList(prefix + "/" + name);
}
}
}

组合模式在JDK的HashMap源码中的应用

在这里插入图片描述

  • Map 就是一个抽象的构建 (类似我们的Component)
  • HashMap是一个中间的构建(Composite), 实现/继承了相关方法(put, putAll)
  • Node 是 HashMap的静态内部类,类似Leaf叶子节点, 该类没有(put, putAll)这些方法
    • static class Node<K,V> implements Map.Entry<K,V>

组合模式总结

  • 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题
  • 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动
  • 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
  • 需要遍历组织机构,或者处理的对象具有树形结构时,非常适合使用组合模式
  • 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/648401.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS 鸿蒙应用开发( 六、实现自定义弹窗CustomDialog)

自定义弹窗&#xff08;CustomDialog&#xff09;可用于广告、中奖、警告、软件更新等与用户交互响应操作。开发者可以通过CustomDialogController类显示自定义弹窗。具体用法请参考自定义弹窗。 在应用的使用和开发中&#xff0c;弹窗是一个很常见的场景&#xff0c;自定义弹窗…

idea连接docker

idea 插件无法连接docker问题 原文&#xff1a;idea 插件无法连接docker问题 // 修改docker配置 vi /usr/lib/systemd/system/docker.service // 加上该段配置允许任何ip访问 -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock // 重启docker即可 systemctl restart dock…

虹科数字化与AR部门升级为安宝特AR子公司

致关心虹科AR的朋友们&#xff1a; 感谢您一直以来对虹科数字化与AR的支持和信任&#xff0c;为了更好地满足市场需求和公司发展的需要&#xff0c;虹科数字化与AR部门现已升级为虹科旗下独立子公司&#xff0c;并正式更名为“安宝特AR”。 ”虹科数字化与AR“自成立以来&…

opencv010 卷积02(方盒滤波和均值滤波)

今天继续学习滤波器的相关知识&#xff01;这篇比较简单&#xff0c;也短一些&#xff0c;明天写高斯滤波 方盒滤波 boxFilter(scr, ddepth, ksize[, dst[, anchor[, normalize[, borderType]]]]) 方盒滤波的卷积核如下&#xff1a; normalize&#xff08;标准化&#xff0…

Linux 挂载读取、卸载 ntfs格式硬盘

windows常用的ntfs硬盘分区格式&#xff0c;在linux通常不能直接读取&#xff0c;不过挂载也是非常容易 一、挂载ntfs分区 1.安装 apt-get install ntfs-3g2.查看现在接上的硬盘 fdisk -l可以找到类似如下的&#xff0c;会显示microsoft basic data 3.创建挂载的目录 创…

微服务JWT的介绍与使用

1. 无状态登录 1.1 微服务的状态 ​ 微服务集群中的每个服务&#xff0c;对外提供的都是Rest风格的接口&#xff0c;而Rest风格的一个最重要的规范就是&#xff1a;服务的无状态性。 ​ 什么是无状态&#xff1f; 服务端不保存任何客户端请求者信息客户端的每次请求必须具备…

数据结构—基础知识(13):树的存储结构

数据结构—基础知识&#xff08;13&#xff09;&#xff1a;树的存储结构 双亲表示法 这种表示方法中&#xff0c;以一组连续的存储单元存储树的结点&#xff0c;每个结点除了数据域data外&#xff0c;还附设一个parent域用以指示其双亲结点的位置。 这种存储结构利用了每个结…

手搓反向迭代器

前言 关于反向迭代器&#xff0c;字如其名&#xff0c;就是将正向迭代器&#xff0c;从反方向再迭代一次就成了&#xff0c;所以我们如此设计反向迭代器&#xff1a; 假设我们已经拥有了一套能够使用&#xff0c;且包含模板的正向迭代器利用适配器模式&#xff0c;让反向迭代…

软件测试生命周期

本章简要介绍了软件开发项目中常用的生命周期模型&#xff0c;并解释了测试在每个模型中扮演的角色。它讨论了各种测试级别和测试类型之间的区别&#xff0c;并解释了这些在开发过程中的应用位置和方式。 大多数软件开发项目是按照事先选择的软件开发生命周期模型来计划和执行…

ZK监控方法以及核心指标

文章目录 1. 监控指标采集1.1 zk版本高于3.6.0监控指标采集1.2 zk版本低于3.6.0监控指标采集1.3 配置promethues采集和大盘 2. 核心告警指标3. 参考文章 探讨zk的监控数据采集方式以及需要关注的核心指标&#xff0c;便于日常生产进行监控和巡检。 1. 监控指标采集 3.6.0 版本…

ORA-12528: TNS: 监听程序: 所有适用例程都无法建立新连

用了网上的办法&#xff1a; 1、修改listener.ora的参数,把动态的参数设置为静态的参数,红色标注部分 位置D:\oracle\product\10.2.0\db_1\NETWORK\ADMIN SID_LIST_LISTENER (SID_LIST (SID_DESC (SID_NAME PLSExtProc) (ORACLE_HOME D:\oracle\produ…

基于PHP反序列化练习

PHP创建一个以自己姓名命名的类&#xff0c;要求存在两个属性&#xff0c;name&#xff0c;age&#xff0c;进行序列化&#xff0c;输出序列化以后的数据。 <!-- PHP创建一个以自己姓名命名的类&#xff0c;要求存在两个属性&#xff0c;name&#xff0c;age --> <?…

【C/C++】C/C++编程——第一个 C++ 程序:HelloWorld

第一个 C 程序&#xff1a;HelloWorld 大家好&#xff0c;我是 shopeeai&#xff0c;也可以叫我虾皮&#xff0c;中科大菜鸟研究生。昨天我们成功搭建好了 C 的开发环境&#xff0c;今天我们来介绍一下第一个 C 程序,打印一个"hello world"。首先我们先贴一下示例代…

【FPGA Verilog开发实战指南】初识Verilog HDL-基础语法

这里写目录标题 Verilog HDL简介与VHDL比较 Verilog HDL基础语法逻辑值关键字moduleendmodule 模块名输入信号输出信号既做输入也做输出线网型变量 wire寄存器型变量 reg参数 parameter参数 localparam常量赋值方式阻塞赋值非阻塞赋值 always语句assign 语句 算数运算符归元运算…

C++ Qt day2

自己封装一个矩形类(Rect)&#xff0c;拥有私有属性:宽度(width)、高度(height)&#xff0c; 定义公有成员函数: 初始化函数:void init(int w, int h) 更改宽度的函数:set_w(int w) 更改高度的函数:set_h(int h) 输出该矩形的周长和面积函数:void show() #include <io…

翻译: 使用 GPT-4 将您的 Streamlit 应用程序提升到一个新的水平一

帮助您更快地设计、调试和优化 Streamlit 应用的专业技巧 设计和扩展 Streamlit 应用程序可能是一项艰巨的任务&#xff01;作为开发人员&#xff0c;我们经常面临一些挑战&#xff0c;例如设计良好的 UI、快速调试我们的应用程序以及快速制作它们。 如果有一个工具可以加快速…

Tomcat运维

目录 一、Tomcat简介 二、系统环境说明 1、关闭防火墙&#xff0c;selinux 2、安装JDK 3、安装Tomcat 三、Tomcat目录介绍 1、tomcat主目录介绍 2、webapps目录介绍 3、Tomcat配置介绍&#xff08;conf&#xff09; 4、Tomcat的管理 四、Tomcat 配置管理页面(了解) …

类和对象 第三部分第三小节:const修饰成员函数

一.常函数&#xff1a; &#xff08;一&#xff09;成员函数后面加const后我们成这个函数为常函数 &#xff08;二&#xff09;常函数内不可以修改成员函数属性 额外补充&#xff1a; this指针的本质&#xff0c;是指针常量&#xff0c;指针指向的是不可以修改的 但是指针指向的…

响应式Web开发项目教程(HTML5+CSS3+Bootstrap)第2版 例4-11 HTML5 表单验证

代码 <!doctype html> <html> <head> <meta charset"utf-8"> <title>HTML5 表单验证</title> </head><body> <form action"#" method"get" novalidate>请输入您的邮箱:<input type&q…

无限学模式-“科研创新的加速器:全面掌握ChatGPT,推动研究方法和工作模式现代化!“

2023年随着OpenAI开发者大会的召开&#xff0c;最重磅更新当属GPTs&#xff0c;多模态API&#xff0c;未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚于互联网和个人电脑的问世。360创始人周鸿祎认为未来各行各业如果不能搭上这班车…