浅谈设计模式 - 组合模式(十二)

简介: 浅谈设计模式 - 组合模式(十二)

前言


组合模式是一种非常重要的设计模式,使用场景几乎随处可见,各类菜单和目录等地方都能看到组合模式的影子,组合模式通常情况下是和树形结构相辅相成的,而树是软件设计里面非常重要的数据结构,这篇文章将介绍什么是组合模式。


什么是组合模式



允许你将对象组合到树形结构表现“整体部分”的结构,组合能让客户以一致的方式处理个别对象和对象组合,组合其实更像是对于对于各种独立组建的“统一性”,可以将一类相似的事物看为一个整体但是拥有完全不同的工作机制。


介绍


可以说将相似的物品形成一个集合的模式就是组合模式,他能看两个相似的物品在一处进行完美的融合以及操作。当我们需要 整体/部分的操作时候,就可以使用这种形式。


特点


  • 组合模式讲究的是整体和部分之间的关系,整体可以包含部分,部分可以回溯到整体,互相包含
  • 组合模式可以让对象结构以“树”的形式包含关系。多数情况可以忽略整体和个体之前的差别


优缺点



优点:


  • 组合模式可以帮助对象和组合的对象一视同仁的对待


缺点:


  • 继承结构,修改抽象类违反开放关闭原则
  • 如果层次结构非常深,递归结构影响效率
  • 使用迭代器有可能造成并发遍历菜单的问题

组合模式以单一职责的原则换取透明性?

组合模式破坏了的单一职责原则,组合了多个对象的方法,同时在方法里面做了多种操作,但是这样做却是可以让整个对象可以更加直观的了解整体和部分的特性,这是设计模式里面非常常见的操作。


组合模式的结构图


组合模式的结构图如下:


网络异常,图片无法展示
|


  • Component 组件:定义组件的接口,这里可以设计为抽象类,可以设计为接口,可以视为组件的“可能的公共行为”。
  • Leaf 叶子节点:用于表示原始对象,叶子节点只需要实现自己的特殊功能即可,比如菜单的菜单子项。
  • Composite 组件节点:定义组件行为,可以具备子节点。同时实现叶子节点的相关操作(继承同一个接口),可以视为一个分类的大类


实际应用场景


由于现实场景当中这样的设计模式结构是有树状结构转换而来的,所以组合模式的使用场景就是出现树形结构的地方。比如:文件目录显示,多及目录呈现等树形结构数据的操作。下面我们就使用一个菜单的结构来了解一下组合模式的“模板”代码。


实战



模拟场景


组合模式是为树形结构设计的一种设计模式,案例参照一个菜单的管理功能作为模拟,我们需要拿到不同的菜单分类,在菜单的分类里面,我们有需要拿到不同的菜单项,我们可以由任意的菜单项进入到不同的菜单分类,同时可以进入不同的叶子节点。

这次的代码案例是从网上找的例子:


抽象组件


抽象组件定义了组件的通知接口,并实现了增删子组件及获取所有子组件的方法。同时重写了hashCodeequales方法(至于原因,请读者自行思考。如有疑问,请在评论区留言)。


package com.jasongj.organization;
import java.util.ArrayList;
import java.util.List;
public abstract class Organization {
  private List<Organization> childOrgs = new ArrayList<Organization>();
  private String name;
  public Organization(String name) {
    this.name = name;
  }
  public String getName() {
    return name;
  }
  public void addOrg(Organization org) {
    childOrgs.add(org);
  }
  public void removeOrg(Organization org) {
    childOrgs.remove(org);
  }
  public List<Organization> getAllOrgs() {
    return childOrgs;
  }
  public abstract void inform(String info);
  @Override
  public int hashCode(){
    return this.name.hashCode();
  }
  @Override
  public boolean equals(Object org){
    if(!(org instanceof Organization)) {
      return false;
    }
    return this.name.equals(((Organization) org).name);
  }
}


简单组件(部门)


简单组件在通知方法中只负责对接收到消息作出响应。


package com.jasongj.organization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Department extends Organization{
  public Department(String name) {
    super(name);
  }
  private static Logger LOGGER = LoggerFactory.getLogger(Department.class);
  public void inform(String info){
    LOGGER.info("{}-{}", info, getName());
  }
}


复合组件(公司)


复合组件在自身对消息作出响应后,还须通知其下所有子组件


package com.jasongj.organization;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Company extends Organization{
  private static Logger LOGGER = LoggerFactory.getLogger(Company.class);
  public Company(String name) {
    super(name);
  }
  public void inform(String info){
    LOGGER.info("{}-{}", info, getName());
    List<Organization> allOrgs = getAllOrgs();
    allOrgs.forEach(org -> org.inform(info+"-"));
  }
}


awt的组合模式


组合模式因为使用了同样的接口,会让叶子节点实现一些不必要的功能,此时一般可以使用一个空对象或者使用更为激进的使用抛出异常的形式。

awt这种老掉牙的东西就不多介绍,java的gui其实就是使用了组合模式,下面是一部分的案例代码:


//创建组件
    public MethodsTank() {
        //创建组件等
        jm = new JMenu("我的菜单(G)");
        jmb = new JMenuBar();
        jl1 = new JMenuItem("开始新游戏(F)");
        jl2 = new JMenuItem("结束游戏");
        jl3 = new JMenuItem("重新开始(R)");
        jl4 = new JMenuItem("存盘退出");
        jl5 = new JMenuItem("回到上次游戏");
        draw = new DrawTank();
        ses = new selectIsSallup();
        //设置快捷键方式
        jm.setMnemonic('G');
        jl1.setMnemonic('f');
        jl3.setMnemonic('r');
        jl4.setMnemonic('q');
        jl5.setMnemonic('w');
        //开启闪烁线程
        new Thread(ses).start();
        //先运行开始画面
        this.addTank();
    }
    public void addTank() {
        //添加菜单栏目
        jm.add(jl1);
        jm.add(jl2);
        jm.add(jl3);
        jm.add(jl4);
        jm.add(jl5);
        jmb.add(jm);
        //运行选关界面
        this.add(ses);
        //对于子菜单添加事件
        jl1.addActionListener(this);
        jl1.setActionCommand("newgame");
        jl2.addActionListener(this);
        jl2.setActionCommand("gameexit");
        jl3.addActionListener(this);
        jl3.setActionCommand("restart");
        //设置窗体的一些基本属性
        this.setTitle("我的坦克大战");
        this.setBounds(600, 350, width, height);
        //添加菜单栏的方式
        this.setJMenuBar(jmb);
        this.setDefaultCloseOperation(this.EXIT_ON_CLOSE);
        this.setVisible(true);
    }


总结


组合模式精髓在于“破而后立”,他虽然违反了设计原则,但是通过更加优雅的形式,实现了将单一的对象由部分变为一个整体。

而组合模式也经常和适配器模式搭配使用,本文的案例只是一个简单的套板,对于组合模式的实际运用场景其实更常见的情况是关于菜单和菜单子项的内容。


结语


组合模式很多情况下可能并不是十分用的上,更多的时候是和其他的设计模式搭配,组合模式我们需要关注的是“整体-部分”的融合统一即可。

相关文章
|
3天前
|
搜索推荐 编译器 Linux
一个可用于企业开发及通用跨平台的Makefile文件
一款适用于企业级开发的通用跨平台Makefile,支持C/C++混合编译、多目标输出(可执行文件、静态/动态库)、Release/Debug版本管理。配置简洁,仅需修改带`MF_CONFIGURE_`前缀的变量,支持脚本化配置与子Makefile管理,具备完善日志、错误提示和跨平台兼容性,附详细文档与示例,便于学习与集成。
271 116
|
18天前
|
域名解析 人工智能
【实操攻略】手把手教学,免费领取.CN域名
即日起至2025年12月31日,购买万小智AI建站或云·企业官网,每单可免费领1个.CN域名首年!跟我了解领取攻略吧~
|
12天前
|
安全 Java Android开发
深度解析 Android 崩溃捕获原理及从崩溃到归因的闭环实践
崩溃堆栈全是 a.b.c?Native 错误查不到行号?本文详解 Android 崩溃采集全链路原理,教你如何把“天书”变“说明书”。RUM SDK 已支持一键接入。
662 219
|
5天前
|
数据采集 人工智能 自然语言处理
Meta SAM3开源:让图像分割,听懂你的话
Meta发布并开源SAM 3,首个支持文本或视觉提示的统一图像视频分割模型,可精准分割“红色条纹伞”等开放词汇概念,覆盖400万独特概念,性能达人类水平75%–80%,推动视觉分割新突破。
344 34
Meta SAM3开源:让图像分割,听懂你的话
|
10天前
|
人工智能 移动开发 自然语言处理
2025最新HTML静态网页制作工具推荐:10款免费在线生成器小白也能5分钟上手
晓猛团队精选2025年10款真正免费、无需编程的在线HTML建站工具,涵盖AI生成、拖拽编辑、设计稿转代码等多种类型,均支持浏览器直接使用、快速出图与文件导出,特别适合零基础用户快速搭建个人网站、落地页或企业官网。
1547 157
|
存储 人工智能 监控
从代码生成到自主决策:打造一个Coding驱动的“自我编程”Agent
本文介绍了一种基于LLM的“自我编程”Agent系统,通过代码驱动实现复杂逻辑。该Agent以Python为执行引擎,结合Py4j实现Java与Python交互,支持多工具调用、记忆分层与上下文工程,具备感知、认知、表达、自我评估等能力模块,目标是打造可进化的“1.5线”智能助手。
897 61
|
7天前
|
编解码 Linux 数据安全/隐私保护
教程分享免费视频压缩软件,免费视频压缩,视频压缩免费,附压缩方法及学习教程
教程分享免费视频压缩软件,免费视频压缩,视频压缩免费,附压缩方法及学习教程
295 140