# studydesignPatterfollowxiaofuge **Repository Path**: liu_yu_hang123/studydesign-patterfollowxiaofuge ## Basic Information - **Project Name**: studydesignPatterfollowxiaofuge - **Description**: 最近跟着京东架构师小傅哥一起来好好手敲一下设计模式,动手实践一下,好好准备秋招,结合小傅哥的pdf文档。 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 2 - **Created**: 2022-07-26 - **Last Updated**: 2026-04-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # studydesignPatterfollowxiaofuge #### 介绍 最近跟着京东架构师小傅哥的视频课程一起来好好手敲一下设计模式,动手实践一下,好好准备秋招,顺便结合小傅哥的pdf文档一起查漏补缺。 只记住设计模式的理论是不行的,工程化的东西实践的经验才是最宝贵的精神财富。 #### 六大设计原则 ## 第一讲 设计模式介绍(23种设计模式) 问题:设计模式是什么?谁发明了设计模式?设计模式有那些种类?该如何学习设计模式? 1.设计模式是系统设计中针对场景的一种解决方案,可以解决功能逻辑开发中的共性问题。 2.模式的概念是由克里斯托佛.亚历山大在其著作建筑语言中首次提出。本书介绍了城市设计的语言,而此类语言的基本单元就是模式。 3.设计模式大体上可以分为3类: - 创建型模式:工厂方法、抽象工厂、生成器、原型、单例(5种),这类模式提供创建对象的机制,能够提升已有代码的灵活型和可复用性 - 结构型模式:适配器、代理、桥接、组合、装饰、外观、享元(7种),这类模式介绍如何将对象和组装成较大的结构,并同时保持劫夺的灵活和高效 - 行为模式:责任链、命令、迭代器、中介者、备忘录、观察者、状态、策略、模板、访问者(10种),这类模式负责对象间的高效沟通和职责委派 4.跟着小傅哥视频一起敲代码。 ## 第二讲 单一职责原则 - 原则定义:一个类应该只有一个发生变化的原因 - 模拟场景:视频软件访问过程中的访问用户、普通用户、VIP用户 - 编码实现:ifelse、判断实现、不易维护 - 单一职责原则(SRP:single responcibility principal):又称单一功能原则,面向对象五个基本原则之一 - 实践改进前的方案如turorials-2.1.0所示,由ifelse来实现三种访客逻辑 - 改进后,提取出公共的接口,分别由三个类来实现接口,最后将其中的接口类分别实现,最后通过测试,方便后续的拓展 - 对应于代码框架的tutorials-2.1.0和2.1.1 在git过程中,发现报以下错误error: 'tutorials-2.1.0/' does not have a commit checked out 经检查发现是由于该路径下有以.开头的隐藏目录和文件,所以导致该错误,所以我们通过使用rm -rf递归的删除该路径,能够正常的提交 ## 第三讲 开闭原则 - 原则定义:开闭原则规定软件中的对象(类、模块、函数等等)应该对于扩展是开放的,但是对于修改是封闭的。可以对原有的方法进行重写 - 开闭原则:扩展开放,修改封闭 - 模拟场景:面积计算、三角形、圆形 - 编码实现:破坏方法、继承扩展 - 对应于代码框架中的2.2.0 ## 第四讲 里氏替换原则 - 里氏替换原则,继承必须确保超类所拥有的性质在子类中依然成立。 - 里氏替换原则:兼容性、维护性、扩展性 - 模拟场景:信用卡、储蓄卡、地铁卡、饭卡 - 编码实现:银行卡类、正向、逆向 - 就是继承后,不能对原有的方法进行重写,里氏替换前后,使用父类对子类进行替换,然后用子类调用父类方法,仍然获得相同的输出 - 对应于代码框架中的2.3.0和2.3.1 ## 第五讲 迪米特法则 - 迪米特法则:意义在于降低类之间的耦合。由于每个对象尽量减少对其他对象的了解,因此,很容易是的系统的功能模块功能独立, - 相互之间不存在或很少有依赖关系。 - 迪米特法则:最少知道,减少依赖 - 模拟场景:学生成绩和排名,校长、教师、学生、成绩、排名 - 编码实现:高内聚、低耦合 - 即逻辑上要实现,校长管理老师,从老师那里获取到学生总分和班级平均成绩,让校长不用知道学生的具体信息,而让老师去管理学生信息 - 对应于代码框架中的2.4.0和2.4.1 ## 第六讲 接口隔离原则 - 接口隔离原则:要求程序员尽量将臃肿庞大的接口拆分为更小的和更具体的接口,让接口中只包含客户感兴趣的方法 - 接口隔离原则:更小的接口、更具体的接口 - 模拟场景:射箭、隐袭、沉默、眩晕 - 编码实现:高内聚、低耦合 ## 第七讲 依赖倒置原则 - 依赖倒置原则:程序要依赖于抽象接口,不要依赖于具体实现。简单拿的说就是要求对抽象进行编程,不要对实现进行编程, - 这样就降低了客户与实现模块间的耦合 - 依赖倒置原则:依赖接口、降低耦合 - 模拟场景:随机抽奖、权重抽奖 - 编码实现、高内聚、低耦合 ## 第八讲 设计模式如何落地 - 通过真实的业务场景来对设计模式进行理解,进行图画讲解 - 为何使用,但是拓展和修改代码的时候,写代码时一定要有特别的设计 - 研发不只是写代码,各方各面都要调节好,产品需要,产品pid,分块领域,分块上下文,高内聚,低耦合 ### 创建型模式 ## 第九讲 工厂方法模式(正式开启设计模式学习) - 工厂模式又称工厂方法模式,是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。 - 这种设计模式也是Java开发中最常见的一种模式,它的主要意图时定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类, - 工厂模式使其创建过程延迟到子类进行。 - 简单说就是为了提供代码结构的扩展性,屏蔽每一个功能类中的具体实现逻辑。让外部可以更加简单的只是知道调用即可,同时,这也是 - 去掉众多ifelse的方式。当然这可能也有一些缺点,比如需要实现的类非常多,如何去维护,怎么减低开发成本。但这些问题都可以在后续的 - 设计模式结合使用中,逐步降低。 - 模拟场景:为了可以让整个学习的案例更加贴近实际开发,这里模拟互联网中的营销场景下的业务。由于营销场景的复杂、多变、临时的特性, - 它所需要的设计需要更加的深入,否则会经常面临各种紧急CRUD操作,从而让代码结构混乱不堪,难以维护。 - 在营销场景中经常会有某个用户做了一些操作;打卡、分享、留言、邀请注册等等,进行积分返利,最后通过积分再兑换奖品,从而促活和拉新。 - 那么我们在这里模拟积分兑换中的发放多种类型商品,假如现在我们有如下三种类型的商品接口:优惠券,实物商品,第三方爱奇艺兑换卡 - 从以上信息看:三个接口返回类型不同,有对象类型,布尔类型,还有一个空类型,入参不同,发放优惠券需要防重,兑换卡需要id,实物商品需要发货位置 - 另外可能会随着后续的业务发展,会新增其他商品类型。因为你所有的开发需求都是随着业务对市场的扩展而带来的。 - 从上到下的优化来看,工厂方法并不复杂,甚至这样的开发结构在你有所理解后,会更加的简单了 - 那么这样的开发的好处知道后,也可以总结出来它的优点;避免创建者与具体的产品逻辑耦合、满足单一职责,每个业务逻辑实现都在所属自己的 - 类中完成、满足开闭原则,无需更改使用调用方就可以i在程序中引入新的产品类型。但这样也会带来一些问题,比如,有非常多的奖品类型,那么 - 实现的子类会极速扩张。因此也需要使用其他的模式进行优化,这些在后续的设计模式中会逐渐涉及到。 - 从案例入手看设计模式往往比理论学的更加容易,因为案例是缩短理论到上手的最佳方式,如果你已经有所收获,一定要去尝试实操。 ## 第十讲 抽象工厂模式(工厂的工厂方法) - 工厂模式分类对比介绍: - 简单工厂:提供方法的工厂 - 工厂方法:提供工厂的方法 - 抽象工厂:提供组合工厂的方法 - 抽象工厂:围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 - 模拟场景:替换Redis双集群升级,代理类抽象场景。 - 抽象工厂模式与工厂方法模式虽然主要意图都是为了解决,接口选择的问题。但在实现上,抽象工厂是一个中心工厂,创建其他工厂的模式。 - 可能在平常的业务开发中很少关注这样的设计模式或者类似的代码结构,但是这种场景确实一致在我们身边 - 场景介绍:很多时候初期业务的蛮荒发展,也会牵动者研发对系统的建设。 - 预估QPS较低、系统压力较小、并发访问不大、近一年没有大动作等等。在考虑时间投入成本的前提前,并不会投入特别多的人力去构建非常完善的系统。就像对 - Redis的使用,往往可能只要是单机的就可以满足现状。 - 但随着业务超过预期的快速发展,系统的负载能力也要随着跟上。原有的单机Redis已经满足不了系统需求。这时候就需要更换为更为健壮的Redis集群服务, - 虽然需要修改但是不能影响目前系统的运行,还要平滑的过渡过去。 - 随着这次升级,可以预见的问题会有: - 1、很多服务用到了Redis需要一起升级到集群。 - 2、需要兼容集群A和集群B,便于后续的灾备。 - 3、两套集群提供的接口和方法各有差异,需要做适配。 - 4、不能影响到目前正常运行的系统。 - 总结:抽象工厂模式,所要解决的问题就是在一个产品族,存在多个不同类型的产品(Redis集群、操作系统)情况下,接口选择问题。而这种场景在业务开发中也是非常多见的, - 只不过可能有时候没有将他们抽象化出来。 - 你的代码只是被fielse埋上了!当你知道什么场景下何时可以被抽象工程优化代码,那么你的代码层次结构以及满足业务需求上,都可以得到很好的完成功能实现并提升扩展性和优雅度 - 那么这个设计模式满足了;单一职责、开闭原则、解耦等优点,但如果说随着业务的不断拓展,可能会造成类实现上的复杂度。但也可以说算不上缺点,因为可以随着其他设计方式的引入和代理类 - 以及自动生成加载的方式降低此项缺点。 ## 第十一讲 建造者模式 - 建造者模式:指将一个复杂的对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。 - 模拟场景:各项装修物料组合套餐选配场景 - 建造者模式所完成的内容就是通过将多个简单对象通过一步步的组成构建出一个复杂对象的过程。 - 例如完王者荣耀时的初始化界面;有三条路、有树木、有野怪、有守卫塔等等,甚至依赖于你的网络情况会控制清晰度。而当你换一个场景进行其他不同模式的选择时,同样会建设道路、树木、野怪等 - 但是他们的摆放和大小都有不同。这里就可以用到建造者模式来初始化有意元素 - 而这样的根据相同的物料,不同的组装所产生的具体的内容,就是建造者模式的最终意图,也就是 - 将一个复杂的构建与其相分离,使得同样的构建过程可以创建不同的表示。 - 具体场景模拟:这里我们模拟装修公司杜宇设计出一些套餐装修服务的场景。 - 很多装修公司都会给出自家的套餐服务,一般有;欧式豪华、轻奢田园、现代简约等等,而这些套餐的后面是不同的商品组合。 - 例如:一级&二级吊顶、多乐士涂料、圣象地板、马可波罗地砖等等,按照不同的套餐的价格选取不同的品牌组合,最终再按照装修面积给出一个整体的报价 - 这里我们就模拟出公司想推出的一些套餐服务,按照不同的价格设定品牌选择组合,以达到使用建造者模式的过程 - 建造者模式主要解决的问题是在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的过程来构成;由于需求的变化, - 这个对象的各个部分经常面临着重大的变化,但是价格呢它们组合在一起的过程却相对稳定。 - 总结:通过上面对建造者模式的使用,已经可以摸索出一点心得。那就是什么时候会选择这样的设计模式,当:一些基本物料不会变,而其组合经常变化的时候,就可以选择这样的设计模式来构建代码。 - 此设计模式满足了单一职责原则以及可复用的技术、建造者独立、可扩展、便于控制细节风险。但同时出现特别多的物料以及很多的组合后,类的不断扩展也会造成难以维护的问题。但这种设计结构模型 - 可以把重复的内容抽象到数据库中,按照需要配置。这样就可以减少代码中大量的重复。 - 设计模式能带给你的是一些思想,但在平时的开发中怎么样清晰的提炼出符合此思路的建造模块,是比较难的。需要经过一些锻炼和不断的承接更多的项目,从而获得这部分经验。有的时候你的带啊吗写的好 - 往往是倒逼的,复杂的业务频繁的变化,不断的挑战! ## 第十二讲 原型模式 - 原型模式:是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 - 模拟场景:上机考试多套试卷,每个人的题目合答案都是乱序排列的场景。 - 原型模式主要解决的问题就是创建重复对象,而这部分对象内容本身比较复杂,生成过程可能从库或者RPC接口中获取数据的耗时较长,因此采用克隆的方式节省时间。 - 其实这种场景经常出现在我们的身边,只不过很少用到自己的开发中,就像ctr+c或ctrl+v - 具体场景:每个人都经历过考试,从纸质版到上机答题,大大小小的也有几百场。而以前坐在教室里答题身边的人都是一套试卷,考试的时候还能偷摸或者别人给发信息抄一抄答案。 - 但从一部分可以上机考试的内容开始,在保证大家的公平性一样的题目下,开始出现试题混排更有做的好的答案选项也混排。这样大大的增加了抄袭成本,也更好的做到了考试的公平性。 - 总结:以上的实际场景模拟了原型模式在开发中重构的作用,但是原型模式的使用频率确实不是很高。如果有一些特殊场景需要使用到,也可以按照此设计模式进行优化 - 另外原型设计模式的优点包括;便于通过克隆方式创建复杂对象、也可以避免重复初始化操作、不需要与类中所属的其他类耦合等。但也有一些缺点如果对象中包括了循环引用的克隆, - 以及类中深度使用对象的克隆,都会使此模式变得异常麻烦。 - 终究设计模式是一整套的思想,在不同的场景合理的运用可以提升整体的架构质量。永远不要想着去凑设计模式,否则将会引起过度设计,以及承接业务反复变化的需求时造成浪费的开发和维护成本 - 初期是代码的优化,中期是设计模式的使用,后期是把控全局服务的搭建。不断的加强自己对全局能力的把控,也加深自己对细节的处理。可上可下才是程序员最佳处理方式,选取做合适的才是最好的选择。 ## 第十三讲 单例模式(重点考察内容) - 单例模式:是Java中最简单的设计模式之一。这种类型的设计模式属于创建模式,他提供了一种创建对象的最佳方式。 - 单例模式主要解决的是,一个全局使用的类频繁的创建和消费,从而提升整体的代码性能。 - 7种单例模式案例,Effective Java作者推荐枚举单例模式 - 单例模式原则: - 1、私有构造(阻止类被通过常规方法实例化) - 2、以静态方法或者枚举返回实例(保证实例的唯一性) - 3、确保实例只有一个,尤其是多线程环境。(保证在创建实例时的线程安全) - 4、确保反序列化时不会重新构建对象。(在有序列化反序列化的场景下防止单例被莫名破坏,造成未考虑到的后果) - 单例模式处理手段: - 主动处理: - 1、synchroinized - 2、volatile - 3、cas - JVM机制: - 1、由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时 - 2、访问final字段时 - 3、在创建线程之前创建对象时 - 4、线程可以看见它将要处理的对象时 - 实现方式:饿汉模式、懒汉模式、双重锁懒汉模式、静态内部类模式、枚举模式 - 总结:虽然只是一个很正常的单例模式,但在各种实现上真的可以看到java的基本功的体现,这里包括了;饿汉,懒汉、线程是否安全、静态类 - 加锁、串行化等等 - 在平时的开发中如果可以确保此类是全局可用不需要做懒加载,那么直接创建并给外部调用即可。但如果是很多的类,有些需要在用户触发一定的条件后 - 才显示,那么一定要用懒加载。线程的安全上可以按需选择。 ### 结构型模式 ## 第十四讲 适配器模式 - 适配器模式:(有时候也称为包装模式或者包装)将一个类的接口适配成用户所期待的。一个适配允许通常因为接口不兼容而不能在一起工作的类工作在一起, - 做法是将类自己的接口包裹在一个已存在的类中。 - 模拟场景:从多个MQ消息体中,抽取指定字段值场景 - 适配器模式的主要作用就是把原本不兼容的接口,通过适配修改做到统一。使得用户方便使用,就像我们提到的万能充、数据线、MAC笔记本的转换头、 - 出国旅游买个插座等,它们都是为了适配各种不同的口做到的兼容。 - 在业务开发中我们会经常的需要做不同接口的兼容,尤其是中台服务,中台需要把各个业务线的各种类型服务做统一包装,再对外提供接口进行使用。 - 而这在我们平常的开发中也是非常常见的。 - 具体场景描述:随着公司的业务的不断发展,当基础的系统逐步成型以后。业务运营就需要开始做用户的拉新和促活,从而保障DUA的增速以及最终DOI转换。 - 而这时候就会需要做一些营销系统,大部分常见的都是裂变、拉客,例如:你邀请一个用户开户、或者邀请一个用户下单,那么平台就会给你返利, - 多邀多得。同时随着拉新的量越来越多开始设置每月下单都会给首单奖励,等等,各种营销场景。 - 那么这个时候做这样一个系统就会接收到各种各样的MQ消息或者接口,如果一个个的去开发,就会耗费很大的成本,同时对于后期的拓展也有一定的难度。 - 此时就会希望有一个系统可以配置一下就把外部的MQ接入运行,这些MQ就像上面提到的可能注册开户消息、商品下单消息等等。 - 而适配器的思想方式也恰恰可以运用到这里,并且强调一下,适配器不只是可以适配接口往往还可以适配一些属性信息。 - 总结:从上文可以看到不适用适配器模式这些功能同样可以实现,但是使用了适配器模式就可以让代码:干净整洁易于维护、减少大量重复的判断和使用、让 - 代码更加易于维护和拓展。 - 尤其是我们对MQ这样的多种消息体中不同属性的同类的值,进行适配再加上代理类,就可以使用简单的配置方式接入对方提供的MQ消息, - 而不需要大量重复的开发。非常有利于拓展。 - 设计模式的学习过程可能会在一些章节中涉及到其他设计模式的体现,只不过不会重点讲解,避免喧宾夺主。但是在实际使用中,很多设计模式都是综合使用 - 并不会单一出现。 ## 第十五讲 桥接模式(植物嫁接,程序员植发) - 桥接模式:是一种结构型设计模式,可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构,从而能在开发时分别使用。 - 模拟场景:多支付渠道(微信、支付宝)与多支付模式(刷脸、指纹)等场景 - 桥接模式的主要作用就是通过将抽象部分与实现部分分离,把多种可匹配的使用进行组合。说白了核心实现也就是在A类中含有B类的接口,通过构造函数传递B类的实现 - 这个B类就是设计的桥 - 那么这样的桥接模式,在我们平常的开发中有哪些场景: - JDBC多种驱动程序的实现、同品牌类型的台式机和笔记本平板、业务实现中的多类接口同组过滤服务等。这些场景都比较适合桥接模式进行实现,因为在一些组合中如果每一个类都 - 实现不同的服务可能会出现笛卡尔积,而使用桥接模式就可以非常简单。 - 具体场景描述:随着市场的竞争在支付服务行业出现了微信和支付宝还包括了一些其他支付服务,但是对于商家来说并不希望改变用户习惯。就像如果我的地摊 - 只能使用微信或者使用支付宝,那么就会让我的顾客伤心,鸡蛋灌饼也卖不动了。 - 在这个时候就出现了第三方平台来承接各个支付能力,同时使用自家的人脸让用户支付起来更加容易。那么这里就出现了多支付和多模式的融合使用,如果给每一个 - 支付都实现一次不同的模式,即使是继承类也需要开发好多。而且随着后面接入更多的支付服务或支付方式,就会呈现爆炸式的扩展。 - 优化说明:从ifelse方式实现可以看出,这是两种不同类型的相互组合。那么就可以把支付方式和支付模式进行分离通过抽象类依赖实现的方式进行桥接,通过这样的拆分后 - 支付与模式其实是可以单独使用的,当需要组合时候只需要把模式传递给支付即可。 - 桥接模式的关键是选择的桥接点拆分,是否可以找到类似的相互组合,如果没有就不必要非得使用桥接模式。 - 总结:通过模拟微信与支付宝两个字符渠道在不同的支付模式下,刷脸、指纹、密码的组合从而体现了桥接模式在这类场景中的合理运用。 - 简化了代码的开发,给后续的需求迭代增加了很好的扩展性。 - 从桥接模式的实现形式来看满足了单一职责和开闭原则,让每一部分内容都很清晰易于维护和拓展,但如果我们是实现的高内聚的代码,那么就会很复杂。所以在选择重构代码 - 的时候,需要考虑好整体的设计,否则选不到合理的设计模式,将会让代码变得难以开发。 - 任何一种设计模式的选择和使用都应该遵循符合场景为主,不要刻意使用。而且统一场景因为业务的复杂从而可能需要使用到多种设计模式的组合, - 才能将代码设计的更加合理。但这种经验需要从实际的项目中学习经验,并不断的运用。 ## 第十六讲 组合模式 - 组合模式:也称为整体-部分模式,它的宗旨是通过将单个对象和组合对象用相同的接口进行表示,使得客户对单个对象和组合对象的使用具有一致性。 - 模拟场景:营销差异化人群发券,决策树引擎搭建场景。 - 从上图可以看到这些螺丝和螺母,通过一堆的链接组织出一棵结构树。而这种通过把相似对象组合成一组可被调用的结构树对象的设计思路叫作组合模式。 - 这种设计方式可以让你的服务组节点进行自由组合对外提供服务,例如你有三个原子校验功能(A:身份证、B:银行卡、C:手机号)服务并对外提供调用使用。有些调用方需要使用AB组合, - 有些调用方需要使用到CBA组合,还有一些肯恩恶搞只使用三者中的一个。那么这个时候你就可以使用组合模式进行构建服务,对于不同类型的调用方配置不同的组织关系树,而这个树结构 - 你可以配置到数据库中也可以不断的通过图形界面来控制树结构。 - 所以不同的设计模式用在恰当好处的场景可以让代码逻辑非常清晰并易于扩展,同时也可以减少团队新增人员对项目的学习成本。 - 具体场景描述:以上是一个非常简化版的营销规则决策树,根据性别、年龄来发放不同类型的优惠券,来刺激消费起到精准用户促活的目的。 - 虽然一部分小伙伴可能并没有开发过营销场景,但你可能时时刻刻的被营销着。比如你去浏览男性喜欢的机械键盘、笔记本电脑、汽车装饰等等, - 那么就会给你推荐此类的优惠券刺激你来消费。那么如果你购物不多,或者钱不在自己手里。那么你是否打过车,有一段时间经常有小伙伴喊,为什么 - 同样的距离,他就10元,我就15元?其实这些都是被营销的案例,一般对于不常使用软件的小伙伴,经常会进行稍微大力度的促活,增加用户粘性。 - 那么在这里,我们就模拟一个类似的决策场景,体现出组合模式在其中起到的重要性。另外,组合模式不只是可以运用于规则决策树, - 还可以做服务包装将不同的接口进行组合配置,对外提供服务能力,减少开发成本。 - 总结:从以上的决策树场景来看,组合模式的主要解决的是一系列简单逻辑节点或者扩展的复杂逻辑节点在不同街斗的组织下,对于外部的调用是仍然可以非常简单的。 - 这部分设计模式保证了开闭原则,无需更改模型结构你就可以提供新的逻辑节点的使用并配合组织出新的关系树。但如果是一些功能差异化非常大的接口进行包装就会 - 变得比较困难,但也不是不能很好的处理,只不过需要做一些适配和特定化的开发。 - 很多时候因为你的极致追求和稍有倔强的工匠精神,即使在面对同样的业务需求,你能完成出最好的代码结构和最易于扩展的技术架构。不要被远不能给你知道提升能力 - 的影响到放弃自己的追求! ## 第十七讲 装饰器模式 - 装饰器模式:在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。 - 场景:SSO单点登录功能扩展,增加拦截用户访问方法范围场景 - 初看上图感觉装饰器模式优点像俄罗斯套娃、某众汽车,而装饰器的核心就是在不改变原有类的基础撒谎给你给类新增功能。不改变原有类,可能有的小伙伴会想到继承, - AOP切面,当然这些方式都可以实现,但是使用装饰器模式会是另外一种思路,更为灵活,可以避免继承导致的子类过多,也可以避免AOP带来的复杂性。 - 熟悉的场景使用到装饰器: - new Bufferedreader(new FileReader(""));这段代码在学习java开发字节流、字符流、文件流的内容时经常用到,这样一层嵌套一层,字节流转字符流等等 - 而这样的方式使用就是装饰器模式的一种体现。 - 具体场景描述:在本案例中我们模拟一个单点登录功能扩充的场景。 - 一般在业务开发初期,往往内部的ERP使用只需要判断账户验证即可,验证通过后即可访问ERP的所有资源。但随着业务的不断发展,团队里开始出现专门的运营人员、 - 营销人员、数据人员,每个人员对于ERP的使用需求不同,有些需要创建活动,有些只是查看数据。同时为了保证数据的安全性,不会让每个用户都有最高的权限。 - 那么以往使用的SSO是一个组件化通用的服务,不能在里面添加需要的用户访问验证功能。这个时候我们就可以使用装饰器模式,扩充原有的单点登录服务。 - 但同时也保证原有功能不会破坏,可以继续使用。 - 装饰器主要解决的是直接继承下因功能的不断横向扩展导致子类膨胀的问题,而使用装饰器模式后就会比直接继承显得更加灵活同时这样也就不再需要考虑子类的维护。 - 在装饰器模式中有四个比较重要点抽象出来的点: - 1、抽象构件角色(Component)-定义抽象接口 - 2、具体构建角色(ConcreteComponent)-实现抽象接口,可以是一组 - 3、装饰角色(Decorator)-定义抽象类并继承接口中的方法,保证一致性 - 4、具体装饰角色(ConcreteDecorator)-扩展装饰具体的实现逻辑 - 通过以上这四项来实现装饰器模式,主要核心内容会体现在抽象类的定义和实现上。 - 总结:使用装饰器模式满足单一职责原则,你可以在自己的装饰类中完成功能逻辑的扩展,而不影响主类,同时可以按需在运行使添加和删除这部分的逻辑。 - 另外装饰器模式与继承父类重写方法,在某些时候需要按需选择,并不一定某一个就是最好。 - 装饰器实现的重点是对抽象类继承接口方式的使用,同时设定被继承的接口可以通过构造函数传递其实现类,由此增加扩展性并重写方法里可以实现此部分父类实现的功能。 - 就像夏天热你穿短裤,冬天冷你穿棉裤,雨天挨浇你穿雨衣一样,你的根本本身没有被改变,而你的需求却被不同的装饰而实现。生活中往往比比皆是设计,当你可以融合 - 这部分活灵活现的例子到代码实现中,往往会创造出更加优雅的实现方式。 ## 第十八讲 外观模式 - 外观模式:隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构性模式,它向现有的系统添加了一个接口,来隐藏系统的复杂性。 - 模拟场景:基于SpringBoot开发门面模式中间件,统一控制接口白名单场景 - 外观模式也称门面模式,主要解决的是降低调用方的使用接口的复杂逻辑组合。这样调用方与实际的接口提供方提供了一个中间层,用于包装逻辑提供Api接口。有些时候外观模式也被用在 - 中间件层,对服务中的通用性复杂逻辑进行中间件层包装,让使用方可以只关心业务开发。 - 那么这样的模式在我们的所见产品功能中也经常遇到,就像几年前我们注册一个网站时候往往要添加很多信息,包括;姓名、昵称、手机号、QQ、邮箱、住址、单身等等,但现在注册一个网站的用户 - 只需要一步即可,无论是手机号还是微信号也都提供了这样的登录服务。而对于服务端应用开发来说以前是提供了一整套的接口,现在注册的时候并没有这些信息,那么服务端就需要进行皆苦包装, - 在前端调用注册的时候服务端获取相应的用户信息(从各个渠道),如果获取不到会让用户后续进行补全(营销不全信息给奖励),以此来拉动用户的注册量和活跃度。 - 具体场景描述:在本案例中我们模拟一个将所有服务接口添加白名单的场景。 - 在项目不断壮大发展的路上,每一次发版上线都需要进行测试,而这部分测试验证一般会进行白名单开量或者切量的方式进行验证。那么如果在每一个接口都添加这样的逻辑,就会非常麻烦且不易维护。 - 另外这是一类具备通用逻辑的共性需求,非常适合开发成组件,以此来治理服务,让研发人员跟多的关心业务功能的开发。 - 一般情况下对于外观模式的使用通常时用在复杂或多个接口进行包装统一对外提供服务上,此种方式也相对见到那在我们平常的业务开发中也是最常用的。你可能经常听到把这两个接口包装一下, - 但在本例子中我们把这种设计思路方都中间件层,让服务变得可以统一控制。 - SpringBoot的starter中间件开发方式,面向切面编程和自定义注解的使用,外部自定义配置信息的透传,SpringBoot与Spring的不同,对于此类方式获取白名单配置存在差异 - 总结:以上我们通过中间件的方式实现外观模式,这样的设计可以很好的增强代码的隔离性,以及复用性,不仅使用上非常灵活也降低了每一个系统都开发这样的服务带来的风险。 - 可能目前你看这只是非常简单的白名单控制,是否需要这样的处理。但往往一个小小的开始会影响着后续无线的扩展,实际的业务开发往往也要复杂的很多,不可能如此简单。因而使用 - 设计模式来让代码结构更加干净整洁。 - 很多时候不是设计模式没有用,而是自己编程开发经验不足导致即使学了设计模式也很难驾驭。毕竟这些知识都是经过一些实际操作提炼出来的精华,但如果你可以按照本系列文章中 - 的案例方式进行实操,还是可以增强这部分设计能力的。 ## 第十九讲 享元模式 - 享元模式:主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构哦型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。 - 模拟场景:基于Redis秒杀,提供活动与库存信息查询场景。 - 享元模式,主要在于共享通用对象,减少内存的使用,提升系统的访问效率。而这部分共享对象通常比较耗内存或者需要查询大量接口或者使用数据库资源,因此统一抽离作为共享对象使用。 - 另外享元模式可以分为服务端和客户端,一般互联网H5和Web场景下大部分数据都需要服务端进行处理,比如数据库连接池的使用、多线程线程池的使用,除了这些功能外,还有需要服务端 - 进行包装后的处理下发给客户端,因为服务端需要做享元处理。但在一些游戏场景下,很多都是客户端需要进行渲染地图效果,比如,树木,花草,鱼虫,通过设置不同元素描述使用享元公用对象,减少内存的占用 - 让客户端的游戏更加流畅。 - 在享元模式的实现中需要使用到享元工厂来进行管理这部分独立的对象和共享的对象,避免出现线程安全的问题。 - 具体场景描述: - 在这个案例中我们模拟在商品秒杀场景下使用享元模式查询优化 - 你是否哦经历过一个商品下单的项目从最初的日均十几单到一个月后每个时段秒杀量破十万的项目。一般在最初如果没有经验的情况下可能会使用数据库行级锁的方式下保证商品库存的扣减操作,但是随着业务 - 的快速发展秒杀用户越来越多,这个时候数据库已经扛不住了,一般都会使用redis的分布式锁来控制商品库存 - 同时查询的时候也不需要每次对不同的活动查询都从库中获取,因为这里除了库存以外其他的活动商品信息都是固定不变的,以此这里一般大家都会缓存到内存中 - 这里我们模拟使用享元模式工厂结构,提供活动商品的查询。活动商品相当于不变的信息,而库存部分属于变化的信息。 - 总结:关于享元模式的设计可以着重学习享元工厂的设计,在一些有着大量重复对象可复用的场景下,使用此场景在服务端减少接口的调用, - 在客户端减少内存的占用,是这个设计模式的主要应用方式。 - 另外通过map结构的使用方式可以看到,使用一个固定id来存放和获取对象,是非常关键的点。而且不只是在享元模式中使用,一些其他工厂模式、适配器模式、组合模式中都可以通过map - 结构存放服务供外部获取,减少ifelse的判断使用。 - 当然除了这种设计的减少内存的使用优点外,也有它带来的缺点,在一些复杂的业务处理场景,很不容易区分出内部和外部状态,就像我们活动信息部分库存变化部分。如果不能很好的拆分, - 就会把享元工厂设计的非常混乱,难以维护。 ## 第二十讲 代理模式 - 代理模式:一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。 - 模拟场景:模拟mybatis-spring中定义DAO接口,使用代理类方式操作数据库原理实现场景。 - 代理模式有点像老大和小弟,也有点像分销商。主要解决的是问题是为某些资源的访问、对象的类的易用操作上提供方便使用的代理服务。而这种设计思想的模式经常会出现在我们的系统中, - 或者你用到过的组件中,它们都会提供给你一种非常简单易用的方式来控制原本你需要编写很多的代码的进行使用的服务类。 - 类似的场景可以想到: - 1.你的数据库访问层面经常会提供一个较为基础的应用,以此来减少应用服务扩容时不至于数据库链接数暴增。 - 2.使用过的一些中间件例如;RPC框架,在拿到jar包对接口的描述后,中间件会在服务启动的时候生成对应的代理类,当调用接口的时候,实际是通过代理类发出的socket信息进行通过。 - 3.另外像我们常用的mybatis,基本是定义接口但是不需要写实现类,就可以对xml或者自定义注解里的sql语句进行增删改查操作。 - 具体场景介绍: - 在本案例中我们模拟实现mybatis-spring中代理类生成部分 - 对于mybatis的使用中只需要定义接口不需要写实现类就可以完成增删改查操作,有疑问的小伙伴,在本章节中就可以学习到这部分知识。解析下来我们会通过实现一个这样的代理类交给spring - 管理的核心过程来讲述代理类模式。 - 这样的案例场景在实际的业务开发中其实不多,因为这是将这种思想运用在中间件开发上,而很多小伙伴经常是做业务开发,所以对Spring Bean的定义以及注册和代理 - 以及反射调用的知识了解的相对较少。但是可以通过本章节作为一个入门学习,逐步了解。 - 总结:关于这部分代理模式的讲解我们采用了开发一个关于mybatis-spring中间件中部分核心功能来体现代理模式的强大之处,所以涉及到了一些关于代理类的创建以及spring中 - bean的注册这些知识点,可能在平常的业务开发中都是很少用到的,但是在中间件开发中确实非常常见的操作。 - 代理模式除了开发中间件外还可以对服务的包装,物联网组件等等,让复杂的各项服务变为轻量级调用、缓存使用。你可以理解为你家里的电灯开关, - 我们不能操作220V电线的人肉连接,但是可以使用开关,避免触电。 - 代理模式的设计方式可以让代码更加整洁、干净易于维护,虽然在这部分开发中额外增加了很多类也包括了自己处理bean的注册等,但是这样的中间件复用性极高 - 也更加智能,可以非常方便的扩展到各个服务应用中。 ### 行为模式 ## 第二十一讲 责任链模式 - 责任链模式:为了避免请求发送者与国歌请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链; - 当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。 - 模拟场景:模拟618电商大促期间,项目上线流程多级负责人审批场景 - 击鼓传雷,看上图你是否想起周星驰有一个大电影,大家坐在海边围成一个圈,拿着一个点燃的炸弹,互相传递。 - 责任链模式的核心就是解决一组服务中的先后执行处理关系,就有点像你没钱花了,需要家庭财务支出审批,10块钱以下找闺女审批,100块钱先闺女审批再媳妇儿审批。 - 你可以理解想象成当你要跳槽的时候被安排的明明白白的被各个领导签字放行。 - 具体场景描述: - 在本案例中我们模拟在618大促期间的业务系统上线审批流程场景 - 像是这些一线电商类的互联网公司,阿里、京东、拼多多等等,在618期间都会做一些运营活动场景以及提供的扩容备战,就像过年期间百度的红包一样。但是所有开发的这些系统都需要陆续的上线,因为临近618 - 有时候也有一些紧急的调整需要上线,但为了保障线上系统的稳定性是尽可能的减少上线的,也会相应的增强审批力度。就像一级响应、二级响应一样。 - 而这审批的过程在随着特定时间点会增加不同级别的负责人加入,每个人就像责任链模式中的每一个核心点。对于研发小伙伴并不需要关心具体的审批流程处理细节,只需要知道这个上线梗严格,级别也更高, - 但是对于研发人员来说同样是点击相同的提审按钮,等待审核。 - 责任链模式可以让各个服务模块更加清晰,而每一盒模块间可以通过next的方式进行获取。而每一个next是由继承的统一抽象类实现的。最终所有类的职责 - 可以动态的进行编排使用,编排的过程可以做成可配置化。 - 总结:从上面代码从if语句重构到使用责任链模式开发可以看到,我们的代码结构变得清晰干净了,也解决了大量if语句的使用。并不是if语句不好,只不过if语句并不适合做系统 - 流程设计,但是在做判断和行为逻辑处理中还是非常可以使用的。 - 在我们前面学习结构性模式中讲到过组合模式,它像一颗组合树一样,我们搭建出了一个流程决策树。其实这样的模式也是可以和责任链模式惊醒组合扩展使用,而这部分的重点在于如何关联链路的 - 关联,最终的执行都是在执行在中间的关系链。 - 责任链模式很好的处理单一职责原则和开闭原则,简单了耦合也使对象关系更加清晰,而且外部的调用方并不需要关心责任链是如何进行处理的(以上程序中可以把责任链的组合进行包装,在提供给外部使用)。 - 但除了这些优点外也需要是适当的场景才进行使用,避免造成性能以及编排混乱调试测试疏漏的问题。 ## 第二十二讲 命令模式 - 命令模式:将一个请求封装为一个对象,使发出请求的责任和执行请求过程分割开。这样两者之间通过命令对象进行沟通,便于将命令对象进行存储、传递、调用、增加与管理。 - 模拟高档餐厅八大菜系,小二点单厨师烹饪场景。 - 命令模式在我们通常的互联网开发中来说用的比较少,但是这样的模式在我们的日常生活中却经常使用到,那就是ctrl+c,ctrl+v,。当然如果你开发过一些桌面应用,也会感受到这样设计模式的应用场景。 - 从这样的模式感受上,可以想到这是把逻辑实现与操作请求进行分离,降低耦合方便扩展。 - 命令模式是行为模式中的一种,以数据驱动的方式将命令对象,可以使用构造函数的方式传递给调用者。调用者再提供相应的实现为命令执行提供操作方法。可能会感觉这部分有一些绕 - 可以通过对代码的实现进行理解,在通过实操来熟练。 - 在这个设计模式的实现过程中有如下几个比较重要的点: - 1.抽象命令类:声明执行命令的接口和方法 - 2.具体命令实现类:接口类的具体实现,可以是一组相似的行为逻辑 - 3.实现者:也就是为命令做实现的具体实现类 - 4.调用者:处理命令、实现的具体操作者,负责对外提供命令服务 - 具体场景描述: - 在这个案例中我们模拟在餐厅中点餐交给厨师烹饪的场景 - 命令场景的核心逻辑是调用方与不需要关心具体的逻辑实现,在这个场景中也就是点餐人员只需要把需要点的各种菜系交给小二即可,小二再把各项菜品交给各个厨师进行烹饪。也就是点餐人员不需要 - 跟各个厨师交流,只需要在统一的环境里下达命令就可以了。 - 在这个场景中可以看到有不同的菜品等,山东、四川、江苏、广东、福建、浙江、湖南,每种菜品都会有不同的厨师进行烹饪。而客户并不会去关心具体是水烹饪,厨师也不会去关心谁点的餐。客户只关心早点上菜,厨师只关心还有 - 多少个菜要上,而这中间的衔接过程由小二完成。 - 总结:命令模式可以将上述的模式拆解为三层大块,命令、命令实现者、命令调用者,当有新的菜品或者厨师扩充时候就可以在指定的类结构下进行实现添加即可,外部的调用也会非常的容易扩展。 - 从以上的内容和例子可以感受到,命令模式的使用场景需要分为三个比较大的块;命令、实现、调用者,而这三块内容的拆分也是选择适合场景的关键因素,经过这样的拆分可以让逻辑具备单一职责的性质,便于扩展。 - 通过这样的实现方式与if语句相比,降低了耦合性也方便了其他的命令和实现的扩展。但同时这样的设计模式也带来了一点问题,就是各种命令与实现的组合下,会扩展出很多的实现类,需要进行管理。 - 设计模式的学习一定要勤加练习,哪怕最开始是模仿实现也是可以的,多次的练习后再去找到一些优化的场景,并逐步运用到自己的开发中。提升自己对代码的设计感觉,让代码结构更加清晰易扩展。 ## 第二十三讲 迭代器模式 - 迭代器模式:提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示。 - 模拟场景:模拟公司组织架构树结构关系,深度迭代遍历人员信息输出场景。 - 迭代器模式,常见的就是我们日常使用的iterator遍历。虽然这个设计模式在我们实际业务开发中的场景并不多,但却几乎每天都要使用jdk为我们提供的list集合遍历。另外增强的for循环虽然是循环 - 输出数据,但是他不是迭代器模式。迭代器模式的特点是实现Iterable接口,通过next的方式获取集合元素,同时具备对元素的删除等操作。而增强的for循环是不可以的。 - 这种设计模式的优点是可以让我们以相同的方式,遍历不同的数据结构元素,这些数据结构包括;数组、链表、树等,而用户在使用遍历的时候并不需要去关心每一种数据结构的遍历处理逻辑,从而让使用变得统一易用。 - 具体场景描述:在本案例中我们模拟迭代遍历输出公司中树形结构的组织架构关系中的雇员列表 - 大部分公司的组织架构都是金字塔结构,也就是这种树形结构,分为一级、二级、三级等部门,每个组织部门由雇员填充,最终体现出一个整体的树形组织架构关系。 - 一般我们常用的遍历就是默认提供的方法,对list集合遍历。但是对于这样的偏业务特性较大的树形结构,如果需要使用到遍历,那么就可以自己来实现。接下来我们会把这个组织层次关系通过树形数据 - 结构来实现,并完成迭代器功能。 - 在实现迭代器模式之前,可以先阅读下java中list方法关于iterator的实现部分,几乎所有的迭代器开发都会按照这个模式来实现,这个模式主要分为以下几块; - 1、Collection,集合方法部分用于对自定义的数据结构添加通用方法;add、remove、iterator等核心方法 - 2、Iterable,提供获取迭代器,这个接口类会被Collection继承 - 3、Iterator,提供两个方法;hasNext\next,会在具体的数据结构中写实现方式。 - 迭代器实现思路 - 1、这里的树形结构我们需要做的是深度遍历,也就是左侧的一直遍历到最深的节点。 - 2、当遍历到最深节点后,开始遍历最深节点的横向节点。 - 3、当横向节点遍历完成后,则向上寻找横向节点,直到树形结构全部遍历完成。 - 总结:当迭代器的设计模式从以上的功能实现可以看到,满足了单一职责原则和开闭原则,外界的调用方也不需要知道任何一个不同的数据结构在 - 使用上的遍历差异。可以非常方便的扩展,也让整个遍历变得更加干净整洁。 - 但从结构的实现上可以看到,迭代器的实现过程相对来说比较复杂的,类的实现上也扩增了需要外部定义的类,使得遍历与原数据结构分开,虽然这比较麻烦的,但 - 可以看到在使用java的jdk时候,迭代器的模式还是很好用的,可以非常方便的扩展和升级。 - 以上的设计模式场景实现可能对信任有一些不好理解点,包括:迭代器三个和接口的定义、树型结构深度遍历思路。这些需要反复实现练习才能深入的理解,事必躬亲,亲历亲为 - 才能让自己掌握这些知识。 ## 第二十四讲 中介者模式 - 中介者模式:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式, - 它是迪米特法则的典型应用。 - 按照Mybatis原理手写ORM框架,给JDBC方式操作数据库增加中介者场景 - 中介者模式要解决的就是复杂功能应用之间的重复调用,在这中间添加一层中介者包装服务,对外提供简单、通用、易扩展的服务能力。 - 这样的设计模式几乎在我们的日常生活中和实际业务开发中都会见到,例如,飞机降落在小姐姐在塔台喊话、无论哪个方向来的候车都从站台上下、公司的系统中有一个中台专门 - 为你包装好所有接口和提供统一的服务等等,这些都运用了中介者模式。除此之外,你用到的一些中间件,它们包装了底层多种数据库的差异化,提供非常简单的方式进行使用。 - 具体场景描述: - 在本案例中,我们通过模仿Mybatis手写ORM框架,通过这样操作数据库学习中介者运用场景 - 除了这样的中间件层使用场景外,对于一些外部接口,例如N种奖品服务,可以由中台系统进行统一包装对外提供服务能力。也是中介者模式的一种思想体现。 - 在本案例中,我们会把jdbc层进行包装,让用户在使用数据库服务的时候,可以和使用mybatis一样简单方便,通过这样的源码方式学习中介者模式,也方便对源码知识 - 的拓展学习,增强知识栈。 - 总结:以上通过中介者模式的设计思想我们手写了一个ORM框架,隐去了对数据库操作的复杂度,让外部的调用方可以非常简单的进行操作数据库。这也是我们平时使用的 - Mybatis的原型,在我们日常的开发使用中,只需要按照配置即可非常简单的操作数据库。 - 除了以上这种组件模式的开发外,还有服务接口的包装也可以使用中介者模式来实现。比如你们公司有很多的奖品接口需要在营销活动中对接,那么可以把这些奖品接口统一收到中台开发 - 一个奖品中心,对外提供服务。这样就不需要每一个需要对接奖品的接口,都去找具体的提供者,而是找中台服务即可。 - 在上述的实现和测试使用中可以看到,这种设计模式满足了单一职责和开闭原则,也就是符合了迪米特原则,即越少人知道越好。外部的人只需要按照需求进行调用, - 不需要知道具体的是如何实现的,复杂的一面已经有组件合作服务平台处理。 ## 第二十五讲 备忘录模式 - 备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。 - 模拟场景:模拟互联网系统上线过程,配置文件回滚场景 - 备忘录模式是可以恢复或者说回滚,配置、版本、悔棋为核心功能的设计模式,而这种设计模式属于行为模式。在功能实现上是以不破坏原对象为基础增加备忘录 - 操作类,记录原对象的行为从而实现备忘录模式。 - 这个设计在我们平常的生活或开发中也是比较常见的,比如,后悔药、孟婆汤,IDEA编辑和撤销、小霸王游戏回档。当然还有我们生活中常见的PhotoShop - 具体场景描述: - 在本案例中我们模拟系统在发布上线的过程线上配置文件用于紧急回滚 - 在大型互联网公司系统的发布上线一定是易用、安全、可处理紧急状况的,同时为了可以隔离线上和本地环境,一般会把配置文件抽取出来放到 - 线上,避免有人误操作导致本地的配置内容发布出去。同时线上的配置文件也会在每次变更的时候进行记录,包括;版本号、时间、MD5、内容信息和操作人 - 在后续上线时如果发现紧急问题,系统就会需要回滚操作,如果执行回滚那么也可以设置配置文件是否回滚。因为每一个版本的系统可能会随着带着 - 一些配置文件的信息,这个时候就可以很方便的让系统与配置文件一起回滚操作。 - 我们接下来就使用备忘录模式,模拟如何记录配置文件信息。实际的使用过程中还会将信息存放到库中进行保存,这里暂时只是使用内存记录。 - 备忘录的设计模式实现方式,重点在于不更改原有类的基础上,增加备忘录类存放记录。可能平时虽然不一定非得按照这个设计模式的代码结构来实现自己的需求, - 但是对于功能上可能也完成过类似的功能,记录系统的信息。 - 总结:此种设计模式的方式可以满足在不破坏原有属性类的基础上,扩充了备忘录的功能。虽然和我们平时使用的思路是一样的,但在具体实现上 - 还可以细细品味,这样的方式在一些源码中也有所体现。 - 在以上的实现中我们是将配置模拟存放在内存中,如果关机了会导致配置信息丢失,因为在一些真实的场景中,还是需要存放到数据库中。那么此种存放到内存 - 中进行回复的场景也不是没有,比如;PhotoShop、运营人员操作ERP配置活动,那么也就是及时性的一般不需要存放到库中进行恢复。 - 另外如果是使用内存方式存放备忘录,需要考虑存储问题,避免造成内存大量消耗。 - 设计模式的学习都是为了更好的写出可扩展、可管理、易维护的代码,而这个学习的过程需要自己不断的尝试实际操作,理论的知识 - 与实际结合还有很长一段距离。切记多多上手! ## 第二十六讲 观察者模式 - 观察者模式:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种 - 模式有时又被称作发布订阅模式、模型视图模式,它是对象行为型模式。 - 模拟场景:模拟类似小客车指标摇号过程,监听消息通知用户中签场景。 - 简单来说观察者模式就是当一个行为发生时传递信息给另外一个用户接收做出相应的处理,两者之间没有直接的耦合关联。例如,狙击手,李云龙。 - 除了生活中的场景外,在我们编程开发中也会常用到一些观察者的模式或者组件,例如我们经常使用的MQ服务,虽然MQ服务是有一个通知中心并不是 - 每一个类服务进行通知,但整体上也可以算作是观察者模式的思路设计。再比如可能有做过的一些类似事件监听总线,让主线服务与其他辅线服务分离, - 为了使系统降低耦合和增强扩展性,也会使用观察者模式进行处理。 - 具体场景描述: - 在本案例中我们模拟每次小客车指标摇号事件通知场景(真实的不会由官网给你发消息) - 可能大部分人看到这个案例一定会想到自己每次摇号都不中的场景,收到一个遗憾的短信通知。当然目前的摇号系统并不会给你发短信,而是由百度或者 - 一些其他插件发的短信。那么假如这个类似摇号功能由你开发,并且需要对外部的用户做一些事件通知以及需要在主流程外再添加一些额外的辅助流程 - 时需要如何处理呢? - 基本很多人对于这样的通知事件类的实现比较粗犷,直接在类里面就添加了。一是考虑这可能不会怎么扩展。二是压根就没考虑过。但如果你有仔细 - 思考过你的核心功能就会发现,这里面有一些核心主链路,还有一部分时辅助功能。比如完成了某个行为后需要触发MQ给外部,以及做一些消息PUSH - 给用户等,这些都不算做是核心流程链路,是可以通过事件通知的方式进行处理。 - 从上图可以分为三大块;事件监听、事件处理、具体的业务流程,另外在业务流程中LotteryService定义的是抽象类,因为这样可以通过抽象类将 - 事件功能屏蔽,外部业务流程开发者不需要知道具体的通知操作。 - 总结:从我们最基本的过程式开发以及后来使用观察者模式面向对象开发,可以看到设计模式改造后,拆分出了核心流程的代码。一般代码中的核心流程 - 不会经常变化。但辅助流程会随着业务的各种变化,包括;营销、裂变、促活等等,因此使用设计模式架设代码就显得非常有必要。 - 此种设计模式从结构上是满足开闭原则的,当你需要新增其他的监听事件或者修改监听逻辑,是不需要改动事件处理类的。但是可能你不能控制调用顺序 - 以及需要做一些事件结果的返回继续操作,所以使用的过程需要考虑场景的合理性。 - 任何一种设计模式有时候都不是单独使用的,需要结合其他模式共同建设。另外设计模式的使用是为了让代码更加易于扩展何维护,不能因为添加设计模式 - 而把结构处理更加复杂以及难以维护。这样的合理使用的经验需要大量的实际操作练习而来。 ## 第二十七讲 状态模式 - 状态模式:对有状态的对象,把复杂的判断逻辑提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。 - 模拟场景:模拟系统营销活动,状态流程审核发布上线场景 - 状态模式描述的是一个行为下的多种状态变更,比如我们最常见的一个网站的页面,在你登录与不登录下展示的内容是略有差异的,而这种 - 登录与不登录就是我们通过改变状态,而让整个行为发生了变化。 - 具体场景描述: - 在本案例中我们模拟营销活动审核状态流转场景(一个活动的上线是多个层级审核上线的) - 在上图中也可以看到我们的流程节点中包括了各个状态到下一个状态扭转的关联条件,比如:审核通过才能到活动中,而不能从编辑中直接到活动中,而这些状态 - 的转变就是我们要完成的场景处理。 - 大部分程序员基本都开发过类似的业务场景,需要对活动或者一些配置需要审核后才能对外发布,而这个审核的过程往往会随着系统的重要程度而设立 - 多级控制,来保证一个活动可以安全上线,避免造成资损。 - 当然有时候会用到一些审批流的过程配置,也是非常方便开发类似的流程的,也可以在配置中设定某个节点的审批人员。但这不是我们主要体现点, - 在本案例中我们主要是模拟学习对一个活动的多个状态节点的审核控制。 - 重构的重点往往是处理掉ifelse,而想处理掉ifelse基本离不开接口与抽象类,另外还需要重新改造代码结构。 - 总结:从以上的两种方式对一个需求的实现中可以看到,在第二种使用设计模式处理后已经没有了ifelse,代码的结构也更加清晰易于扩展。这就是设计 - 模式的好处,可以非常强大的改变原有代码的结构,让以后的扩展和维护都变得容易些。 - 在实现结构的编码方式上可以看到这不再是面向过程的编程,而是面向对象的结构。并且这样的设计模式满足了单一职责和开闭原则,当你只有满足这样的结构下 - 才会发现代码的扩展是容易的,也就是增加和修改功能不会影响整体的变化。 - 但如果状态和各项流转较多像本文案例中,就会产生较多的实现类。因此可能也会让代码的实现上带来了时间成本,因为如果遇到这样的场景可以按需评估投入回报率。 - 主要点在于看是否经常修改、是否可以做成组件化、抽离业务与非业务功能。 ## 第二十八讲 策略模式 - 策略模式:指定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它 - 通过对算法的封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。 - 模拟场景:模拟多种营销类型优惠券,折扣金额计算策略场景 - 策略模式是一种行为模式,也就是替代大量ifelse的利器。他能帮你解决的是场景,一般具有同类可替代的行为逻辑算法场景。比如,不同类型的交易方式 - (信用卡,支付宝、微信)、生成唯一ID策略(UUID、DB自增、DB+Redis、雪花算法、Leaf算法)等,都可以使用策略模式进行行为包装,供给外部使用。 - 具体场景描述: - 在本案例中我们模拟在购买商品时候使用的各种类型的优惠券(满减、直减、折扣、n元购) - 这个场景几乎也是大家的一个日常购物省钱渠道,购买商品的时候都希望找一些优惠券,让购买的商品更加实惠。而且到了大促的时候就会有更多的优惠券需要计算 - 那些商品一起购买的更加优惠! - 这样的场景有时候用户用起来还是蛮爽的,但是最初这样的功能的设定以及产品的不断迭代,对于程序员开发还是不太容易。因为这里包括了很多的规则和优惠逻辑 - ,所以我们模拟其中一个计算优惠的方式,使用策略模式来实现。 - 总结:以上的策略模式案例相对来说并不复杂,主要的逻辑都是体现在关于不同种类的优惠券的计算折扣策略上。结构来说也比较简单,在实际的开发中这样的设计模式 - 也是非常有用的。另外这样的设计与命令模式、适配器模式结构相似,但是思路是有差异的。 - 通过策略设计模式的使用可以把我们方法中的if语句优化掉,大量的if语句使用会让代码难以扩展,也不好维护,同时后期遇到各种各样的问题也难维护。在使用这样的 - 设计模式后可以很好的满足隔离性与扩展性,对于不断新增的需求也非常方便承接。 - 策略模式、适配器模式、组合模式等,在一些结构上是比较相似的,但是每一个模式是有自己的逻辑特点,在使用的过程中最佳的方式是经过较多的实践来吸取经验,为后续 - 的研发设计提供更好的技术输出。 ## 第二十九讲 模板模式 - 模板模式:指定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤 - 它是一种类行为型模式。 - 模拟场景:模拟爬虫各类电商商品,生成营销推广海报场景。 - 模板模式的核心设计思路是通过在抽象类中定义抽象方法的执行顺序,并将抽象方法设定为只有子类实现,但不设计独立访问的方法。简单来说也就是把你安排的明明白白的 - 就像西游记的99八十一难,基本每一关都是;师傅被掳走、打妖怪、妖怪被收走,具体什么妖怪你自己定义,怎么打你想办法,最后收走还是弄死看你的本事, - 我只管定义执行顺序和基本策略,具体的每一难由观音安排。 - 具体场景描述: - 在本案例中我们模拟爬虫各类电商商品,生成营销推广海报场景 - 关于模板模式的核心点在于由抽象类定义抽象方法执行策略,也就是说父类规定了好一系列的执行标准,这些标准的串联成一整套业务。 - 在这个场景中我们模拟爬虫爬取各类商家的商品信息,生成推广海报赚取商品返利。声明,这里事模拟爬取,并没有真的爬取 - 在整个爬取的过程中,可以分为,模拟登录、爬取信息、生成海报,这三个步骤,另外; - 1、因为有些商品只有登录后才可以爬取,并且登录可以看到一些特定的价格这与未登录的用户看到的价格不同。 - 2、不同的电商网站爬取方式不同,解析方式也不同,因此可以作为每一个实现类中的特定实现。 - 3、生成海报的步骤基本一样,但会有特定的商品来源标识。所以这样三个步骤可以使用模板模式来设定,并有具体的场景做子类实现。 - 总结:通过上面的实现可以看到模板模式在定义统一结构也就是执行标准上非常方便,也就很好的控制了后续的实现者不关心调用逻辑,按照统一方式执行。 - 那么类的继承者只需要关心具体的业务逻辑实现即可。 - 另外模板模式也是为了解决子类通用方法,放到父类中设计的优化。让每一个子类只做子类需要完成的内容,而不需要关心其他逻辑。这样提取公用代码, - 行为由父类管理,扩展可变部分,也就是非常有利于开发拓展和迭代。 - 但每一种设计模式都有自己的特定场景,如果超过场景外的建设就需要额外考虑其他模式的运用,而不是非要生搬硬套,否则自己不清楚为什么这么做, - 也很难让后续者继续维护代码。而想要活学活用就需要多加练习,有实践的经历。 ## 第三十讲 访问者模式 - 访问者模式:指将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作, - 为数据结构中的每个元素提供多种访问方式。 - 模拟场景:家长和校长,对学生和老师的不同视觉信息的访问场景 - 访问者要解决的核心事项是,在一个稳定的数据结构下,例如用户信息、雇员信息等,增加易变的业务访问逻辑。为了增强扩展性,将这两部分的业务解耦的一种设计模式。 - 说白了访问者模式的核心在于同一个事物不同视角下的访问信息不同,比如一个美女手里拿个冰激凌。小朋友会注意到冰激凌, - 大朋友会找自己喜欢的地方观察敌情。 - 具体场景描述: - 在本案例中我们模拟校园中的学生和老师对于不同用户的访问视角。这个案例场景我们模拟校园中有学生和老师两种身份的用户,那么对于家长和校长关心的角度 - 来看,它们的视角是不同的。家长更关心孩子的成绩和老师的能力,校长更关心老师所在班级的人数和升学率。 - 那么这样学生和老师就是一个固定信息的内容,而想让不同视角的用户获取关心的信息,就比较适合使用观察者模式来实现,从而让实体和业务解耦, - 增强扩展性。但观察者模式的只能个体类结构相对复杂,需要梳理清楚再开发。 - 访问者模式的类结构相对其他设计模式来说比较复杂,但这样的设计模式在我看来更加有魅力,他能阔开你对代码结构的新认知,用这样思维 - 不断的建设更好的代码架构。 - 关于这个案例的核心逻辑实现,有以下几点; - 1、建立用户抽象类和抽象访问方法,再由不同的用户实现;老师和学生 - 2、建立访问者接口,用于不同人员的访问操作;校长和家长 - 3、最终是对数据的看板建设,用于实现不同视角的访问结果输出。 - 总结:从以上的业务场景中可以看到,在嵌入访问者模式后,可以让整个工程结构变得容易添加和修改。也就做到了系统服务之间的解耦,不至于 - 为了不同类型信息的访问而增加很多多余的if判断或者类的强制转换。也就是通过这样的设计模式让代码结构更加清晰。 - 另外在实现的过程可能你发现了,定义抽象类的时候还需要等待访问者接口的定义,这样的设计首先从实现上会让代码的组织变得有难度。 - 另外从设计模式原则的角度来看,违背了迪米特原则,也就是最少知道原则。因此在使用上一定要符合场景的运用,以及提取这部分设计思想啊的精髓 - 好的学习方式才好更容易接受知识,学习编程的更需要的不单单是看,而是操作。二十多种设计模式每一种都有自己ide设计技巧,也可以说是巧妙之处, - 这些巧妙的地方往往是解决复杂难题的最佳视角。亲力亲为,才能为所欲为,为了自己的欲望而努力! #### 特技 1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md 2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) 3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) 6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)