【23种设计模式】组合模式(Composite Pattern) .Net Core实现

news/2024/5/17 8:24:59/文章来源:https://blog.csdn.net/weixin_44231544/article/details/127050675

文章目录

    • 需求变更
    • 我们应该怎么做?
      • 组合和单个对象是指什么呢?
    • 使用组合模式来设计菜单
    • 组合迭代器
    • 来源

组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。

意图:将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

何时使用: 1、您想表示对象的部分-整体层次结构(树形结构)。 2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

如何解决:树枝和叶子实现统一接口,树枝内部组合该接口。

关键代码:树枝内部组合该接口,并且含有内部属性 List,里面放 Component。

应用实例: 1、算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作数也可以是操作数、操作符和另一个操作数。 2、在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。

优点: 1、高层模块调用简单。 2、节点自由增加。

缺点:在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。

使用场景:部分、整体场景,如树形菜单,文件、文件夹的管理。

注意事项:定义时为具体类。

需求变更

需结合文章(使用C# (.NET Core) 实现迭代器设计模式)一起看.

上述文章我们研究了多个菜单一起使用的问题.

就当我们感觉我们的设计已经足够好的时候, 新的需求来了, 我们不仅要支持多种菜单, 还要支持菜单下可以拥有子菜单.

例如我想在DinerMenu下添加一个甜点子菜单(dessert menu). 以我们目前的设计, 貌似无法实现该需求.
在这里插入图片描述
目前我们无法把dessertmenu放到MenuItem的数组里.

我们应该怎么做?

  • 我们需要一种类似树形的结构, 让其可以容纳/适应菜单, 子菜单以及菜单项.
  • 我们还需要维护一种可以在该结构下遍历所有菜单的方法, 要和使用遍历器一样简单.
  • 遍历条目的方法需要更灵活, 例如, 我可能只遍历DinerMenu下的甜点菜单(dessert menu), 或者遍历整个DinerMenu, 包括甜点菜单.

在这里插入图片描述

组合和单个对象是指什么呢?

如果我们拥有一个树形结构的菜单, 子菜单, 或者子菜单和菜单项一起, 那么就可以说任何一个菜单都是一个组合, 因为它可以包含其它菜单或菜单项.

而单独的对象就是菜单项, 它们不包含其它对象.

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
使用组合模式, 我们可以把相同的操作作用于组合或者单个对象上. 也就是说, 大多数情况下我们可以忽略对象们的组合与单个对象之间的差别.

该模式的类图:

在这里插入图片描述
客户Client, 使用Component来操作组合中的对象.

Component定义了所有对象的接口, 包括组合节点与叶子. Component接口也可能实现了一些默认的操作, 这里就是add, remove, getChild.

叶子Leaf会继承Component的默认操作, 但是有些操作也许并不适合叶子, 这个过会再说.

叶子Leaf没有子节点.

组合Composite需要为拥有子节点的组件定义行为. 同样还实现了叶子相关的操作, 其中有些操作可能不适合组合, 这种情况下异常可能会发生.

使用组合模式来设计菜单

首先, 需要创建一个component接口, 它作为菜单和菜单项的共同接口, 这样就可以在菜单或菜单项上调用同样的方法了.
在这里插入图片描述
由于菜单和菜单项必须实现同一个接口, 但是毕竟它们的角色还是不同的, 所以并不是每一个接口里(抽象类里)的默认实现方法对它们都有意义. 针对毫无意义的默认方法, 有时最好的办法是抛出一个运行时异常. 例如(NotSupportedException, C#).

MenuComponent:

using System;namespace CompositePattern.Abstractions
{public abstract class MenuComponent{public virtual void Add(MenuComponent menuComponent){throw new NotSupportedException();}public virtual void Remove(MenuComponent menuComponent){throw new NotSupportedException();}public virtual MenuComponent GetChild(int i){throw new NotSupportedException();}public virtual  string Name => throw new NotSupportedException();public virtual  string Description => throw new NotSupportedException();public virtual  double Price => throw new NotSupportedException();public virtual bool IsVegetarian => throw new NotSupportedException();public virtual void Print(){throw new NotSupportedException();}}
}

MenuItem:

using System;
using CompositePattern.Abstractions;namespace CompositePattern.Menus
{public class MenuItem : MenuComponent{public MenuItem(string name, string description, double price, bool isVegetarian){Name = name;Description = description;Price = price;IsVegetarian = isVegetarian;}public override string Name { get; }public override string Description { get; }public override double Price { get; }public override bool IsVegetarian { get; }public override void Print(){Console.Write($"\t{Name}");if (IsVegetarian){Console.Write("(v)");}Console.WriteLine($", {Price}");Console.WriteLine($"\t\t -- {Description}");}}
}

Menu:

using System;
using System.Collections.Generic;
using CompositePattern.Abstractions;namespace CompositePattern.Menus
{public class Menu : MenuComponent{readonly List<MenuComponent> _menuComponents;public Menu(string name, string description){Name = name;Description = description;_menuComponents = new List<MenuComponent>();}public override string Name { get; }public override string Description { get; }public override void Add(MenuComponent menuComponent){_menuComponents.Add(menuComponent);}public override void Remove(MenuComponent menuComponent){_menuComponents.Remove(menuComponent);}public override MenuComponent GetChild(int i){return _menuComponents[i];}public override void Print(){Console.Write($"\n{Name}");Console.WriteLine($", {Description}");Console.WriteLine("------------------------------");}}
}

注意Menu和MenuItem的Print()方法, 它们目前只能打印自己的东西, 还无法打印出整个组合. 也就是说如果打印的是菜单Menu的话, 那么它下面挂着的菜单Menu和菜单项MenuItems都应该被打印出来.

那么我们现在修复这个问题:

public override void Print(){Console.Write($"\n{Name}");Console.WriteLine($", {Description}");Console.WriteLine("------------------------------");foreach (var menuComponent in _menuComponents){menuComponent.Print();}}

服务员 Waitress:

using CompositePattern.Abstractions;namespace CompositePattern.Waitresses
{public class Waitress{private readonly MenuComponent _allMenus;public Waitress(MenuComponent allMenus){_allMenus = allMenus;}public void PrintMenu(){_allMenus.Print();}}
}

按照这个设计, 菜单组合在运行时将会是这个样子:
在这里插入图片描述
下面我们来测试一下:

using System;
using CompositePattern.Menus;
using CompositePattern.Waitresses;namespace CompositePattern
{class Program{static void Main(string[] args){MenuTestDrive();Console.ReadKey();}static void MenuTestDrive(){var pancakeHouseMenu = new Menu("PANCAKE HOUSE MENU", "Breakfast");var dinerMenu = new Menu("DINER MENU", "Lunch");var cafeMenu = new Menu("CAFE MENU", "Dinner");var dessertMenu = new Menu("DESSERT MENU", "Dessert of courrse!");var allMenus = new Menu("ALL MENUS", "All menus combined");allMenus.Add(pancakeHouseMenu);allMenus.Add(dinerMenu);allMenus.Add(cafeMenu);pancakeHouseMenu.Add(new MenuItem("Vegetarian BLT", "(Fakin’) Bacon with lettuce & tomato on whole wheat", true, 2.99));pancakeHouseMenu.Add(new MenuItem("K&B’s Pancake Breakfast", "Pancakes with scrambled eggs, and toast", true, 2.99));pancakeHouseMenu.Add(new MenuItem("Regular Pancake Breakfast", "Pancakes with fried eggs, sausage", false, 2.99));pancakeHouseMenu.Add(new MenuItem("Blueberry Pancakes", "Pancakes made with fresh blueberries", true, 3.49));pancakeHouseMenu.Add(new MenuItem("Waffles", "Waffles, with your choice of blueberries or strawberries", true, 3.59));dinerMenu.Add(new MenuItem("Vegetarian BLT", "(Fakin’) Bacon with lettuce & tomato on whole wheat", true, 2.99));dinerMenu.Add(new MenuItem("BLT", "Bacon with lettuce & tomato on whole wheat", false, 2.99));dinerMenu.Add(new MenuItem("Soup of the day", "Soup of the day, with a side of potato salad", false, 3.29));dinerMenu.Add(new MenuItem("Hotdog", "A hot dog, with saurkraut, relish, onions, topped with cheese", false, 3.05));dinerMenu.Add(new MenuItem("Pasta", "Spaghetti with Marinara Sauce, and a slice of sourdough bread", true, 3.89));dinerMenu.Add(dessertMenu);dessertMenu.Add(new MenuItem("Apple pie", "Apple pie with a flakey crust, topped with vanilla ice cream", true, 1.59));dessertMenu.Add(new MenuItem("Cheese pie", "Creamy New York cheessecake, with a chocolate graham crust", true, 1.99));dessertMenu.Add(new MenuItem("Sorbet", "A scoop of raspberry and a scoop of lime", true, 1.89));cafeMenu.Add(new MenuItem("Veggie Burger and Air Fries", "Veggie burger on a whole wheat bun, lettuce, tomato, and fries", true, 3.99));cafeMenu.Add(new MenuItem("Soup of the day", "A cup of the soup of the day, with a side salad", false, 3.69));cafeMenu.Add(new MenuItem("Burrito", "A large burrito, with whole pinto beans, salsa, guacamole", true, 4.29));var waitress = new Waitress(allMenus);waitress.PrintMenu();}}
}

在这里插入图片描述
Ok.

慢着, 之前我们讲过单一职责原则. 现在一个类拥有了两个职责…

确实是这样的, 我们可以这样说, 组合模式用单一责任原则换取了透明性.

透明性是什么? 就是允许组件接口(Component interface)包括了子节点管理操作和叶子操作, 客户可以一致的对待组合节点或叶子; 所以任何一个元素到底是组合节点还是叶子, 这件事对客户来说是透明的.

当然这么做会损失一些安全性. 客户可以对某种类型的节点做出毫无意义的操作, 当然了, 这也是设计的决定.

组合迭代器

服务员现在想打印所有的菜单, 或者打印出所有的素食菜单项.

这里我们就需要实现组合迭代器.

要实现一个组合迭代器, 首先在抽象类MenuComponent里添加一个CreateEnumerator()的方法.

   public virtual IEnumerator<MenuComponent> CreateEnumerator(){return new NullEnumerator();}

注意NullEnumerator:

using System.Collections;
using System.Collections.Generic;
using CompositePattern.Abstractions;namespace CompositePattern.Iterators
{public class NullEnumerator : IEnumerator<MenuComponent>{public bool MoveNext(){return false;}public void Reset(){}public MenuComponent Current => null;object IEnumerator.Current => Current;public void Dispose(){}}
}

我们可以用两种方式来实现NullEnumerator:

  1. 返回null
  2. 当MoveNext()被调用的时候总返回false. (我采用的是这个)

这对MenuItem, 就没有必要实现这个创建迭代器(遍历器)方法了.

请仔细看下面这个组合迭代器(遍历器)的代码, 一定要弄明白, 这里面就是递归, 递归:

using System;
using System.Collections;
using System.Collections.Generic;
using CompositePattern.Abstractions;
using CompositePattern.Menus;namespace CompositePattern.Iterators
{public class CompositeEnumerator : IEnumerator<MenuComponent>{private readonly Stack<IEnumerator<MenuComponent>> _stack = new Stack<IEnumerator<MenuComponent>>();public CompositeEnumerator(IEnumerator<MenuComponent> enumerator){_stack.Push(enumerator);}public bool MoveNext(){if (_stack.Count == 0){return false;}var enumerator = _stack.Peek();if (!enumerator.MoveNext()){_stack.Pop();return MoveNext();}return true;}public MenuComponent Current{get{var enumerator = _stack.Peek();var menuComponent = enumerator.Current;if (menuComponent is Menu){_stack.Push(menuComponent.CreateEnumerator());}return menuComponent;}}object IEnumerator.Current => Current;public void Reset(){throw new NotImplementedException();}public void Dispose(){}}
}

服务员 Waitress添加打印素食菜单的方法:

public void PrintVegetarianMenu(){var enumerator = _allMenus.CreateEnumerator();Console.WriteLine("\nVEGETARIAN MENU\n--------");while (enumerator.MoveNext()){var menuComponent = enumerator.Current;try{if (menuComponent.IsVegetarian){menuComponent.Print();}}catch (NotSupportedException e){}}}

注意这里的try catch, try catch一般是用来捕获异常的. 我们也可以不这样做, 我们可以先判断它的类型是否为MenuItem, 但这个过程就让我们失去了透明性, 也就是说 我们无法一致的对待Menu和MenuItem了.

我们也可以在Menu里面实现IsVegetarian属性Get方法, 这可以保证透明性. 但是这样做不一定合理, 也许其它人有更合理的原因会把Menu的IsVegetarian给实现了. 所以我们还是使用try catch吧.

测试:
在这里插入图片描述

来源

组合模式-菜鸟教程
使用C# (.NET Core) 实现组合设计模式 (Composite Pattern)

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

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

相关文章

【服务器数据恢复】AIX环境下误删除逻辑卷的数据恢复方案

一、AIX存储层面相关的知识&AIX环境下LV误删除后的恢复方案。 对于AIX而言&#xff0c;PV相当于物理磁盘&#xff0c;一个VG由若干个PV组成&#xff0c;这让我们可以将容量不同的存储空间组合起来进行统一分配。AIX把同一个VG的所有PV按相同大小的存储颗粒&#xff08;PP&…

自制操作系统系列(二):软盘读取

代码仓库地址&#xff1a;https://github.com/freedom-xiao007/operating-system 简介 在上一篇中&#xff0c;我们使用汇编编写了一个直接显示hello的程序&#xff0c;接下来我们继续探索如果使用汇编读取软盘数据 软盘数据读取准备 在上一篇中&#xff0c;我们使用nasm将…

让运维化繁为简,云原生可观测平台 Alibaba Cloud Lens 正式发布

9 月 28 日&#xff0c;阿里云正式推出云产品可观测平台 Alibaba Cloud Lens&#xff08;Lens 透镜&#xff0c;意为洞察细微变化&#xff09;&#xff0c;从成本、性能、安全、数据保护、稳定性、访问分析六个维度&#xff0c;为用户提供对存储类、网络类、数据库类等云产品的…

还在埋头敲代码?不妨学学设计模式,必能让你工作事半功倍

设计模式在开发中占很重要的地位&#xff1b;在大型项目中使用好设计模式往往会取得事半功倍的效果&#xff1b;下面就介绍下几种在开发中常用到的设计模式 装饰者模式(Decorator Pattern) 装饰者模式是在不必改变原类文件和使用继承的情况下&#xff0c;动态地扩展一个对象的…

中断系统:外部中断

中断系统&#xff1a;外部中断 实现功能 按下独立按键Key3&#xff0c;LED流水灯向右&#xff1b;按下Key4&#xff0c;LED流水灯向左。 单片机型号&#xff1a;STC89C52 硬件原理 中断系统就是当系统在进行一项工作时&#xff0c;需要终止这项工作&#xff0c;转而去执行另一…

Springboot复习

本笔记来自b站尚硅谷 文章目录SpringbootHelloWorld原理Value获取值和ConfigurationProperties获取值比较PropertySource 和 ImportResourceprofile自动配置原理整合日志指定配置全面接管SpringMVC启动流程自定义starterdev-toolsyml提示指标监控Springboot HelloWorld原理 配置…

Python函数练习题:通讯录管理程序实战案例

嗨害大家好鸭&#xff01;我是小熊猫❤ 好久没有整实战案例类文章辽 今天就来整一整~ 功能简介 实现一个通讯录管理程序&#xff0c;使用函数来实现程序&#xff0c;采用模块化的程序设计方法&#xff1a; 划分通讯录程序的功能模块&#xff0c;使用函数实现相应的功能首先…

数据湖-hudi概述

前言 数据湖是目前比较热的一个概念&#xff0c;许多企业都在构建或者计划构建自己的数据湖。 数据湖是一个集中式存储库&#xff0c;允许您以任意规模存储所有结构化和非结构化数据。您可以按原样存储数据&#xff08;无需先对数据进行结构化处理&#xff09;&#xff0c;并运…

nginx80转443

多了一个server&#xff0c;做了一个301的跳转 server { listen 80; server_name www.web1.com web1.com; &#xff08;这里是可写别名的&#xff0c;第二个是别名&#xff09; return 301 https://www.web1.com$request_uri; &#xff08;当别人访问80&#xff0c;让它转到…

智能网联-浅谈基于PKI的车联网应用服务安全

智能网联-浅谈基于PKI的车联网应用服务安全 面对车联网快速发展趋势&#xff0c;信息安全是一项必要的防护措施。车端应用数字证书可认证合法身份&#xff0c;保障数据加密通信&#xff0c;防止信息的泄露与攻击。基于PKI的车联网应用服务安全认证体系框架&#xff0c;从国家层…

Java多线程的创建与Thread类的方法及使用

Java多线程的创建与Thread类的方法及使用&#x1f34e;一.Thread类的属性与方法&#x1f347;1.1什么是线程&#x1f347;1.2Thread类的基础常见的构造方法&#x1f347;1.3Thread的常见属性&#x1f347;1.4Thread类常用的基础方法&#x1f34e;二.Java线程的创建&#x1f347…

6步搭建一个飞机大战游戏

摘要&#xff1a;本文以华为云软件开发平台DevCloud为例&#xff0c;展示飞机大战游戏开发的DevOps实践流程。DevOps实践 DevOps实践是一种开发、测试运维一体化的模式&#xff0c;其实践的外在表现一般包括了如代码仓库、构建、测试、发布、配置、监控等工具形成的一个完整的…

引擎之旅 Chapter.3 文件系统

文章目录引言在此之前...Unicode和ASCIIC风格字符串的操作函数集合字符串操作字符串类型转换Part1&#xff1a;操作文件名和文件路径Part2&#xff1a;单个文件的读写文件打开的模式TFile的定义Part3&#xff1a;异步文件I/O异步I/O线程文件类中的异步方法引言 为什么会将文件…

数据中台开源解决方案(一)

数据中台商业的解决方案有很多,开源框架种类繁多,每一个模块都有很多开源的套件。以查询引擎为例,可以使用的开源工具有MySQL、Redis、Impala、MongoDB、PgSQL等。可以根据实际业务需要,选择合适的开源套件。 可供选择的解决方案太多,重点推荐开源解决方案,框架图如下图所…

【RCJ-2 AC220V 0.015A静态冲击继电器】

系列型号 RCJ-2/48VDC冲击继电器 RCJ-2/110VDC冲击继电器 RCJ-2/220VDC冲击继电器 RCJ-2/100VAC冲击继电器 RCJ-2/127VAC冲击继电器 RCJ-2/220VAC冲击继电器 RCJ-3/220VAC冲击继电器 RCJ-3/127VAC冲击继电器 RCJ-3/100VAC冲击继电器 RCJ-3/220VDC冲击继电器 RCJ-3/110VDC冲击继…

【论文笔记】CIKM‘22 & Amazon | (Navip) 推荐系统中图神经网络的去偏邻域聚合

目录1. Introduction2. Related Work2.1 基于GNN的推荐2.2 逆倾向评分IPS3. 模型3.1 Navip4. 实验结果本文是亚马逊在CIKM 2022的一篇工作 论文地址&#xff1a;https://arxiv.org/pdf/2208.08847.pdf 问题 图形神经网络(GNN)对于曝光偏差的脆弱性导致模型偏差&#xff0c;产生…

09-Pytorch中的序列化容器

目录 1.梯度消失和梯度爆炸 1.1 梯度消失 1.2 梯度爆炸 1.3 解决梯度消失或梯度爆炸的经验 2.nn.Sequential 3.nn.BatchNorm1d 4.nn.Dropout 1.梯度消失和梯度爆炸 在使用pytorch中的序列化容器之前&#xff0c;我们先来了解一下常见的梯度消失和梯度爆炸的问题。 1.1 …

②、HTML 元素学习

HTML 元素 HTML 文档由 HTML 元素定义。 HTML 元素 *开始标签常被称为起始标签&#xff08;opening tag&#xff09;&#xff0c;结束标签常称为闭合标签&#xff08;closing tag&#xff09;。 HTML 元素语法 HTML 元素以开始标签起始HTML 元素以结束标签终止元素的内容是开始…

Class02

Class02 1.自动类型值转换 隐式类型转换 显式类型转换 隐式类型转换&#xff08;自动类型转换&#xff09; 隐式类型转换是小范围向大范围转换 实际上小范围包含在大范围内 整数类型的默认类型为int 浮点数类型的默认类型为double 显式类型转换&#xff08;强制类型转换&am…

linux 预读机制 (linux4.14)

一、基本概念 设计背景 文件一般是顺序访问的&#xff0c;访问[A, B]范围的数据后&#xff0c;接下来很可能访问[B1, BN]数据。由于访问磁盘、flash等存储器件比较耗时&#xff0c;在访问 [A, B]的时候&#xff0c;如果提前把[B1, BN]数据从存储器件读取到ram中&#xff0c;那…