平板上做网站的软件,购物网站开发,谷歌商店paypal下载官网,用多说的网站1.创建型设计模式
创建型设计模式包括#xff1a;单例模式、工厂模式、建造者模式、原型模式。它主要解决对象的创建问题#xff0c;封装复杂的创建过程#xff0c;解耦对象的创建代码和使用代码。
1.单例模式
单例模式用来创建全局唯一的对象。一个类只允许创建一个对象…1.创建型设计模式
创建型设计模式包括单例模式、工厂模式、建造者模式、原型模式。它主要解决对象的创建问题封装复杂的创建过程解耦对象的创建代码和使用代码。
1.单例模式
单例模式用来创建全局唯一的对象。一个类只允许创建一个对象或者叫实例那这个类就是单例类这种设计模式叫做单例模式。单例模式有几种经典的实现方式它们分别是饿汉式、懒汉式、双重校验、静态内部类、枚举。
尽管单例是一个很常用的设计模式在实际的开发中我们也确实经常用到它但是有些人认为单例是一种反模式anti-pattern并不推荐使用主要的理由有以下几点
单例对 OOP 特性的支持不友好单例会隐藏类之间的依赖关系单例对代码的扩展性不友好单例对代码的可测试性不友好单例不支持有参的构造函数
那么有什么替代单例的解决方案呢 如果要完全解决这些问题我们可能要从跟上寻找其他方式来实现全局唯一类。比如通过工厂模式、IOC 容器来保证全局唯一性。
有人把单例当做反模式主张杜绝在项目中使用我个人觉得这个观点有点极端。模式本身没有对错关键看你怎么用。如果单例类并没有后续扩展的需求并且不依赖外部系统那设计成单例类就没有太大问题。对于一些全局类我们在其他地方 new 的话还要在类之间传来传去不如直接做成单例类使用起来简洁方便。
此外我们还讲了进程唯一单例、线程唯一单例、集群唯一单例、多例等扩展知识点这一部分在实际的开发中并不会被用到但是可以扩展你的思路、锻炼你的逻辑思维。
2.工厂模式
工厂模式包括简单工厂、工厂方法、抽象工厂这3种细分模式。其中简单工厂和工厂方法比较常用抽象工厂的应用场景比较特殊所以很少用到。
工厂模式用来创建不同但是相关类型的对象继承同一父类或者接口的一组子类由给定的类型来决定创建哪种类型的对象。实际上如果创建对象的逻辑并不复杂那我们就直接通过 new 来创建对象就可以了不需要使用工厂模式。当创建逻辑比较复杂是一个 “大工程” 的时候我们就考虑使用工厂模式封装对象的创建过程将对象的创建和使用相分离。
当每个对象的创建逻辑都比较简单的时候我推荐使用简单工厂模式将多个对象的创建逻辑放到一个工厂类中。当每个对象的创建逻辑都比较复杂的时候为了避免一个过于庞大的类我们推荐使用工厂方法模式将创建逻辑拆分的更细每个对象的创建逻辑独立到各个工厂类中。
详细点说工厂模式的作用有下面 4 个这也是判断要不要使用工厂模式最本质的参考标准。
封装变化创建逻辑有可能变化封装成工厂类之后创建逻辑的变更对调用者透明。代码复用创建代码抽离到独立的工厂类之后可以复用。隔离复杂性封装复杂的创建逻辑调用者无需了解如何创建对象。控制复杂度将创建代码抽离出来让原本的函数或类职责更单一代码更简洁。
此外我们还讲了工厂模式一个非常经典的应用场景依赖注入框架比如 Spring IOC、Google Guice它用来集中创建、组装、管理对象跟具体业务代码解耦让程序员聚焦在业务代码的开发商。DI 框架已经成为了我们开发的必备框架。
3.建造者模式
建造者模式用来创建复杂对象可以通过设置不同的可选参数 “定制化” 地创建不同的对象。建造者模式的原理和实现比较简单重点是掌握应用场景避免过度使用。
如果一个类有很多属性为了避免构造函数参数列表过长影响代码的可读性和易用性我们可以通过构造函数配合 set() 方法来解决。但是如果存在下面情况中的任何一种我们就考虑使用建造者模式了。
我们把类的必填属性放到构造函数中强制创建对象时就设置。如果必填的属性有很多把这些必填属性都放到构造函数中设置那构造函数就又会出现参数列表过长的问题。我们把必填属性通过 set() 方法设置那校验这些必填属性是否已经填写的逻辑就无处安放。如果类的属性之间有依赖关系或者约束条件我们继续使用构造函数配合 set() 方法的设计思路那这些依赖关系或者约束条件就无处安放。我们希望创建不可变对象也就是说对象在创建好之后就不能再修改内部的属性值要实现这个功能我们就不能暴露 set() 方法。构造函数配合 set() 方法来设置属性值的方式就不适用了。
4.原型模式
如果对象的创建成本比较大而同一个类的不同对象之间差别不大大部分字段相同在这种情况下我们可以利用已有对象原型进行复制或者叫拷贝的方式来创建新对象已达到节省创建时间的目的。这种基于原型来创建对象的方式叫做原型模式。
原型模式有两种实现方式深拷贝和浅拷贝。浅拷贝只会复制对象的基本数据类型和引用对象的内存地址不会递归地复制引用对象以及引用对象的引用对象…而深拷贝得到的是一份完完全全独立的对象。所以深拷贝比浅拷贝来说更加耗时更加耗内存空间。
如果要拷贝的对象是不可变对象浅拷贝共享不可变对象是没有问题的但对于可变对象来说浅拷贝得到的对象和原始对象会共享部分数据就有可能出现数据被修改的风险也就变得复杂多了。操作非常耗时的情况下比较推荐使用浅拷贝否则没有充分的理由不要为了一点点的性能提升而使用浅拷贝。
二、结构型设计模式
结构型模式主要总结了一些类或对象组合在一起的经典结构这些经典的结构可以解决特定应用场景的问题。结构型模式包括代理模式、桥接模式、装饰器模式、适配器模式、门面模式、组合模式、享元模式。
1.代理模式
代理模式在不改变原始类接口的条件下为原始类定义一个代理类主要目的是控制访问而非加强功能这是它跟装饰者模式最大的不同。一般情况下我们让代理类和原始类实现相同的接口。但是如果原始类没有定义接口并且原始类代码并不是我们开发维护的。在这种情况下我们可以通过让代理类继承原始类的方式来实现代理模式。
静态代理需要针对每个类多创建一个代理类并且每个代理类的代码都有点像模板式的 “重复” 代码增加了维护和开发成本。对于静态代理存在的问题可以通过动态代理来解决。我们不事先为每个原始类编写代理类而是在运行时动态地创建原始类对应的代理类然后再系统中用代理类替换掉原始类。
代理模式常用在业务系统中开发一些非功能性需求比如监控、统计、鉴权、事务、幂等、日志。我们将这些功能与业务功能解耦放到代理类统一处理让程序员只需要关注业务方面的开发。此外代理模式还可以用在 RPC、缓存等应用场景中。
2.桥接模式
桥接模式的代码实现非常简单但理解起来稍微有点难度并且应用场景也比较局限所以相对来说桥接模式在实际的项目中并没有那么常用你只需要简单了解见到能认识就可以了。
桥接模式有两种理解方式。
第一种理解方式是 “将抽象和实现解耦让它们能独立开发”。这种理解方式比较特别应用场景也不多。另一种理解方式更加简单等同于 “组合优于继承” 设计原则这中理解方式更加通用应用场景比较多。
不管哪种理解方式它们的代码结构都是想同的都是一种类之间的组合关系。
对于第一种理解方式弄懂定义中 “抽象” 和 “实现” 两个概念是理解它的关键。定义中的 “抽象”指的并非 “抽象类”而是被抽象出来的一套 “类库”它只包含骨架代码真正的业务逻辑需要委派给定义种的 “实现” 来完成。而定义种的 “实现”也并非接口实现类而是一套独立的 “类库”。“抽象” 和 “实现” 独立开发通过对象之间的组合关系组装在一起。
3.装饰器模式
装饰器模式主要用于解决继承过于复杂的问题通过组合来替代继承给原始类增强功能。这也是判断是否该用装饰器模式的一个重要的依据。
此外装饰器模式还有一个特点那就是可以对原始类嵌套使用多个装饰器。为了满足这样的需求在设计的时候装饰器类需要跟原始类继承想同的抽象类或接口。
4.适配器模式
代理模式、装饰器模式提供的都是跟原始类想同的接口而适配器提供跟原始类不同的接口。适配器模式是用来做适配的它将不兼容的接口转换为可兼容的接口让原本由于接口不兼容而不能一起工作的类可以一起工作。适配器模式的实现方式有两种类适配器和对象适配器。其中类适配器使用继承关系来实现对象适配器使用组合关系来实现。
适配器模式是一种事后补救策略用来补救设计上的缺陷。应用这种设计模式是 “无奈之举”。如果在设计初期我们就能规避接口不兼容的问题那这种模式就无用武之地了。在实际的开发中什么情况下才会出现接口不兼容呢我总结了下面这 5 中场景
封装有缺陷的接口设计统一多个类的接口设计替换依赖的外部系统兼容老版本接口适配不同格式的数据
5.门面模式
门面模式原理、实现都非常简单应用场景比较明确。它通过封装细粒度的接口提供组合各个细粒度接口的高层次接口来提高接口的易用性或者解决性能、分布式事务等问题。
6.组合模式
组合模式跟面向对象中的 “组合关系通过组合来组装两个类”完全是两码事。这里的组合模式主要是用来处理树形接口数据。正式因为其应用场景的特殊性数据必须能表示成树形结构这也导致了这种模式在实际的项目开发中并不那么常用。但是一旦数据满足树形结构应用这种设计模式就能发挥很大作用能让代码变得非常简洁。
组合模式的设计思路与其说是一种设计模式倒不如说是对业务场景的一种数据结构和算法的抽象。其中数据可以表示成数这种数据结构业务需求可以通过在树上的递归遍历算法来实现。组合模式将一组对象组织成树状结构将单个对象和组合对象都看作树种的节点以统一处理并且它利用属性接口的特点递归地处理每个子树依此简化代码实现。
7.享元模式
所谓 “享元”顾名思义就是被共享的单元。享元模式的意图是复用对象节省内存前提是享元对象是不可变对象。
具体来讲当一个系统中存在大量重复对象时可以利用享元模式将对象设计成享元在内存中只留存一份实例供多出代码引用这样就可以减少内存中对象的数量以起到节省内存的目的。实际上不仅仅相同的对象可以设计成享元对于相似对象也可以将这些对象中想同的部分字段提取出来设计成享元让这些大量相似对象引用这些享元。
三、行为型设计模式
创建型设计模式主要解决 “对象的创建” 问题结构型设计模式主要解决 “类或对象的组合” 问题那行为型设计模式主要解决的是 “类或对象之间的交互” 问题。行为型模式比较多有 11 种它们分别是观察者模式、模板模式、策略模式、职责链模式、迭代器模式、状态模式、访问者模式、备忘录模式、命令模式、解释器模式、中介模式。
1.观察者模式
观察者模式将观察者和被观察者代码解耦。观察者模式的应用场景非常广泛小到代码层面的解耦大到架构层面的解耦再或者一些产品的设计思路都有这个模式的影子比如邮件订阅、RSS feeds本质上都是观察者模式。
不同的应用场景和需求下这个模式也有截然不同的实现方式有同步阻塞的实现方式也有异步非阻塞的实现方式有进程内的实现方式也有跨进程的实现方式。
同步阻塞是最经典的实现方式主要是为了代码解耦异步非阻塞除了能实现代码解耦外还能提高代码的执行效率。进程间的观察者模式解耦更加彻底一般都是基于消息队列实现用来实现不同进程间的被观察者和观察者之间的交互。
框架的作用是隐藏实现细节降低开发难度实现代码复用解耦业务与非业务代码让程序员聚焦业务开发。针对异步非阻塞观察者模式我们业可以将其抽象成 EventBus 框架来达到这样的效果。EventBus 翻译为 “事件总线”它提供了实现观察者模式的骨架代码。我们可以基于此框架非常容易地在自己的业务场景中实现观察者模式不需要从零开始开发。
2.模板模式
模板方法模式在一个方法中定义一个算法骨架并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下重新定义算法中的某些步骤。这里的 “算法”可以理解为 “业务逻辑”并不是特指数据结构和算法中的 “算法”。这里的算法骨架是 “模板”包含算法骨架的方法就是 “模板方法”这也是模板方式模式名字的由来。
模板模式有两大作用扩展和复用。
其中复用指的是可以复用父类中提供的模板方法的代码。扩展指的是框架通过模板模式提供的功能扩展点让框架用户可以在不修改框架源码的情况下基于扩展点定制化框架的功能。
此外我们还提到了回调。它跟模板模式具有相同的作用代码复用和扩展。在一些框架、类库、组件等的设计中经常会用到比如 JdbcTemplate 就是用了回调。 相对于普通的函数调用回调是一种双向调用关系。A 类实现注册某个函数 F 到 B 类A 类在调用 B 类的 P 函数时B 类反过来调用 A 类注册给它的 F 函数。这里的 F 函数就是 “回调函数”。A 调用 BB 反过来调用 A这种调用机制就叫做 “回调”。 回调可以细分为同步回调和异步回调。从应用场景上来看同步回调看起来更新模板模式异步回调看起来更像观察者模式。回调跟模板模式的区别更多的是在代码实现上而非应用场景上。回调基于组合关系来实现模板模式基于继承关系来实现。回调比模板模式更加灵活。
3.策略模式
策略模式定义一组算法类将每个算法分别封装起来让它们可以互相替换。策略模式可以使算法的变化独立于它们的客户端这里的客户端指的是使用算法的代码。策略模式用来解耦策略的定义、创建、使用。实际上一个完整的策略模式就是由这三个部分组成。
策略类的定义比较简单包含一个策略接口和一组实现这个接口的策略类。策略的创建由工长类来完成封装测试创建的细节。策略模式的包含一组策略可选客户端代码选择使用哪个策略有两种确定方式编译时静态确定和运行时动态确定。其中“运行时动态确定” 才是策略模式最典型的应用场景。
在实际的项目开发中策略模式也比较常用。最常见的应用场景时利用它来避免冗长的 if-else 或 switch 分支判断。不过它的作用还不止如此。它也可以像模板模式那样提供框架的扩展点等等。实际上策略模式的主要作用还是解耦策略的定义、创建和使用控制代码的复杂度让每个部分都不至于过于复杂、代码量过多。此外对于复杂代码来说策略模式还能让其满足开闭原则添加新策略的时候最小化、集中话代码改动减少引入 bug 的风险。
4.职责链模式
在职责链模式中多个处理器依次处理同一个请求。一个请求先经过 A 处理器处理然后再把请求传递给 B 处理器B 处理器处理完成后再传递给 C 处理器以此类推形成一个链条。链条上的每个处理器各自承担各自的处理职责所以叫职责链模式。
在 GoF 的定义中一旦某个处理器能处理这个请求就不会再继续将请求传递给后续的处理器了。当然在实际的开发中也存在对这个模式的变体那就是请求不会中途终止传递而是会被所有的处理器都处理一遍。
职责链模式常用在框架开发中用来实现过滤器、拦截器功能让框架的使用者在不修要修改框架源码的情况下添加新的过滤、拦截功能。这也体现了对扩展开放、对修改关闭的设计原则。
5.迭代器模式
迭代器模式也叫游标模式它用来遍历集合对象。这里说的 “集合对象”也可以叫 “容器” “聚合对象”实际上就是一组包含对象的对象比如数组、链表、树、图、跳表。迭代器模式主要作用是解耦容器代码和遍历代码。大部分编程语音都提供了现成的迭代器可以使用我们不需要从零开发。
遍历结合的方式一种有三种for 循环、foreach 循环、迭代器遍历。后两种本质上属于一种都可以看做迭代器遍历。相对于 for 循环遍历利用迭代器来遍历有 3 个优势
迭代器模式封装集合内部的复杂数据结构开发者不需要了解如何遍历直接使用容器提供的迭代器即可。迭代器模式将集合对象的遍历操作从集合类中拆分出来放到迭代器类中让两者的职责更加单一迭代器模式让添加新的遍历算法更加容易更符合开闭原则。此外因为迭代器都实现相同的接口在开发中基于接口而非实现编程替换迭代器也变得更加容易。
通过迭代器来遍历集合元素的同时新增或者删除集合中的元素有可能会导致某个元素被重复遍历或遍历不到。针对这个问题有两种比较干脆利索的解决方案来避免出现不可预期的运行结果。一种是遍历的时候不允许增删元素另一种是增删元素之后让遍历报错。
第一种解决方式比较难实现因为很难确定迭代器使用结束的时间点。第二种解决方式更加合理Java 语言就是采用的这种解决方案。增删元素之后选择 fail-fast 解决方式让遍历操作直接抛出运行时异常。
6.状态模式
状态模式一般用来实现状态机而状态机常用在游戏、工作流引擎等系统开发中。状态机又叫有限状态机它由三个部分组成状态、事件、动作。其中事件也叫转移条件。事件触发状态的转移及动作的执行。不过动作不是必须得也可能只转移状态不执行任何操作。
针对状态机由三种实现方式
第一种实现方式叫分支逻辑法。利用 if-else 或者 switch-case 分支逻辑参照状态转移图将每个状态转移原模原样得直译成代码。对简单的状态机来说这种实现方式最简单、最直接是首选。第二种实现方式叫查表法。对于状态很多、状态转移比较复杂的状态机来说查表法比较适合。通过二维数组来表示状态转移图能极大的提高代码的可读性和可维护性。第三种实现方式是利用状态模式。对于状态不多、状态转移也简单但事件触发执行的动作包含的业务逻辑可能比较复杂的状态机来说我们首选这种方法。
7.访问者模式
访问者模式允许一个或多个操作应用到一组对象上设计意图是解耦操作和对象本身保持类职责单一、满足开闭原则及应对代码的复杂性。
对于访问者模式学习的难点在于代码实现。而代码实现比较复杂的主要原因是函数重载在大部分面向对象编程语言中是静态绑定的。也就是说调用类的哪个重载函数是在编译期间由参数的声明类型决定的而非运行时根据参数的实际类型决定的。此外还讲到 Double Dispatch。如果某种语音支持 Double Dispatch那就不需要访问者模式了。
正是因为代码实现难理解所以在项目中应用这种设计模式会导致代码的可读性比较差。如果你的同事不了解这种设计模式可能就会读不懂、维护不了你写的代码。所以除非不得已不要使用这种设计模式。
8.备忘录模式
备忘录模式也叫快照模式具体来说就是在不违背封装原则的前提下捕获一个对象的内部状态并在该对象之外保存这个状态以便之后恢复对象先前的状态。这个模式表达了两部分的内容一部分是存储副本以便后期恢复另一部分是要在不违背封装原则的前提下进行对象的备份和恢复。
备忘录模式的应用场景也比较明确和有限主要用来防丢失、撤销、恢复等。它跟 “备份” 很相似。两种的主要区别在于备忘录模式更侧重代码的设计和实现。备份更侧重架构设计或产品设计。
对于大对象的备份来说备份占用的存储空间会比较大备份和恢复的耗时会比较长。针对这个问题不同的业务场景有不同的处理方式。
比如只备份必要的恢复信息结合最新的数据来恢复再比如全量备份和增量备份相结合低频全量备份高频增量备份两种结合来做恢复。
9.命令模式
命令模式在平时工作中并不常用你稍微了解下即可。
落实到编程命令模式用到最核心的实现手段就是将函数封装成对象。我们知道在大部分编程语言中函数是没法作为参数传递给其他函数的也没法复制给变量。借助命令模式我们将函数封装成对象这样就可以实现把函数像对象一样使用。
命令模式的主要作用和应用场景是用来控制命令的执行、异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等这才是命令模式能发挥独一无二作用的地方。
10.解释器模式
解释器模式为某个语言定义它的语法表示并定义一个解释器用来处理这个语法。实际上这里的 “语言” 不仅仅指我们平时说的中、英、法等各种语言。从广义上来讲只要是能承载信息的载体都可以称之为 “语言”比如古代的结绳记事、盲文、哑语、摩斯密码等。
要想了解 “语言” 要表达的信息就必须定义相应的语法规则。这样书写者就可以根据语法规则来书写 “句子”阅读者根据语法规则来阅读 “句子”这样才能做到信息的正确传递。而我们要讲的解释器模式其实就是用来根据语法规则解读 “句子” 的解释器。
解释器模式的代码实现比较灵活也没有固定的模板。前面讲过应用设计模式主要是应对代码的复杂性解释器模式也不例外。它的代码实现的核心思想就是将语法解析的工作拆分到各个小类中以此来避免大而全的解析类。一般的做法是将语法规则拆分一些小的独立的单元然后对每个单元进行解析最终合并为对整个语法规则的解析。
11.中介模式
中介模式的设计思想跟中间层很像通过引入一个中间层将一组对象之间的交互关系从多对多转换为一对多。原来一个对象要跟 n 个对象交互现在只需要跟一个中介对象交互从而最小化对象之间的交互关系降低了代码的复杂度提高了代码的可读性和可维护性。
观察者模式和中介模式都是为了实现参与者之间的解耦简化交互关系。两者的不同之处在于应用场景上。
在观察者模式的应用场景中参与者之间的交互比较有条理一般都是单向的一个参与者只有一个身份要么是观察者要么是被观察者。而在中介模式的应用场景中参与者之间的交互关系错综复杂既可以是消息的发送者也可以同时是消息的接收者。