设计模式第六讲:责任链模式和迭代器模式详解

news2024/10/2 12:19:13

一. 责任链模式

1. 背景

在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这样的例子还有很多,如找领导出差报销、生活中的“击鼓传花”游戏等。

2. 定义和特点

(1). 定义:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

PS:责任链模式也叫职责链模式。在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,所以责任链将请求的发送者和请求的处理者解耦了。

(2). 优点:

 A. 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。

 B. 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。

 C. 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。

 D. 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。

 E. 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

(3). 缺点:

 A. 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。

 B. 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。

 C. 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

3. 具体实现

(1). 模式结构

 A. 抽象处理者:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。

 B. 具体处理者:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。

 C. 客户端调用:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

结构图如下:

(2). 使用场景

 员工请假,不同的天数需要被不同级别的领导批准,这个时候可以采用责任链模式。每个领导都是一个具体的请假处理者,同时提炼出来一个抽象处理者,包括:请假处理方法和下一个节点,最后由客户端建立责任链,并将请求传递进去。

更多C++后台开发技术点知识内容包括C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,MongoDB,ZK,流媒体,音视频开发,Linux内核,TCP/IP,协程,DPDK多个高级知识点。

C/C++Linux服务器开发高级架构师/C++后台开发架构师免费学习地址

【文章福利】另外还整理一些C++后台开发架构师 相关学习资料,面试题,教学视频,以及学习路线图,免费分享有需要的可以点击领取

(3). 代码实操

请假请求类:

    /// <summary>
    /// 请假请求类
    /// </summary>
    public class LeaveRequest
    {
        /// <summary>
        /// 姓名
        /// </summary>
        public string name { get; set; }

        /// <summary>
        /// 请假天数
        /// </summary>
        public int leaveDays { get; set; }

    }

抽象处理者和具体处理者:

    /// <summary>
    /// 抽象处理者角色
    /// </summary>
    public abstract class AbstractHandler
    {
        /// <summary>
        /// 代表责任链中的下一个角色
        /// </summary>
        public AbstractHandler NextHandler { get; set; }

        /// <summary>
        /// 处理请假请求
        /// </summary>
        public abstract void HandleRequest(LeaveRequest request);
    }
 /// <summary>
    /// 直接领导(一级领导)
    /// </summary>
    public class LeaderHandler1 : AbstractHandler
    {
        /// <summary>
        /// 处理请假请求
        /// </summary>
        public override void HandleRequest(LeaveRequest request)
        {
            if (request.leaveDays<=3)
            {
                Console.WriteLine($"直接领导(一级领导)已经批准{request.name}的请假请求,请假天数:{request.leaveDays}");
            }
            else
            {
                if (this.NextHandler!=null)
                {
                    this.NextHandler.HandleRequest(request);
                }
                else
                {
                    Console.WriteLine($"{request.name}请假天数太多,没有人批准该假条!");
                }
            }
            
        }
    }
 /// <summary>
    /// 二级领导
    /// </summary>
    public class LeaderHandler2 : AbstractHandler
    {
        /// <summary>
        /// 处理请假请求
        /// </summary>
        public override void HandleRequest(LeaveRequest request)
        {
            if (request.leaveDays > 3 && request.leaveDays <= 7)
            {
                Console.WriteLine($"二级领导已经批准{request.name}的请假请求,请假天数:{request.leaveDays}");
            }
            else
            {
                if (this.NextHandler != null)
                {
                    this.NextHandler.HandleRequest(request);
                }
                else
                {
                    Console.WriteLine($"{request.name}请假天数太多,没有人批准该假条!");
                }
            }

        }
    }
 /// <summary>
    /// 最高领导(三级)
    /// </summary>
    public class LeaderHandler3 : AbstractHandler
    {
        /// <summary>
        /// 处理请假请求
        /// </summary>
        public override void HandleRequest(LeaveRequest request)
        {
            if (request.leaveDays > 7 && request.leaveDays <= 30)
            {
                Console.WriteLine($"最高领导(三级)已经批准{request.name}的请假请求,请假天数:{request.leaveDays}");
            }
            else
            {
                if (this.NextHandler != null)
                {
                    this.NextHandler.HandleRequest(request);
                }
                else
                {
                    Console.WriteLine($"{request.name}请假天数太多,没有人批准该假条!");
                }
            }

        }
    }

客户单创建责任链并调用:

                //1. 有人要请假
                LeaveRequest myRequest1 = new LeaveRequest() { 
                    name="小明",
                    leaveDays=6
                };
                LeaveRequest myRequest2 = new LeaveRequest()
                {
                    name = "小王",
                    leaveDays = 2
                };
                LeaveRequest myRequest3 = new LeaveRequest()
                {
                    name = "小放",
                    leaveDays = 15
                };
                LeaveRequest myRequest4 = new LeaveRequest()
                {
                    name = "张三",
                    leaveDays = 35
                };

                //2. 请假相关的责任链
                LeaderHandler1 handler1 = new LeaderHandler1();
                LeaderHandler2 handler2 = new LeaderHandler2();
                LeaderHandler3 handler3 = new LeaderHandler3();
                handler1.NextHandler = handler2;
                handler2.NextHandler = handler3;

                //3.进行请假处理
                handler1.HandleRequest(myRequest1);
                handler1.HandleRequest(myRequest2);
                handler1.HandleRequest(myRequest3);
                handler1.HandleRequest(myRequest4);

测试结果:

4. 适用场景分析

 (1). 有多个对象可以处理一个请求,哪个对象处理该请求由运行时刻自动确定。

 (2). 可动态指定一组对象处理请求,或添加新的处理者。

 (3). 在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。

二. 迭代器模式

1. 背景

  在现实生活以及程序设计中,经常要访问一个聚合对象中的各个元素,如【数据结构】中的链表遍历,通常的做法是将链表的创建和遍历都放在同一个类中,但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码,这违背了 “开闭原则”。既然将遍历方法封装在聚合类中不可取,那么聚合类中不提供遍历方法,将遍历方法由用户自己实现是否可行呢?答案是同样不可取,因为这种方式会存在两个缺点:

 A. 暴露了聚合类的内部表示,使其数据不安全;

 B. 增加了客户的负担。

 “迭代器模式”能较好地克服以上缺点,它在客户访问类与聚合类之间插入一个迭代器,这分离了聚合对象与其遍历行为,对客户也隐藏了其内部细节,且满足“单一职责原则”和“开闭原则”。比如C#中的List就实现了迭代器模式。

2. 定义和特点

(1). 定义:提供一个对象顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

(2). 优点:

 A. 访问一个聚合对象的内容而无须暴露它的内部表示。

 B. 遍历任务交由迭代器完成,这简化了聚合类。

 C. 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。

 D. 增加新的聚合类和迭代器类都很方便,无须修改原有代码。

 E. 封装性良好,为遍历不同的聚合结构提供一个统一的接口。

(3). 缺点:

 增加了类的个数,这在一定程度上增加了系统的复杂性

3. 具体实现

(1). 模式结构

 迭代器模式是通过将聚合对象的遍历行为分离出来,抽象成迭代器类来实现的,其目的是在不暴露聚合对象的内部结构的情况下,让外部代码透明地访问聚合的内部数据。

 A. 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。

 B. 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。

 C. 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。

 D. 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

结构如下图:

(2). 使用场景

  见下面代码模拟。

(3). 代码实操

聚合接口和具体的聚合类

    /// <summary>
    /// 抽象聚合接口
    /// </summary>
    public interface IAggregate<T>
    {
        public void Add(T obj);
        public void Remove(T obj);

        /// <summary>
        /// 获取聚合对象的迭代器
        /// </summary>
        /// <returns></returns>
        public Iterator<T> GetIterator();

    }
    /// <summary>
    /// 具体聚合
    /// </summary>
    public class ConcreteAggregate<T> : IAggregate<T>
    {
        private List<T> list = new List<T>();

        public void Add(T obj)
        {
            list.Add(obj);
        }
        public void Remove(T obj)
        {
            list.Remove(obj);
        }
        /// <summary>
        /// 获取迭代器
        /// </summary>
        /// <returns></returns>
        public Iterator<T> GetIterator()
        {
            return new ConcreteIterator<T>(list);
        }
   
    }

抽象迭代器和具体的迭代器

   /// <summary>
    /// 抽象迭代器
    /// </summary>
    public interface Iterator<T>
    {
        T First();
        T Next();
        bool HasNext();
    }
    /// <summary>
    /// 具体的迭代器
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ConcreteIterator<T> : Iterator<T>
    {
        private List<T> list = null;
        private int index = 0;
        public ConcreteIterator(List<T> myList)
        {
            this.list = myList;
        }

        public T First()
        {
            return list[0];
        }

        public bool HasNext()
        {
            if (index<list.Count-1)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public T Next()
        {
            T obj =default(T);
            if (this.HasNext())
            {
                obj = list[++index];
            }
            return obj;
        }
    }

测试

{
                //聚合对象添加数据
                IAggregate<string> ag = new ConcreteAggregate<string>();
                ag.Add("001");
                ag.Add("002");
                ag.Add("003");
                ag.Add("004");
                //获取对应的迭代器
                Iterator<string> it = ag.GetIterator();
                //输出数据
                Console.WriteLine($"第一个元素为:{it.First()}");
                Console.WriteLine("剩下的元素为:");
                while (it.HasNext())
                {
                    Console.WriteLine(it.Next());
                }
}

运行结果

4. 适用场景分析

 (1). 当需要为聚合对象提供多种遍历方式时。

 (2). 当需要为遍历不同的聚合结构提供一个统一的接口时。

 (3). 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。

PS: 由于聚合与迭代器的关系非常密切,所以大多数语言在实现聚合类时都提供了迭代器类,因此大数情况下使用语言中已有的聚合类的迭代器就已经够了。

原文链接:https://www.cnblogs.com/yaopengfei/p/13489597.html

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java基础】020 -- 常见API

目录 一、游戏打包exe 二、Math 1、Math类的常用方法 ①、代码实现 2、小结 3、练习 ①、练习一&#xff1a;改进判断一个数是否为一个质数 ②、练习二&#xff1a;自幂数 三、System 1、时间原点 2、常用方法 3、课堂练习 4、注意事项 5、小结 四、Runtime 1、常用方法 2、练习…

微服务项目【商品秒杀接口压测及优化】

生成测试用户 将UserUtils工具类导入到zmall-user模块中&#xff0c;运行生成测试用户信息&#xff0c;可根据自身电脑情况来生成用户数量。 UserUtils&#xff1a; package com.xujie.zmall.utils;import com.alibaba.nacos.common.utils.MD5Utils; import com.fasterxml.j…

【黄啊码】我问ChatGPT如何学习PHP语言,它是这么说的

大家好&#xff0c;我是黄啊码&#xff0c;最近大家都在流行整chatGPT&#xff0c;今天它来了&#xff01;别人都在吹嘘它万能&#xff0c;能够代替程序员写代码&#xff0c;今天我们就让它教教我们学习PHP语言&#xff1a; 黄啊码&#xff1a; 如何有效学习php语言&#xff1…

关于剩余电流动作继电器在配电系统中的应用探讨

摘 要&#xff1a;据了解&#xff0c;我国每年剩余电流动作继电器&#xff08;RCD&#xff09;的使用量超过2.7亿台&#xff0c;属于CCC认证产品&#xff0c;广泛应用于住宅、办公、商业、酒店、学校等民用建筑和数据中心及工业场所。 现在剩余电流动作继电器依据的标准是GB/T…

vue项目如何使用 SheetJS(xlsx)插件?

简言 SheetJS是一款非常好用的前端处理表格文件的工具。它分社区版和专业版&#xff0c;我们今天来介绍如何简单使用它的社区版。 SheetJS社区版官网 介绍 你应该打开官网浏览具体使用详情。 安装 打开官网在如上图的Installation板块中可以找到各种运行模块的使用方式。 …

MongoDB 覆盖索引查询

MongoDB 覆盖索引查询 官方的MongoDB的文档中对覆盖查询做了说明&#xff1a; 所有的查询字段是索引的一部分所有的查询返回字段在同一个索引中 由于所有出现在查询中的字段是索引的一部分&#xff0c; MongoDB 无需在整个数据文档中检索匹配查询条件和返回使用相同索引的查询…

【半监督医学图像分割 2023 CVPR】UCMT 论文翻译

文章目录【半监督医学图像分割 2023 CVPR】UCMT 论文翻译摘要1. 介绍2. 相关工作2.1 半监督学习2.2 半监督分割2.3 不确定性引导的半监督语义分割3. 方法3.1 问题的定义3.2 总览3.3 协作式均值教师3.4 不确定性指导混合4. 实验和结论5. 总结【半监督医学图像分割 2023 CVPR】UC…

webpack(4版本)使用

webpack简介&#xff1a;webpack 是一种前端资源构建工具&#xff0c;一个静态模块打包器(module bundler)。在 webpack 看来, 前端的所有资源文件(js/json/css/img/less/...)都会作为模块处理。它将根据模块的依赖关系进行静态分析&#xff0c;打包生成对应的静态资源(bundle)…

sentinel持久化方案

一.sentinel规则推送原理 1.原有内存规则存储原理 (1)dashborad中请求到服务器后&#xff0c;在controller中通过http把规则直接推送给client&#xff0c;client接收后把规则放入内存&#xff1b; 2.持久化推送规则原理 ![在这里插入代码片](https://img-blog.csdnimg.cn/1…

质量保障体系建设演进案例

在业务早期发展阶段&#xff0c;主要是产品驱动、研发和测试互相配合。不同的测试方法是验证和保障交付质量的手段&#xff0c;而不是构建质量体系的基石。测试的努力带来的更多是一些“安全感”&#xff0c;而非安全保障。因此&#xff0c;要做到高质量的交付&#xff0c;就需…

k8s简单搭建

前言 最近学习k8s&#xff0c;跟着网上各种教程搭建了简单的版本&#xff0c;一个master节点&#xff0c;两个node节点&#xff0c;这里记录下防止以后忘记。 具体步骤 准备环境 用Oracle VM VirtualBox虚拟机软件安装3台虚拟机&#xff0c;一台master节点&#xff0c;两台…

Wails简介

https://wails.io/zh-Hans/docs/introduction 简介 Wails 是一个可让您使用 Go 和 Web 技术编写桌面应用的项目。 将它看作为 Go 的快并且轻量的 Electron 替代品。 您可以使用 Go 的灵活性和强大功能&#xff0c;结合丰富的现代前端&#xff0c;轻松的构建应用程序。 功能…

mac上安装mysql

mac上安装mysql1. 关于Linux上安装mysql2. 下载安装2.1 下载2.2 安装3. 客户端连接mysql3.1 先查看mysql服务3.2 连接mysql客户端3.2.1 终端使用命令连接3.2.2 可视化工具连接3.3 其他简单操作&#xff08;启动服务等&#xff09;3.3.1 可视化界面操作4. 配置环境变量4.1 配置环…

视图、索引、存储过程、触发器

视图、索引、存储过程、触发器 group by补充&#xff1a; 规范来说&#xff0c;分组查询中&#xff0c;select后的字段只能是group by的字段或者是聚合函数。mysql在这有一个小优化&#xff0c;分组后如果某个字段的所有记录相同&#xff0c;同样可以select。 视图 视图是虚拟…

一文详解java.nio.ByteBuffer

java.nio.ByteBuffer是一个可以进行缓冲区分配、读取和写入的缓冲区&#xff0c;其持有一个字节数组&#xff0c;并通过4个属性&#xff1a;capacity、limit、position、mark来管理缓冲区&#xff0c;进行字节级别读取和数据写入。基于此&#xff0c;ByteBuffer常被用来处理网络…

MySql数据库约束

概述、目的 概念&#xff1a;约束是作用于表中字段上的规则&#xff0c;用于限制存储在表中的数据。 目的&#xff1a;保证数据库中数据的正确性、有效性和完整性。 分类&#xff1a; 约束描述关键字非空约束限制该字段的数据不能为nullNOT NULL唯一约束保证该字段的所有数据都…

【闲聊杂谈】高并发下基于LVS的负载均衡

1、使用http协议进行网络请求 在前几年公布的用户入网数据中&#xff0c;移动入网的数量已经达到六七亿的规模&#xff0c;固网用户数也达到三至五个亿。想要解决这么大并发访问的场景&#xff0c;有多种的解决方案&#xff0c;常规有基于4层的&#xff0c;也有基于7层的。这个…

ChatGPT提示语编写指南

ChatGPT AI 对话模型自 2022 年 11 月下旬开始可用&#xff0c;此后用户一直在探索聊天机器人的局限性和功能。 然而&#xff0c;OpenAI 也在不断地进行调整&#xff0c;因此 ChatGPT 处于不断变化的状态。 但是我们在这个小指南中描述的提示应该是永恒的。 要获得想要的结果&…

SqlSession 和 SqlSessionTemplate 简单使用及注意事项

1、SqlSession 简单使用 先简单说下 SqlSession 是什么&#xff1f;SqlSession 是对 Connection 的包装&#xff0c;简化对数据库操作。所以你获取到一个 SqlSession 就相当于获取到一个数据库连接&#xff0c;就可以对数据库进行操作。 SqlSession API 如下图示&#xff1a;…

基于 CentOS7 的 KVM 部署 + 虚拟机创建

目录一、实验环境二、部署 KVM三、创建虚拟机四、远程管理 KVM 虚拟机FAQ一、实验环境 实验环境&#xff1a;VMware Workstation 16 Pro 打开虚拟机之前&#xff0c;首先开启 VMware Workstation Pro 16 上的硬件辅助虚拟化功能&#xff0c;如下图所示&#xff1a; 二、部署 …