设计模式_组合模式_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>

组合模式总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第2章 信息技术发展——2.1 信息技术及其发展

文章目录 2.1 信息技术及其发展2.1.1 计算机软硬件2.1.2 计算机网络1.网络标准协议1)OSI2)IEEE802协议族3)TCP/IP 2.软件定义网络3.第五代移动通信技术 2.1.3 存储和数据库1.存储技术2.数据结构模型1)层次模型2)网状模型3)关系模型 3.常用数据库类型1)关系型数据库2)非关系型数…

Docker镜像的创建

基于现有镜像创建 先用现有镜像创建启动容器 docker run 再进入容器进行内容更新 docker exec -it 最后提交成新的镜像 docker commit 然后将修改后的容器提交为新的镜像&#xff0c;需要使用该容器的 ID 号创建新镜像 进入容器可查看相关性能 基于本地模板创…

【软考上岸-备考系统集成项目管理工程师1】

文章目录 今日座右铭&#xff1a;最好的偷懒方式&#xff0c;就是一次做好。 文章目录 文章目录前言一、系统集成项目管理工程师作用二、系统集成项目管理工程师报名要求三、软考报名流程四、教材参考说明总结 前言 计算机信息系统集成项目管理师对应资格考试名称&#xff1a;…

FPGA高端项目:Xilinx Zynq7020系列FPGA多路视频拼接 工程解决方案 提供6套工程源码和技术支持

目录 1、前言版本更新说明给读者的一封信FPGA就业高端项目培训计划免责声明 2、相关方案推荐我已有的FPGA视频拼接叠加融合方案本方案在Xilinx Kintex7 系列FPGA上的应用本方案在Xilinx Artix7 系列FPGA上的应用 3、设计思路框架视频源选择ov5640 i2c配置及采集动态彩条多路视频…

k8s学习(RKE+k8s+rancher2.x)成长系列之概念介绍(一)

一、前言 本文使用国内大多数中小型企业使用的RKE搭建K8s并拉起高可用Rancher2.x的搭建方式&#xff0c;以相关技术概念为起点&#xff0c;实际环境搭建&#xff0c;程序部署为终点&#xff0c;从0到1的实操演示的学习方式&#xff0c;一步一步&#xff0c;保姆级的方式学习k8…

语音生成、写作增强、论文辅助、英文学习,AI原生应用精彩推荐一箩筐!

崭新的2024年已然降临&#xff0c;飞桨星河社区再次涌现出诸多精彩纷呈的AI原生应用&#xff0c;快来一同探索&#xff0c;发现这些应用带来的无限惊喜与可能吧&#xff01; 语音生成&#xff1a;10音色自由选择 应用介绍 本应用基于ERNIE SDK和语音合成工具&#xff0c;可以输…

“情暖寒冬 温暖相伴”关爱慰问

岁末寒冬&#xff0c;春节将至&#xff0c;为持续巩固脱贫攻坚成果&#xff0c;扎实助力乡村振兴&#xff0c;市融媒体中心开展“情暖寒冬 温暖相伴”慰问活动。 1月25市融媒体中心联合我市志愿者协会在南村镇忽树庄村开展“情暖寒冬 温暖相伴”慰问活动&#xff0c;为脱贫户送…

爬虫js逆向分析——x平台(实现)

爬虫js逆向分析——x平台&#xff08;实现&#xff09; &#xff08;仅供学习&#xff0c;本案例只是分析流程没有账号&#xff09;网址&#xff1a;https://xuexi.chinabett.com/ 1.分析请求包格式 打开控制台&#xff0c;并勾选保存日志&#xff0c;然后点击登录看发送了什…

【自然语言处理的发展】

自然语言处理的发展 自然语言处理&#xff08;NLP&#xff09;作为人工智能领域的一个分支&#xff0c;旨在让计算机理解和生成人类语言。随着深度学习和大数据技术的不断进步&#xff0c;NLP在近年来取得了显著的突破。本文将探讨NLP技术的发展历程、最新技术进展以及未来展望…

Debezium发布历史89

原文地址&#xff1a; https://debezium.io/blog/2020/03/31/debezium-newsletter-01-2020/ 欢迎关注留言&#xff0c;我是收集整理小能手&#xff0c;工具翻译&#xff0c;仅供参考&#xff0c;笔芯笔芯. Debezium 时事通讯 01/2020 2020 年 3 月 31 日 作者&#xff1a; 克…

力扣刷MySQL-第八弹(详细讲解)

&#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &#x1f388;该系列文章专栏&#xff1a;力扣刷题讲解-MySQL &#x1f379;文章作者技术和水平很有限&#xff0c;如果文中出…

Eureka的实操--中篇

Eureka的实操 2、安全连接方式 Eureka的安全连接可以通过以下步骤实现&#xff1a; 添加依赖&#xff1a;在项目的pom.xml文件中添加Eureka的依赖。配置安全连接&#xff1a;在项目的application.yml或application.properties文件中添加Eureka的安全连接配置。具体包括设置安…

RabbitMQ简单模式和工作模式

RabbitMQ 是一个消息队列中间件&#xff0c;用于在分布式系统中进行消息传递。在 RabbitMQ 中&#xff0c;有几种工作模式&#xff0c;其中简单模式和工作模式是其中两种基本的模式之一。 简单模式&#xff08;Simple Mode&#xff09;&#xff1a; 在简单模式中&#xff0c;有…

C++大学教程(第九版)6.48掷骰子游戏的改进

文章目录 题目代码运行截图 题目 (掷骰子游戏的改进)请修改图6.11 中的双游戏序允许家下赌注。 把序中运行掷骰子游戏的部分打包为一个函数。 初始化变量 bankBalance 为 1000美元。 提示玩家输入赌注数&#xff1a;wager。 利用一个 while 循环来检查 wager 是否小于或等于 b…

直线导轨运行不顺畅时怎么办?

为了确保直线导轨正常工作&#xff0c;确保设备的精度和稳定性&#xff0c;避免因此带来的生产损失和质量问题&#xff0c;需要及时处理直线导轨运行不顺畅或产生噪音等问题&#xff0c;今天我们就来详说如何解决直线导轨运行不顺畅。 1、长时间使用后&#xff0c;直线导轨表面…

达美乐3年亏9亿,披萨下沉能否“救市”?

“达门”在西北首店“出圈”。 作为中国首家西式快餐连锁品牌&#xff0c;达美乐于2023年12月24日在西安开出西北首店&#xff0c;单日销售额创造全球纪录32万。 此前&#xff0c;达势股份&#xff08;下称“达美乐中国”&#xff0c;01405.HK&#xff09;在港交所挂牌上市&a…

【Linux系统编程应用层开发目录】介绍Linux应用层开发的知识点和文章

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f440;专栏地址&#x1f440;&#xff1a;&#x1f680;Linux C语言&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、…

详细介绍 Go 中如何实现 bitset

文章目录 bitset 结构元素位置代码实现构造函数BitSet 的方法基础方法containsclearadd 集合方法computeSize方法定义intersectuniondifference 遍历集合的元素总结 最近尝试在 B 站录些小视频&#xff0c;我的 B 站主页。录视频当是为了彻底搞懂某个知识点的最后一步吧&#x…

向量数据库(1)

一、向量数据库 1&#xff0c;什么是向量数据库 专门存储和查询向量数据的数据库系统&#xff0c;通过高翔的向量索引和查询功能&#xff0c;使得在大规模向量数据集上进行相似性搜索和分析变得更高效和容易。 存储向量数据&#xff1a;处理百万或者十亿的大规模数据集向量索…

多符号表达式的共同子表达式提取教程

生成的符号表达式&#xff0c;可能会存在过于冗长的问题&#xff0c;且多个符号表达式中&#xff0c;有可能存在相同的计算部分&#xff0c;如果不进行处理&#xff0c;计算过程中会导致某些算式计算多次&#xff0c;从而影响计算效率。 那么多个符号表达式生成函数时&#xf…