再谈23种设计模式(2):结构型模式(趣图解释)
回顾一下《再谈设计模式—模式23种设计模式总结》
23 种设计模式的分类表
范围\目的 | 创建型模式 | 结构型模式 | 行为型模式 |
---|---|---|---|
类模式 | 工厂方法 | (类)适配器 | 模板方法、解释器 |
对象模式 | 单例 原型 抽象工厂 建造者 |
代理 装饰 桥接 (对象)适配器外观 享元 组合 |
策略 职责链 状态 观察者 中介者 迭代器 访问者 备忘录 命令 |
创建型模式(Creational Patterns):
-
单例模式(Singleton pattern):某个类只能有一个实例,提供一个全局的访问点。
-
工厂方法(factory method pattern):定义一个创建对象的接口,让子类决定实例化那个类。
-
抽象工厂(Abstract factory pattern):创建相关或依赖对象的家族,而无需明确指定具体类。
-
建造者模式(Builder pattern):封装一个复杂对象的构建过程,并可以按步骤构造。
-
原型模式(prototype pattern):通过复制现有的实例来创建新的实例。
这个在《再谈设计模式—模式23种设计模式总结》已经讲过,
结构型模式(Structural Patterns):
-
代理模式(Proxy Pattern):其实就是一个代理卡在买卖双方中间,做一些流程上的控制。代理模式的结构性就体现在多了一个代理类,卡在「买卖双方」中间,这种结构特征不言而喻。
-
装饰模式(Decorator Pattern):指的是使用很多其他类对原有类进行功能增强,就像美颜APP一样,我们使用很多滤镜对照片进行功能增强。这一个个的滤镜就嵌套在原有的照片上,就像一个个功能增强类嵌套在原有的基础类上一样。装饰模式的结构性就体现在这种嵌套关系上,一层层的嵌套组成了一种结构。
-
桥接模式(Bridge Pattern):其实就是把固定的和变化的分离开来,使用组合的方式去实现。将固定的放在原地不动,而变化的则抽离出去作为一个新的类,再通过组合的形式被老的类引用。这里的结构体现在旧类对新类的引用,它们之间变成了一种结构型关系。
-
适配器模式(Adapter Pattern):指的是可以让不兼容的对象能够合作,通常情况是有一个适配器类实现了多个接口,从而在接口方法中写入特定逻辑去做兼容适配。这里的结构体现在对于两种对象的兼容,通过兼容将新老对象组合起来了。、
-
外观模式(Facade Pattern):指的是提供一个全新的接口层,将子系统复杂的接口内容屏蔽了。则像是肯德基的前台一样,帮你把一切复杂的东西屏蔽了。你不需要关心汉堡怎么做,奥尔良鸡翅怎么做。你只需要告诉前台要吃什么,它就会把汉堡、鸡翅等东西做好了拿给你。
在外观模式中,我们在使用方与子系统之间插入了一层接口层,去屏蔽复杂的内部细节。就像是肯德基的前台,帮我们屏蔽了内部食物的制作细节一样。门面模式的结构就体现在我们插入的这一层「门面」上,它将使用方与子系统连接起来,让使用更方便了!
-
享元模式(Flyweight Pattern):指的是共享同一个元素,是一种节省内存的设计模式,其实就类似我们的池技术。与其他对象复杂的结构相比,我都不觉得享元模式是一种设计模式,而只是一种思想而已。但如果严格地从设计模式的定义来讲的话,那其实也可以算是。
在享元模式中,我们会新增一个类去保存元素的映射池。而这个新增的类就相当于是一个新增的对象,通过组合的形式去节省内存的消耗。享元模式的结构性更多是通过组合的形式体现的。
-
组合模式(Composite Pattern):就是多个结构组合起来,形成一个更复杂的结构。组合模式更多是一种数据结构的呈现,而不能说是一种设计模式。但从广义的设计模式定义来看,将组合模式说成是一种设计模式,也没有错。
大家会发现组合模式和桥梁模式非常像,其实这两者之间并没有太大的差别,甚至是说基本一样。桥梁模式与组合模式,其实都是基于组合这种关系,不同的对象组合起来形成更大的结构体。桥梁模式是基于组合模式的。
创建型模式VS结构型模式:
想象你在开一家餐厅,你需要准备食材来做菜。
创建型模式就像是你的食材供应商,它们告诉你如何更好地获取和管理食材。例如,
-
单例模式确保你只有一个供应商账户
-
工厂方法让你根据需要的食材类型选择不同的供应商
-
建造者模式则帮助你组合多种食材来准备一个复杂的菜单。
结构型模式就像是你的厨房布局和工作流程。它们告诉你如何组织厨房,使得厨师们能高效地工作,不同的工作站能很好地协同。例如
-
适配器模式就像是一个能让你使用不同电源插座的转换器
-
外观模式则像是一个前台服务员,客人只需要和他交流,而不需要直接和厨房里的每个人打交道。
总之:
-
创建型模式集中于如何创建对象(旨在提供更多创建对象的灵活性)
-
结构型模式集中于如何组织不同的类和对象(集中于如何组织不同的类和对象)。
所以,结构类型,就不用过多的代码,用趣图来解释更好
代理模式(Proxy)
代理模式用于为另一个对象提供一个替身或占位符,以控制对这个对象的访问。
由于某些原因需要给某对象(目标对象)提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
在前端中,代理模式可以用于懒加载、事件代理、数据绑定、缓存等应用。
-
Vue.js的vue-router库使用代理模式来控制路由跳转时的导航守卫。
-
通过创建一个代理服务器,实现跨域请求,避免浏览器的同源策略限制。
-
使用代理对象管理对象的访问,如ES6的Proxy可以实现对象属性的监控。
-
实现图片懒加载,通过代理控制图片的加载时机,提高页面加载性能。
装饰器模式(Decorator)
装饰者模式的主要目的是动态地给对象添加额外的职责(功能)。装饰者模式通过包装对象来扩展其功能,而不是通过继承。
装饰者可以在保持接口不变的情况下,为对象添加新的功能。
装饰者模式通常用于以下情况:
-
当需要扩展一个类的功能,但不想通过继承增加子类的方式。
-
当需要动态地给对象添加功能,这些功能可以轻松地撤销。
前端装饰模式举例
-
在Koa.js中,通过装饰器模式组织中间件,每个中间件可以对请求处理进行装饰。
-
ES7的装饰器语法允许注解和修改类和属性,提供了一种在声明时注入额外逻辑的方式。
使用继承的方式存在的问题:
扩展性不好
如果要再加一种配料(火腿肠),我们就会发现需要给FriedRice和FriedNoodles分别定义一个子类。如果要新增一个快餐品类(炒河粉)的话,就需要定义更多的子类。
产生过多的子类
我们使用装饰者模式对快餐店案例进行改进,体会装饰者模式的精髓。
代理模式和装饰者模式的区别
-
意图:
-
代理模式主要是控制对对象的访问,
-
装饰者模式主要是为对象添加新的功能。
-
实现:
-
代理模式通常只有一个代理类,它隐藏了实际对象的细节。
-
装饰者模式可以有多个不同的装饰者类,它们可以堆叠在一起,为对象添加多个层次的功能。
-
设计:
-
代理模式通常在代理类中直接引用实际对象,
-
装饰者模式通常使用抽象组件类作为装饰者和实际对象的共同父类。
-
灵活性:
-
装饰者模式提供了更高的灵活性,可以在运行时通过组合不同的装饰者来扩展对象的功能。
-
代理模式通常在编译时就已经确定了代理的行为。
适配器模式(Adapter)
你可以创建一个适配器。 这是一个特殊的对象, 能够转换对象接口, 使其能与其他对象进行交互。
适配器模式通过封装对象将复杂的转换过程藏于幕后。 被封装的对象甚至察觉不到适配器的存在。
适配器模式用于桥接接口不兼容的对象,使得它们可以一起工作。常用于应对API升级导致的接口改变,或实现不同库之间的数据交互。
-
Polyfill技术填补了新旧浏览器之间API实现的差异。
-
将多个不同的第三方API整合到一个统一的API接口下,简化客户端的调用复杂度。
适配器模式结构
-
目标接口(Target):这是客户端期望使用的接口。它定义了客户端需要的特定领域的操作。
-
待适配的类(Adaptee):这是已经存在的类,它提供了一些有用的行为,但其接口与目标接口不兼容。
-
适配器(Adapter): 适配器实现或继承目标接口,并包含一个待适配类的实例。
-
适配器负责将目标接口的调用转换为对待适配类的接口的调用。
-
适配器可以是类适配器(通过多重继承实现)或对象适配器(通过组合实现)。
-
客户端(Client):客户端是依赖于目标接口的任何类。客户端期望任何实现目标接口的对象都能按照目标接口的约定行事。
桥接模式(Bridge)
桥接模式用于分离抽象部分与实现部分,使得它们可以各自独立地变化。这在处理多维度变化的系统设计中尤为有用。
桥接模式推荐看知乎:
-
桥接模式最核心的思想是什么? - 码匠er的回答 - 知乎 https://www.zhihu.com/question/367863297/answer/3412860103
-
桥接模式最核心的思想是什么? - Gopher大威的回答 - 知乎 https://www.zhihu.com/question/367863297/answer/2389262577
-
看完就明白的桥接模式柠檬大师柠檬大师 https://zhuanlan.zhihu.com/p/390412916
桥接模式使用场景
如果系统中存在多个维度的变化,可以使用桥接模式来处理;
当需要在抽象和实现层次上都分别进行扩展时,桥接模式因为对两个维度进行解耦,可以很方便实现扩展;
对不希望使用继承或者多继承导致系统中类的个数急剧增加时可以使用桥接模式。
桥接模式在前端的案例
-
Styled Components等CSS-in-JS库通过为组件提供样式“主题"来隔离样式和组件逻辑。
-
JS框架与DOM库的分离,如React和Virtual DOM的关系,提高了框架的灵活性和效率。
适配器模式VS和桥接模式
适配器模式
-
目的:适配器模式的目的是允许不兼容的接口之间能够相互合作。它通过创建一个中间层来实现现有接口与目标接口之间的兼容。
-
应用场景:当你想要使用一个已经存在的类,但其接口与你的需求不匹配时,你可以使用适配器模式。适配器模式常用于确保已有的类可以与其他类一起工作,而不需要修改它们的源代码。
-
实现方式:适配器实现了目标接口,并持有一个被适配者的引用。适配器将目标接口的调用转换为对被适配者的调用。
桥接模式
-
目的:桥接模式的目的是将抽象与实现分离,以便两者可以独立地变化。它通过定义一个抽象层和实现层的接口,然后通过组合的方式将抽象层与实现层连接起来。
-
应用场景:当你想要避免抽象和实现之间的永久绑定时,或者当类的抽象和实现都可以通过子类化的方式独立地扩展时,你可以使用桥接模式。桥接模式常用于实现平台独立的功能,或者处理多维度的变化。
-
实现方式:桥接模式通过定义一个抽象类和一个实现类接口,抽象类持有实现类接口的引用。客户端代码可以独立地扩展抽象类和实现类,而不会影响到彼此。
区别总结
-
设计意图不同:
-
适配器模式主要用于使现有的不兼容接口能够一起工作,
-
桥接模式则是为了分离抽象和实现,使它们可以独立变化。
-
应用场景不同:
-
适配器模式通常用于集成第三方库、API或遗留系统,
-
桥接模式则用于设计时考虑到系统可能在多个维度上变化。
-
实现方式不同:
-
适配器模式通过封装一个已有的不兼容接口来提供所需的接口
-
桥接模式则通过组合的方式将抽象和实现解耦。
尽管适配器模式和桥接模式都涉及到接口和类之间的关系,但它们的设计目的和使用方式有明显的区别。适配器模式关注于解决现有系统的兼容性问题,而桥接模式关注于设计前期的抽象和实现的分离。
出发点不同。
适配器:改变已有的两个接口,让他们相容。
桥接模式:分离抽象化和实现,使两者的接口可以不同,目的是分离。
所以说,如果你拿到两个已有模块,想让他们同时工作,那么你使用的适配器。
如果你还什么都没有,但是想分开实现,那么桥接是一个选择。
桥接是先有桥,才有两端的东西
适配是先有两边的东西,才有适配器
桥接是在桥好了之后,两边的东西还可以变化。
例如游戏手柄,就象个桥,它把你的任何操作转化成指令。
(虽然,你可以任何操作组合,但是你的操作脱不开山下左右,a,b,选择 ,确定)
JRE本身就是一个就是一个很好的桥,先写好在linux上执行的Jre,再写好可以在windows下执行的JRE,
这样无论什么样的Java程序,只要配和相应的Jre就能在Linux或者Windows上运行.
两个Jre并没有限定你写什么样的程序,但要求你必须用Java来写。
外观模式(Facade)
外观模式提供了一个统一的接口来访问子系统的一群接口。这种模式定义了一个高层接口,使子系统更易于使用。
外观(Facade)模式是“迪米特法则(Law of Demeter)”的典型应用——>最少知识原则(Principle of Least Knowledge)——>不要和陌生人说话——>就透过一个窗口了解世界就好了
前端的外观模式案例:
-
使用jQuery操作DOM提供了简单的API来执行常见任务,例如操作类、属性、事件等,隐藏了底层复杂性。
-
统一封装多个复杂API调用,如Fetch API封装HTTP请求的细节,提供更简洁的API。
享元模式(Flyweight)
享元模式通过共享技术来支持大量细粒度对象的复用,减少应用程序中对象数量,节省系统资源。
享元模式中的“享元”指被共享的单元。享元模式通过复用对象,以达到节省内存的目的。
运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。
当一个系统中存在大量重复对象,若这些重复的对象是不可变对象,就能利用享元模式将对象设计成享元,在内存中只保留一份实例,供引用。这就减少内存中对象的数量,最终节省内存。
不仅相同对象可设计成享元,相似对象,也能提取对象中的相同部分(字段)设计成享元。
“不可变对象”:一旦通过构造器初始化完成后,其状态(对象的成员变量或属性)就不会再被修改。所以,不可变对象不能暴露任何set()等修改内部状态的方法。之所以要求享元是不可变对象,是因为它会被多处代码共享使用,避免一处代码对享元进行了修改,影响到其他使用它的代码。
在工厂类中,通过一个Map或List缓存已创建好的享元对象,以复用。
具体的推荐阅读:
-
如何评价设计模式中的享元模式? - JavaEdge的回答 - 知乎 https://www.zhihu.com/question/485995402/answer/2507093089
-
如何评价设计模式中的享元模式? - 云游的回答 - 知乎https://www.zhihu.com/question/485995402/answer/3284897645
前端案例:
-
大量相似的DOM元素事件监听:如果为每个按钮单独添加事件监听器,将会导致大量的内存消耗。使用享元模式,你可以只使用一个事件监听器来管理所有的按钮。
-
图形渲染:如粒子系统或游戏中的星空。如果每个图形都有自己的颜色、大小和位置属性,那么存储这些属性将消耗大量内存。享元模式可以帮助你共享这些属性,减少内存使用。
-
字符串池和数据库连接池技术,通过复用对象减少系统开销。
-
React 的 PureComponent是一种优化 React 组件性能的方法,它通过共享和复用相同的组件实例来减少渲染次数和提高性能。
享元模式VS单例、缓存、对象池
区别设计模式,不能光看代码,而要看设计意图,即要解决的问题。
-
单例模式是为了保证对象全局唯一
-
享元模式是为了实现对象复用,节省内存。缓存是为了提高访问效率,而非复用
-
池化技术中的“复用”理解为“重复使用”,主要是为了节省时间
组合模式(Composite Pattern)
又名部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
结构
组合模式不是随表组合的,他是将对象组合成树形结构,并以一致的方式对它们进行操作。
组合模式主要包含三种角色:
-
抽象根节点(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。
-
树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
-
叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。
组合模式的核心特征是将对象组合成树形结构。如果没有树形结构,那么就不是组合模式。
组合模式的关键特点包括:
-
透明性:客户端代码使用组合结构和单个对象的方式相同,无需关心它们之间的区别。
-
单一职责原则:组合模式允许你将操作分配给相应的组件类,无论这个类是简单的叶节点还是复杂的容器。
-
树形结构:组件之间形成树形结构,其中节点可以是叶节点或者更复杂的容器(也称为复合对象)。
具体实现推荐阅读:
-
设计模式之组合模式 https://zhuanlan.zhihu.com/p/25024599
-
设计模式(十四)----结构型模式之组合模式 https://www.cnblogs.com/xiaoyh/p/16560054.html
通过上面的组合模式就可以构建出这样的数据结构:
参考文章:
设计模式(九)----结构型模式之代理模式 https://www.cnblogs.com/xiaoyh/p/16558524.html
设计模式(十)----结构型模式之适配器模式 https://www.cnblogs.com/xiaoyh/p/16558530.html
设计模式(十一)----结构型模式之装饰者模式 https://www.cnblogs.com/xiaoyh/p/16559968.html
设计模式(十四)----结构型模式之组合模式 https://www.cnblogs.com/xiaoyh/p/16560054.html
转载本站文章《再谈23种设计模式(2):结构型模式(趣图解释)》,
请注明出处:https://www.zhoulujun.cn/html/theory/engineering/model/9081.html