当前位置: 首页 > news >正文

Composite组合模式(结构型模式)

1、概述

在面向对象系统中,经常会遇到一些具有"容器性质"的对象,它们自己在充当容器的同时,也充当其他对象的容器.

 

2、案例

需要构建一个容器系统,需要满足以下几点要求:

(1)、容器需要能创建和删除子容器

(2)、但是整个系统有最终的容器结构一一取名SingleBox

(3)、容器有自有业务逻辑,能执行指定的操作.

实现如下:

        /// <summary>
        /// 容器接口
        /// </summary>
        public interface IBox
        {
            void Process();

            void AddBox(IBox box);

            void RemoveBox(IBox box);
        }

        /// <summary>
        /// 最终节点的容器,这个容器无法进行添加和删除子容器的操作
        /// </summary>
        public class SingleBox : IBox
        {
            public void Process()
            {

            }

            public void AddBox(IBox box)
            {
                throw new Exception("SingleBox容器无法添加子容器");
            }

            public void RemoveBox(IBox box)
            {
                throw new Exception("SingleBox容器无法移除子容器");
            }
        }

        /// <summary>
        /// 普通容器,可以进行添加和删除子容器的操作
        /// </summary>
        public class ContainerBox : IBox
        {
            private List<IBox> _containerBox = new List<IBox>();

            public void Process() { }

            /// <summary>
            /// 模拟实现,没有实际意义,表达一种概念,获取当前容器的子容器
            /// </summary>
            /// <returns></returns>
            public List<ContainerBox> GetBox()
            {
                return _containerBox;
            }

            public void AddBox(IBox box)
            {
                _containerBox.Add(box);
            }

            public void RemoveBox(IBox box)
            {
                if (_containerBox.Contains(box))
                    _containerBox.Remove(box);
            }
        }

调用代码如下:

        /// <summary>
        /// 第三方调用系统
        /// </summary>
        public class ThirdSystem
        {
            public void Run()
            {
                IBox box = Factory.GetBox();
                if (box is ContainerBox)
                {
                    //如果当前容器是ContainerBox,执行该容器的Process方法
                    box.Process();
                    //获取该容器所有的子容器
                    var list = ((ContainerBox)box).GetBox();
                    //这里对所有的子容器进行递归操作,确保它们全部执行到类型为SingleBox
                }
                else if (box is SingleBox)
                {
                    box.Process();
                }
            }
        }

分析客户端调用代码发现,客户端调用代码在获取容器的子容器时,需要递归处理子容器,从而使客户端代码与复杂的容器结构发生了耦合,这样在设计上是不合理,客户端代码不能承担这种复杂度,而是应该交给容器系统去处理这种复杂度.

so,这种设计需要进行重构.

        /// <summary>
        /// 容器接口
        /// </summary>
        public interface IBox
        {
            void Process();

            void AddBox(IBox box);

            void RemoveBox(IBox box);
        }

        /// <summary>
        /// 最终节点的容器,这个容器无法进行添加和删除子容器的操作
        /// </summary>
        public class SingleBox : IBox
        {
            public void Process()
            {

            }

            public void AddBox(IBox box)
            {
                throw new Exception("SingleBox容器无法添加子容器");
            }

            public void RemoveBox(IBox box)
            {
                throw new Exception("SingleBox容器无法移除子容器");
            }
        }

        /// <summary>
        /// 普通容器,可以进行添加和删除子容器的操作
        /// </summary>
        public class ContainerBox : IBox
        {
            private List<IBox> _containerBox = new List<IBox>();

            //做一些容器该做的事情,比如说容器加载,做完之后卸载等等操作

            public void Process()
            {
                if (_containerBox.Count > 0)
                {
                    //遍历当前容器的所有子容器,然后执行子容器的操作,接着遍历该子容器的所有子容器
                    //进行它该进行的操作,循环这个操作,知道执行到SingleBox,因为它没有子容器,所有跳出
                    //Foreach循环,完成整颗容器树的遍历
                    foreach(IBox box in _containerBox)
                    {
                        box.Process();
                    }
                }
            }

            public void AddBox(IBox box)
            {
                _containerBox.Add(box);
            }

            public void RemoveBox(IBox box)
            {
                if (_containerBox.Contains(box))
                    _containerBox.Remove(box);
            }
        }

客户端调用代码如下:

        /// <summary>
        /// 第三方调用系统
        /// </summary>
        public class ThirdSystem
        {
            public void Run()
            {
                IBox box = Factory.GetBox();
                if (box is ContainerBox)
                {
                    //Procss会遍历当前容器的所有的子容器,并且执行这些容器的方法
                    box.Process();
                  
                }
                else if (box is SingleBox)
                {
                    box.Process();
                }
            }
        }

ok,现在的客户端调用代码与复杂的容器完成了解耦.而且完成了提出的需求.实现了对容器管理的同时,形成了一个树形结构.

but,上面的代码还是存在缺陷,IBox接口承担了两种职责,一种是是维护容器,另一种是处理容器的结构,执行容器的方法,虽然违背了OOP职责单一的原则,但是这种代价可以接受.

 

3、组合模式的要点

(1)、重构的代码使用了组合模式,组合模式采用树形结构来实现普遍存在的对象容器,将原先暴露给客户端的"一对多"的关系转换为"一对一"的关系,使得客户端代码可以一致地处理容器对象,不需要关心处理的是单个对象还是含有树形结构的容器对象,将递归处理容器的复杂度交给组合模式来承担.

 

(2)、将客户端调用代码与负责的容器结构解耦是Composite组合模式的核心思想,解耦之后,客户端代码与依赖的是容器抽象,而不是容器的内部实现结构,从而更能应对变化,试想以下,如果不这么做,如果容器对象发生改变,那么客户端就需要承受这种改变.

 

(3)、Composite模式在具体的实现中.可以让父对象的子对象进行反向追溯,如果父对象有频繁的遍历需求,可以使用缓存来改善效率.

 

(4)、Asp.Net中的控件大量使用了组合模式,可以参考帮助理解.

转载于:https://www.cnblogs.com/GreenLeaves/p/9819038.html

相关文章:

  • SQL基础知识
  • 开放源代码库指南
  • WPF换肤之三:WPF中的WndProc
  • 【转】VUE 爬坑之旅-- 如何对公共JS,CSS进行统一管理,全局调用
  • 各个浏览器之间常见的兼容性问题
  • 为什么需要RPC,而不是简单的HTTP接口
  • 和开源硬件相关的几个词,免费、山寨、创客教育,以及未来 | COSCon'18
  • 2018云计算行业现状及2020年云计算发展趋势
  • 当我们谈论Promise时,我们说些什么
  • 谷歌推迟公布Google+漏洞遭参议员不满
  • 今日头条完成超25亿美元融资 软银GA与KKR参与
  • iOS开发中实用的lldb命令
  • 网络时间戳
  • 昨天1024程序员节,我故意写了个死循环~
  • Git常用命令记录
  • 《微软的软件测试之道》成书始末、出版宣告、补充致谢名单及相关信息
  • 【挥舞JS】JS实现继承,封装一个extends方法
  • es6要点
  • learning koa2.x
  • markdown编辑器简评
  • PHP面试之三:MySQL数据库
  • Spring思维导图,让Spring不再难懂(mvc篇)
  • 多线程 start 和 run 方法到底有什么区别?
  • 分享一份非常强势的Android面试题
  • 关键词挖掘技术哪家强(一)基于node.js技术开发一个关键字查询工具
  • 关于字符编码你应该知道的事情
  • 记一次用 NodeJs 实现模拟登录的思路
  • 讲清楚之javascript作用域
  • 聚类分析——Kmeans
  • 力扣(LeetCode)21
  • 实战|智能家居行业移动应用性能分析
  • 通过获取异步加载JS文件进度实现一个canvas环形loading图
  • 项目管理碎碎念系列之一:干系人管理
  • 学习JavaScript数据结构与算法 — 树
  • 在Mac OS X上安装 Ruby运行环境
  • Nginx惊现漏洞 百万网站面临“拖库”风险
  • 格斗健身潮牌24KiCK获近千万Pre-A轮融资,用户留存高达9个月 ...
  • #NOIP 2014# day.1 T2 联合权值
  • (4)事件处理——(2)在页面加载的时候执行任务(Performing tasks on page load)...
  • (42)STM32——LCD显示屏实验笔记
  • (7)STL算法之交换赋值
  • (delphi11最新学习资料) Object Pascal 学习笔记---第2章第五节(日期和时间)
  • (javascript)再说document.body.scrollTop的使用问题
  • (笔试题)合法字符串
  • (分享)自己整理的一些简单awk实用语句
  • (简单有案例)前端实现主题切换、动态换肤的两种简单方式
  • (论文阅读32/100)Flowing convnets for human pose estimation in videos
  • (转)linux下的时间函数使用
  • ***通过什么方式***网吧
  • .helper勒索病毒的最新威胁:如何恢复您的数据?
  • .net 受管制代码
  • .NET实现之(自动更新)
  • .NET中winform传递参数至Url并获得返回值或文件
  • .pub是什么文件_Rust 模块和文件 - 「译」
  • /bin/bash^M: bad interpreter: No such file or directory