程序员必知!组合模式的实战应用与案例分析

程序员必知!组合模式的实战应用与案例分析 - 程序员古德

组合模式是一种设计模式,允许将对象组合成树形结构并像单个对象一样使用它们,这种模式在处理类似公司组织结构这样的树形数据时非常有用,通过组合模式,我们可以将公司和部门视为同一类型的对象,从而以统一的方式处理发送给不同层级的请求或任务,叶节点是没有子节点的对象,而复合节点则包含子节点,客户端可以与这些节点进行交互,无需知道它们的具体类型。组合模式提供了表示层次结构的灵活方式,并统一了客户端的交互方式。

定义

程序员必知!组合模式的实战应用与案例分析 - 程序员古德

组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得客户端对单个对象和复合对象的使用具有一致性。

举个业务中形象的例子,比如,我们有一个餐饮公司,其中提供了各种食品,如汉堡、薯条、饮料等,同时,公司也提供套餐服务,比如“汉堡套餐”包括汉堡、薯条和饮料,“全餐套餐”包括汉堡、薯条、饮料和沙拉。在这个例子中,每一个食品,如汉堡、薯条、饮料和沙拉,都可以被视为一个单独的组件,而套餐,比如“汉堡套餐”和“全餐套餐”,则是由这些组件组合而成的复合组件。

使用组合模式,我们可以设计一个统一的接口,比如“Orderable”,所有食品和套餐都实现这个接口,这个接口可以包含方法如“getPrice()”和“serve()”,单个食品如汉堡、薯条等实现这些方法以返回自己的价格和提供自己的服务,而套餐则在其实现中递归地调用其子组件的相应方法,从而计算出总价并提供整套服务。

这样,无论是单个食品还是套餐,对于客户来说,都可以通过相同的接口进行点单和获取价格等操作,而无需关心它们内部的具体构成。

代码案例

程序员必知!组合模式的实战应用与案例分析 - 程序员古德

就拿上面餐饮公司的业务案例,下面分别是针对该业务实现的未使用组合模式的反例和使用了组合模式的正例。

1、反例,未使用组合模式

没有使用组合模式,可能会采用一种比较简单直接的方法来实现,如下代码:

// 食品接口  
public interface Food {  double getPrice();  
}  // 汉堡类  
public class Burger implements Food {  @Override  public double getPrice() {  return 10.0;  }  
}  // 薯条类  
public class Fries implements Food {  @Override  public double getPrice() {  return 5.0;  }  
}  // 饮料类  
public class Drink implements Food {  @Override  public double getPrice() {  return 3.0;  }  
}  // 沙拉类  
public class Salad implements Food {  @Override  public double getPrice() {  return 7.0;  }  
}  // 汉堡套餐类  
public class BurgerCombo {  private Burger burger = new Burger();  private Fries fries = new Fries();  private Drink drink = new Drink();  public double getPrice() {  return burger.getPrice() + fries.getPrice() + drink.getPrice();  }  
}  // 全餐套餐类  
public class FullMealCombo {  private Burger burger = new Burger();  private Fries fries = new Fries();  private Drink drink = new Drink();  private Salad salad = new Salad();  public double getPrice() {  return burger.getPrice() + fries.getPrice() + drink.getPrice() + salad.getPrice();  }  
}  // 客户端调用案例  
public class Client {  public static void main(String[] args) {  // 创建汉堡套餐对象并获取价格  BurgerCombo burgerCombo = new BurgerCombo();  System.out.println("汉堡套餐价格: " + burgerCombo.getPrice()); // 输出: 18.0  // 创建全餐套餐对象并获取价格  FullMealCombo fullMealCombo = new FullMealCombo();  System.out.println("全餐套餐价格: " + fullMealCombo.getPrice()); // 输出: 28.0  }  
}

在上述代码中,我们定义了Food接口和四个实现类BurgerFriesDrinkSalad,然后,我们为每种套餐创建了一个单独的类(BurgerComboFullMealCombo),并在这些类中组合了不同的食品对象。

这种方式的问题在于,每当我们需要添加新的套餐或者修改现有套餐的组合时,都需要创建或修改相应的类,这会导致代码的维护成本增加,并且不利于代码的复用和扩展。

2、正例,使用组合模式

使用组合模式,如下代码:

// 菜单项接口,定义了计算价格的方法  
interface MenuItem {  double getPrice();  
}  // 具体菜单项类,实现了菜单项接口  
class Burger implements MenuItem {  @Override  public double getPrice() {  return 5.0; // 汉堡的价格  }  
}  class Fries implements MenuItem {  @Override  public double getPrice() {  return 3.0; // 薯条的价格  }  
}  class Drink implements MenuItem {  @Override  public double getPrice() {  return 2.0; // 饮料的价格  }  
}  class Salad implements MenuItem {  @Override  public double getPrice() {  return 4.0; // 沙拉的价格  }  
}  // 套餐类,实现了菜单项接口,并包含一个菜单项列表  
class Meal extends ArrayList<MenuItem> implements MenuItem {  @Override  public double getPrice() {  double sum = 0.0;  for (MenuItem item : this) {  sum += item.getPrice(); // 计算套餐的总价格  }  return sum;  }  
}  // 客户端调用案例  
public class Main {  public static void main(String[] args) {  // 创建具体的菜单项对象  MenuItem burger = new Burger();  MenuItem fries = new Fries();  MenuItem drink = new Drink();  MenuItem salad = new Salad();  // 创建套餐对象,并将具体的菜单项添加到套餐中  Meal burgerMeal = new Meal();  burgerMeal.add(burger);  burgerMeal.add(fries);  burgerMeal.add(drink);  System.out.println("汉堡套餐的价格: $" + burgerMeal.getPrice()); // 输出: 汉堡套餐的价格: $10.0  Meal fullMeal = new Meal();  fullMeal.add(burger);  fullMeal.add(fries);  fullMeal.add(drink);  fullMeal.add(salad);  System.out.println("全餐套餐的价格: $" + fullMeal.getPrice()); // 输出: 全餐套餐的价格: $14.0  }  
}

在上述代码中,我们定义了一个MenuItem接口,它有一个getPrice方法用于计算价格,具体的菜单项(如BurgerFriesDrinkSalad)实现了这个接口,套餐类(Meal)也实现了这个接口,并且包含一个菜单项列表,在套餐类的getPrice方法中,我们遍历这个列表并计算总价格,客户端代码中,我们创建了具体的菜单项对象和套餐对象,并将菜单项添加到套餐中,然后输出套餐的价格。

核心总结

程序员必知!桥接模式的实战应用与案例分析 - 程序员古德

组合模式,就是把一些对象组合起来,形成一个树状的结构,这种结构特别像我们平常生活中遇到的那种“部分与整体”的关系。比如说,一个公司由不同的部门组成,每个部门又有自己的员工,这样一层一层组合起来,就形成了一个完整的公司结构。

使用组合模式,可以简化客户端的代码,客户端在处理单个对象和一堆对象组合起来的大对象时,不需要知道它们之间的区别,可以用同样的方式处理,这就好像我们在处理一个单独的员工和一个整个部门时,可以用同样的方法来下达指令,而不需要去了解他们内部的具体细节。

组合模式还能提高系统的可扩展性,如果我们想添加新的组件类型,只需要在现有的基础上稍作修改,就可以轻松实现,这就像一个搭积木的过程,我们可以随时添加新的积木类型,搭建出更丰富的造型。

它的缺点就是结构的复杂性,如果组件和叶子有很多共同行为,但又存在一些微小的差异,那么在组合模式中处理它们可能会变得很复杂。这就好像我们在处理一堆形状、颜色都差不多的积木时,要找出其中那一块与众不同的积木一样困难。另外,由于组合模式使用了递归,如果组合的结构太深或者太大,可能会导致大量的内存消耗。这就像我们不断地往积木塔上添加积木,最终导致塔太高而倒塌一样。

关注我,每天学习互联网编程技术 - 程序员古德

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

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

相关文章

SV-9001 壁挂式网络采播终端

SV-9001 壁挂式网络采播终端 一、描述 SV-9001是深圳锐科达电子有限公司的一款壁挂式网络采播终端&#xff0c;具有10/100M以太网接口&#xff0c;配置一路线路输入和一组麦克风输入&#xff0c;可以直接连接音源输出设备或麦克风&#xff0c;将采集音源编码后发送至网络播放终…

仲晶同志简历

女&#xff0c;汉族。1972年出生&#xff0c;国防大学科技与装备教研室教官。1992年&#xff0c;仲晶毕业于军事气象学院&#xff0c;1996年成为国防大学国防科技发展战略学硕士研究生&#xff0c;毕业后留校任教。曾出版过9部军事专著&#xff0c;先后发表学术论文100多万字。…

CANoe中最常见的文件类型

文件类型图标说明文件说明保存步骤附加说明example.cfg此文件相当于一个集成的可执行文件&#xff0c;双击该图标就能打开工程Flie-》savepanel.xvp是vxp&#xff0c;控制盘文件home界面。打开panel 图标。在专门编辑界面保存。trace界面 导出的报文 报文存在多种格式&#xff…

Vue3+Vite项目搭建

为什么选择vite而不是vue-cli&#xff1a; vite下一代前端开发与构建工具 vite创建的项目默认vue3 优势&#xff1a; 开发环境中&#xff0c;无需打包&#xff0c;可快速的冷启动 轻量快速的热重载&#xff08;HMR&#xff09; 真正的按需编译&#xff0c;不在等待整个应用…

Elasticsearch:是时候离开了! - 在 Elasticsearch 文档上使用 TTL

作者&#xff1a;来自 Elastic David Pilato 想象一下&#xff0c;圣诞老人必须向世界上所有的孩子们分发礼物。 他有很多工作要做&#xff0c;他需要保持高效。 他有一份所有孩子的名单&#xff0c;并且知道他们住在哪里。 他很可能会将礼物按区域分组&#xff0c;然后再交付。…

递增的三元子序列

题目链接 递增的三元子序列 题目描述 注意点 1 < nums.length < 500000子序列的三个元素在原数组中可以不是连续的实现时间复杂度为 O(n) &#xff0c;空间复杂度为 O(1) 的解决方案 解答思路 使用贪心算法&#xff0c;在遍历数组时&#xff0c;要记录当前数组中的最…

大模型学习与实践笔记(五)

一、环境配置 1. huggingface 镜像下载 sentence-transformers 开源词向量模型 import os# 设置环境变量 os.environ[HF_ENDPOINT] https://hf-mirror.com# 下载模型 os.system(huggingface-cli download --resume-download sentence-transformers/paraphrase-multilingual-…

回声状态网络(Echo State Networks,ESN)详细原理讲解及Python代码实现

回声状态网络&#xff08;Echo State Networks,ESN&#xff09;详细讲解及Python代码实现 1 基本概念 回声状态网络是一种循环神经网络。ESN 训练方式与传统 RNN 不同。网络结构如下图&#xff1a; &#xff08;1&#xff09;储层&#xff08;Reservoir&#xff09;&#x…

DCP文件传输的重要性与应用

在数字时代&#xff0c;文件传输已成为商业运作中不可或缺的一环。随着企业越来越多地采用云基础设施和服务&#xff0c;有效地在云和团队之间传输大文件和数据集变得至关重要。在这一背景下&#xff0c;数据复制协议&#xff08;DCP&#xff09;文件传输应运而生&#xff0c;引…

推特Ads投放

一、准备工作&#xff1a; 推特广告账户&#xff1a; 您需要在推特上拥有一个有效的广告账户。 支付方式&#xff1a; 您需要关联一个有效的支付方式&#xff0c;通常是信用卡或其他支持的支付方式。 广告内容符合政策&#xff1a; 您的广告内容必须符合推特的广告政策&#…

Spring Boot - Application Events 的发布顺序_ApplicationStartingEvent

文章目录 概述Code源码分析 概述 Spring Boot 的广播机制是基于观察者模式实现的&#xff0c;它允许在 Spring 应用程序中发布和监听事件。这种机制的主要目的是为了实现解耦&#xff0c;使得应用程序中的不同组件可以独立地改变和复用逻辑&#xff0c;而无需直接进行通信。 …

Java SE入门及基础(8)

关系运算符和逻辑运算符 1. 关系运算符 关系运算符包含 > < > < ! boolean result 2 > 3 ; boolean result1 10 10 ; 关系运算符比较的结果是一个布尔值 2. 逻辑运算符 逻辑运算符包含&#xff1a; 逻辑与 &&&#xff…