1 Star 1 Fork 0

水依寒 / design_mode

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

设计模式解析

代码环境

  • JDK 16.1
  • IDEA 2021.1.3
  • SpringBoot 2.5.2

设计模式结构

设计模式

设计原则

设计原则名称 设计原则简介 重要性
单一职责原则(Single Responsibility Principle, SRP) 类的职责要单一,不能将太多的职责放在一个类中。该原则是实现高内聚、低耦合的指导方针。 ★★★★☆
开闭原则(Open-Closed Principle, OCP) 一个软件实体应当对扩展开放,对修改关闭。即在不修改源代码的情 况下改变对象的行为。 ★★★★★
里氏代换原则(Liskov Substitution Principle, LSP) 在软件系统中,一个可以接受基类(父类)对象的地方必然可以接受一个子类对象。里氏原则属于开闭原则的实现。 ★★★★☆
依赖倒转原则 (Dependency Inversion Principle, DIP) 抽象不应该依赖于细节(实现),细节应当依赖于抽象。换言之,要针对 接口编程,而不是针对实现编程。 用到接口的地方,通过依赖注入将 接口的实现对象注入进去。 ★★★★★
接口隔离原则 (Interface Segregation Principle, ISP) 使用多个专门的接口来取代一个统一的接口。 ★★☆☆☆
合成复用原则 (Composite Reuse Principle, CRP) 在系统中应该尽量多使用组合和聚合关联,尽量少使用甚至不使用继承关系。 ★★★★☆
迪米特法则(Law of Demeter, LoD) 一个软件实体应当尽可能少地与其他实体发生相互作用。通过引入一 个合理的第三者来降低现有对象之间的耦合度 ★★★☆☆

1.单一职责原则

定义:一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。

解说:一个类(或者大到模块,小到方法)承担的职责越多,它被复用的可能性越小,而且如果一个类承担的职责过 多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作。 类的职责主要包括两个方面:数据职责和行为职责,数据职责通过其属性来体现,而行为职责通过其方法来体现。 单一职责原则是实现高内聚、低耦合的指导方针,在很多代码重构方法中都能找到它的存在,它是最简单但又最难运 用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力 和相关重构经验。

实例:以登录实现为例:

2.开闭原则

定义:一个软件实体应当对扩展开放,对修改关闭。也就是说在设计一个模块的时候,应当使这个模块可以在不被修 改的前提下被扩展,即实现在不修改源代码的情况下改变这个模块的行为。

解说:开闭原则还可以通过一个更加具体的“对可变性封装原则”来描述,对可变性封装原则(EVP)要求找到系统的可变 因素并将其封装起来。 如果一个软件设计符合开闭原则,那么可以非常方便地对系统进行扩展,而且在扩展时无须修改现有代码,使得软件 系统在拥有适应性和灵活性的同时具备较好的稳定性和延续性。为了满足开闭原则,需要对系统进行抽象化设计,抽 象化是开闭原则的关键。

实例:我们拿报表功能来说, BarChart 和 PieChart 为不同的报表功能,此时在 ChartDisplay 中使用报表功能, 可以直接new对应的功能,但如果增加新的报表功能,在 ChartDisplay 中使用,就需要改代码了,这就违背了开闭 原则。

3.里氏代换原则

定义:所有引用基类(父类)的地方必须能透明地使用其子类的对象。

解说:里氏代换原则可以通俗表述为:在软件中将一个基类对象替换成它的子类对象,程序将不会产生任 何错误和 异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不 一定能够使用基类对象。 里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对 象,因此在程序中尽量 使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用 子类对象来替换父类对象。

使用里氏代换原则需要注意

(1)子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏代 换原则,为了保证系统 的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在 以父类定义的对象中使用该方法。

(2) 我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现 父接口,并实现在父类 中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地 扩展系统的功能,同时无须修改原有子类的代 码,增加新的功能可以通过增加一个新的子类 来实现。里氏代换原则是开闭原则的具体实现手段之一。

(3) Java语言中,在编译阶段,Java编译器会检查一个程序是否符合里氏代换原则,这是一个 与实现无关的、纯语法 意义上的检查,但Java编译器的检查是有局限的。

实例:我们以给客户发消息为例,给VIP客户(VipCustomer)和普通客户(CommonCustomer)发消息,在 SendMessage 中分别定义给普通会员和VIP发消息,如果以后有新的客户分类,不仅要添加客户分类,还要修改 SendMessage ,违背了开闭原则。

4.依赖倒转原则

定义:抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。

注意点:依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层 类,即使用接 口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据 类型的转换等,而不要用具体类来做 这些事情。为了确保该原则的应用,一个具体类应当只 实现接口或抽象类中声明过的方法,而不要给出多余的方 法,否则将无法调用到在子类中增 加的新方法。

在引入抽象层后,系统将具有很好的灵活性,在程序中尽量使用抽象层进行编程,而将具体 类写在配置文件中,这 样一来,如果系统行为发生变化,只需要对抽象层进行扩展,并修改 配置文件,而无须修改原有系统的源代码,在 不修改的情况下来扩展系统的功能,满足开闭 原则的要求。

在实现依赖倒转原则时,我们需要针对抽象层编程,而将具体类的对象通过依赖注入 (DependencyInjection, DI)的 方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发 生依赖关系时,通过抽象来注入所依赖的对象。 常用的注入方式有三种,分别是:构造注 入,设值注入(Setter注入)和接口注入。构造注入是指通过构造函数来传 入具体类的对象, 设值注入是指通过Setter方法来传入具体类的对象,而接口注入是指通过在接口中声明的业务 方 法来传入具体类的对象。这些方法在定义时使用的是抽象类型,在运行时再传入具体类型 的对象,由子类对象来覆 盖父类对象。

总结

1、针对接口编程
2、在接口或抽象类中定义方法、声明变量
3、类只实现接口或抽象类中的方法,不要定义多余的方法
4、给抽象对象或接口注入依赖对象时,采用依赖注入方式

实例: 我们可以把之前的开闭原则案例修改一下,利用Spring框架进行修改,可读性更强,同时遵循了开闭原则、里氏代换 原则和依赖倒转原则,如下图:

5.接口隔离原则

定义:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。

讲解:接口仅仅提供客户端 需要的行为,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接 口, 而不要提供大的总接口。在面向对象编程语言中,实现一个接口就需要实现该接口中定义的 所有方法,因此大 的总接口使用起来不一定很方便,为了使接口的职责单一,需要将大接口 中的方法根据其职责不同分别放在不同的 小接口中,以确保每个接口使用起来都较为方便, 并都承担某一单一角色。接口应该尽量细化,同时接口中的方法 应该尽量少,每个接口中只 包含一个客户端(如子模块或业务逻辑类)所需的方法即可,这种机制也称为“定制服 务”,即 为不同的客户端提供宽窄不同的接口。

总结

需要用到哪些方法,接口中就只提供哪些方法,用不到的方法,接口中不提供。

注意

需要用到哪些方法,接口中就只提供哪些方法,用不到的方法,接口中不提供。

实例:下图展示了一个拥有多个客户类的系统,在系统中定义了一个巨大的接口DataRead来服务所有的客户类。 原始设计方案:

6.合成复用原则

定义:尽量使用对象组合,而不是继承来达到复用的目的。

讲解:合成复用原则就是在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些 已有的对象,使 之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用功能 的目的。简言之:复用时要尽量使用组 合/聚合关系(关联关系),少用继承。

在面向对象设计中,可以通过两种方法在不同的环境中复用已有的设计和实现,即通过组合/ 聚合关系或通过继 承,但首先应该考虑使用组合/聚合,组合/聚合可以使系统更加灵活,降低 类与类之间的耦合度,一个类的变化对其 他类造成的影响相对较少;其次才考虑继承,在使 用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于 对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用 继承复用。

通过继承来进行复用的主要问题在于继承复用会破坏系统的封装性,因为继承会将基类的实 现细节暴露给子 类,由于基类的内部细节通常对子类来说是可见的,所以这种复用又称“白 箱”复用,如果基类发生改变,那么子类的 实现也不得不发生改变;

由于组合或聚合关系可以将已有的对象(也可称为成员对象)纳入到新对象中,使之成为新对象的一部分,因此 新对象可以调用已有对象的功能,这样做可以使得成员对象的内部实现 细节对于新对象不可见。

总结

7.迪米特法则

定义:一个软件实体应当尽可能少地与其他实体发生相互作用。

讲解:如果一个系统符合迪米特法则,那么当其中某一个模块发生修改时,就会尽量少地影响其他 模块,扩展会相 对容易,这是对软件实体之间通信的限制,迪米特法则要求限制软件实体之 间通信的宽度和深度。迪米特法则可降 低系统的耦合度,使类与类之间保持松散的耦合关系。 复用的方式: ①组合/聚合关系实现复用 ②继承实现复用 继承复用问题:会破坏系统的封装性,会把基类实现暴露给子类。 组合/聚合复用:已有对象的功能细节,对组合而成的新对象是不可见的,封装性教好。迪米特法则要求我们在设计系统时,应该尽量减少对象之间的交互,如果两个对象之间不必 彼此直接通信,那么这 两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需 要调用另一个对象的某一个方法的话,可以通 过第三者转发这个调用。简言之,就是通过引 入一个合理的第三者来降低现有对象之间的耦合度。

作用:降低系统的耦合度 实例:我们在做增删改查的时候,如果直接用控制层调用Dao,业务处理的关系会比较乱,我们需要合理增加一个中 间对象(业务层)来解决个问题。 原始设计方案:

设计模式分类

GOF中共提到了23种设计模式不是孤立存在的,很多模式之间存在一定的关联关系,在大的系统开发中常常同时使 用多种设计模式。这23种设计模式根据功能作用来划分,可以划分为3类:

(1)创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”,单例、原型、工厂方法、抽 象工厂、建造者5种设计模式属于创建型模式。

(2)结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,代理、适配器、桥接、装饰、外观、享元、 组合7种设计模式属于结构型模式。

(3)行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职 责。模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器11种设计模式属于 行为型模式。

GOF的23种设计模式:

1、单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多
例模式。
2、原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
3、工厂方法(Factory Method)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
4、抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
5、建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该
复杂对象。
6、代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、
增强或修改该对象的一些特性。
7、适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的
那些类能一起工作。
8、桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽
象和实现这两个可变维度的耦合度。
9、装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。
10、外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
11、享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
12、组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
13、模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以
不改变该算法结构的情况下重定义该算法的某些特定步骤。
14、策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响
使用算法的客户。
15、命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
16、职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通
过这种方式去除对象之间的耦合。
17、状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
18、观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,
从而影响其他对象的行为。
19、中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有
对象之间不必相互了解。
20、迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
21、访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多
个访问者对象访问。
22、备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
23、解释器(Interpreter)模式:提供如何定义语言的放法,以及对语言句子的解释方法,即解释器。

设计模式常用案例

1. 单例模式

单例模式(Singleton Pattern)是 Java 中最常见的设计模式之一。这种类型的设计模式属于创建型模式,它提供了 一种创建对象的最佳方式。

单利模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。该类还提供了一种访问它 唯一对象的方式,其他类可以直接访问该方法获取该对象实例,而不需要实例化该类的对象。

单利模式特点:

1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。

单利模式优点:

1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2、避免对资源的多重占用(比如写文件操作)。

单利模式真实应用场景:

1、网站的计数器
2、应用程序的日志应用
3、数据库连接池设计
4、多线程的线程池设计

饿汉式

创建一个单利对象 SingleModel , SingleModel 类有它的私有构造函数和本身的一个静态实例。 SingleModel 类提供了一个静态方法,供外界获取它的静态实例。 DesignTest 我们的演示类使用 SingleModel 类 来获取 SingleModel 对象。

懒汉式

懒汉式有这些特点:

1、延迟加载创建,也就是用到对象的时候,才会创建
2、线程安全问题需要手动处理(不添加同步方法,线程不安全,添加了同步方法,效率低)
3、实现容易

双重校验锁

SingleModel3 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

2.观察者模式

定义

对象之间存在一对多或者一对一依赖,当一个对象改变状态,依赖它的对象会收到通知并自动更新。 MQ其实就属于一种观察者模式,发布者发布信息,订阅者获取信息,订阅了就能收到信息,没订阅就收不到信息。

优点:

1、观察者和被观察者是抽象耦合的。
2、建立一套触发机制。

缺点:

1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。

spirng 观察者模式

ApplicationContext 事件机制是观察者设计模式的实现,通过 ApplicationEvent 类和 ApplicationListener 接口,可以实现 ApplicationContext 事件处理。

如果容器中有一个 ApplicationListener Bean ,每当 ApplicationContext 发布 ApplicationEvent 时, ApplicationListener Bean 将自动被触发。这种事件机制都必须需要程序显示的触发。

其中spring有一些内置的事件,当完成某种操作时会发出某些事件动作。比如监听 ContextRefreshedEvent 事件, 当所有的bean都初始化完成并被成功装载后会触发该事件,实现 ApplicationListener 接口可以收到监听动作,然后可以写自己的逻辑。

同样事件可以自定义、监听也可以自定义,完全根据自己的业务逻辑来处理。

对象说明:

1、ApplicationContext容器对象
2、ApplicationEvent事件对象(ContextRefreshedEvent容器刷新事件)
3、ApplicationListener事件监听对象

代理模式

定义

​ 给某对象提供一个代理对象,通过代理对象可以访问该对象的功能。主要解决通过代理去访问[不能直接访问的对象],例如 租房中介,你可以直接通过中介去了解房东的房源信息,此时中介就可以称为代理。

优点:

1、职责清晰。

2、高扩展性。

3、智能化。

缺点:

1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。

2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

代理实现方式:

基于接口的动态代理

​ 提供者:JDK官方的Proxy类。

​ 要求:被代理类最少实现一个接口。

基于子类的动态代理

​ 提供者:第三方的CGLib,如果报asmxxxx异常,需要导入asm.jar。

​ 要求:被代理类不能用final修饰的类(最终类)。

JDK动态代理

JDK动态代理要点:

1、被代理的类必须实现一个接口

2、创建代理对象的时候,用JDK代理需要实现InvocationHandler

3、代理过程在invoke中实现

我们以王五租房为例,王五通过中介直接租用户主房屋,中介在这里充当代理角色,户主充当被代理角色。

MIT License Copyright (c) 2021 水依寒 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

设计模式解析 展开 收起
Java
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Java
1
https://gitee.com/shuiyihan12/design_mode.git
git@gitee.com:shuiyihan12/design_mode.git
shuiyihan12
design_mode
design_mode
master

搜索帮助