设计模式总结

文章目录

前言:

设计模式要做的事情就是解耦。

创建型模式是将创建和使用代码解耦;

结构型模式是将不同功能的代码解耦;

行为型模式是将不同的行为代码解耦;

借助设计模式,我们利用更好的代码结构,将一大坨代码拆分成职责更单一的小类,让其满足开闭原则、高内聚低耦合等特性,以此来控制和应对代码的复杂性,提高代码的可扩展性。

每个设计模式都应该由两部分组成:

第一部分是应用场景,即这个模式可以解决哪类问题;

第二部分是解决方案,即这个模式的设计思路和具体的代码实现。

设计模式总结:

设计原则总结
设计模式意义可读性、代码简洁、可复用、可扩展、可维护
单一职责一个类应该只负责一项职责
接口隔离客户端不应该依赖他不需要的接口,如果我们依赖的接口的方法我们用不到,那就应该把这个接口拆分
依赖倒置高层模块不应该依赖低层模块,高层应该依赖低层的抽象。依赖倒置核心是面向抽象编程,使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
classA里面使用了classB,classA是高层,classB是低层。
里氏替换如何正确的使用继承。在子类中尽量不要重写父类的方法,父类的方法可以使用final修饰。如果使用继承,那么父类修改方法必须要考虑子类。如果必须使用其方法,则可以使用依赖的方式。就是在B类中创建A类的对象a,通过a调用A类的方法。
开闭原则当功能需要变化时,尽量通过扩展代码来实现变化,而不是通过修改已有的代码来实现变化。所以我们在搭建框架的时候, 用抽象构建框架,用实现扩展细节。
迪米特法则一个类对自己依赖的类知道的越少越好。迪米特法则的核心是降低类之间的耦合,不要让陌生的类出现在局部变量中。
合成复用原则尽量使用合成/聚合的方式,而不是使用继承
设计模式总结
创建型模式:主要解决对象的创建问题,封装复杂的创建过程,将创建和使用代码解耦。
单例模式定义:用来创建全局唯一的对象,一个类只允许创建一个实例,并且提供一个全局访问这个类的方法。
应用:
  • 节约内存:程序中只存在一个单例对象。
  • 处理资源访问冲突:在多线程环境下,会创建多个对象方位资源,多个对象会有互相争夺资源的问题。
  • 表示全局唯一类:从业务概念上,有些数据在系统中只应该保存一份,就比较适合设计为单例类。比如,系统的配置信息类。
工厂模式定义:有一个具体的工厂类,由给定的参数来决定创建哪种类型的对象。
应用:
  • 将实例化对象的代码提取出来,放到一个类中统一管理和维护。
  • 当对象的创建逻辑比较复杂,并且要组合其他类对象,做各种初始化操作的时候,我们推荐使用工厂模式。
补充:
由于简单工厂模式新增产品时需要直接修改工厂类,违反了开放封闭原则。因此可以使用反射来创建实例对象
原型模式定义:如果对象的创建成本比较大,我们可以利用对已有对象进行复制(拷贝)的方式,来创建新对象,以达到节省创建时间的目的。
应用:
  • 何为创建成本大:创建一个对象需要进行复杂的计算和读写等耗时操作。
  • 浅拷贝:只是拷贝了对象的引用地址,当原来对象数据变化的时候,新拷贝的对象也会实时变化。他们的hashcode是同一个。
  • 深拷贝:深拷贝重新开辟内存来存储拷贝的数据。
建造者定义:建造者模式是用来创建复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象。
结构型模式:主要解决“类或对象的组合或组装”问题,将不同功能的代码解耦。主要总结了一些类或对象组合在一起的经典结构,这些经典的结构可以解决特定应用场景的问题。
代理模式定义:为一个对象提供一个代理,以控制对这个对象的访问。
理解:代理类和原始类需要实现相同的接口,在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
应用:
  • 可以在目标对象实现的基础上,增加额外的功能操作,即扩展目标对象的功能。
  • 通过代理类,将客户端和目标类关系解耦,客户端只需要了解代理类的即可。代理类可以灵活的替换目标类。
  • 动态代理:Proxy的newProxyInstance方法
装饰者定义:使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,在不改变现有(真实)对象结构的情况下(装饰器类需要跟原始类继承相同的抽象类或者接口),动态地给该对象增加一些职责(即增加其额外功能)
理解:主要解决继承关系过于复杂的问题,通过组合来替代继承,给原始类添加增强功能。
应用:
  • 对象的功能要求可以动态地添加,也可以再动态地撤销时;
  • 装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。
  • 比如咖啡:可以添加奶、糖、不添加
适配器定义:将不兼容的接口转换为可兼容的接口
理解:适配器模式将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。
应用:
  • 封装有缺陷的接口、统一多个类的接口、替换依赖的外部系统
  • 兼容老版本接口、适配不同格式的数据
  • 适配器模式有两种实现方式:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。
外观模式定义:外观模式通过定义外观类,为子系统提供统一的接口,让子系统更方便被调用。
应用:
  • 可以用来封装系统的底层实现,隐藏系统的复杂性,提供一组更加简单易用、更高层的接口。
  • 比如:我们写了一个工具模块,给其他模块使用。
享元模式定义:当一个系统中存在着大量的重复对象,我们就可以利用享元模式,在内存中只保留一份实例,供多处代码调用。
作用:
  • 复用对象,节省内存。
桥接模式定义:用组合关系代替继承关系,将抽象与实现分离,使它们可以独立变化。
作用:
  • 组合优于继承。
  • 一个对象有多种维度,比如图形有形状颜色的组合方式、人有性别职业的组合
组合模式定义:用来处理树形结构数据。
理解:组合模式主要用来处理树形结构的数据。组合模式,将一组对象组织成树形结构,将单个对象和组合对象都看作树中的节点,以统一处理逻辑,并且它利用树形结构的特点,递归地处理每个子树,依次简化代码实现。
应用:组织架构、ViewGroup和View、文件夹
  • 代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
  • 装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。
  • 桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。
  • 适配器模式:代理模式、装饰器模式提供的都是跟原始类相同的接口,而适配器提供跟原始类不同的接口。
行为型设计模式:主要解决的就是“类或对象之间的交互”问题,将不同的行为代码解耦。
责任链定义:在职责链模式中,多个处理器依次处理同一个请求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模式。
应用:事件分发、okhttp的拦截器链
模板方法定义:在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现(或者理解为将一些具体的实现细节在子类中实现)。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。这里的“算法”,我们可以理解为广义上的“业务逻辑”。
  • 模板模式主要是用来解决复用和扩展两个问题。
  • 复用:模板模式把一个算法中不变的流程抽象到父类的模板方法 templateMethod() 中,将可变的部分 method1()、method2() 留给子类来实现。所有的子类都可以复用父类中模板方法定义的流程代码。
  • 扩展:这里所说的扩展,并不是指代码的扩展性,而是指框架的扩展性。让用户可以在不修改框架源码的情况下,定制化框架的功能。比如使用钩子函数,重写子类方法等。
  • 比如:BaseActivity、AsyncTask、图片加载流程
观察者定义:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。被依赖的对象叫作被观察者(Observable),依赖的对象叫作观察者(Observer)。
作用:将观察者和被观察者解耦。
命令模式定义:将请求(命令)封装为一个对象,使发出请求的命令者和执行请求的执行者分割开。这样两者之间通过命令对象进行沟通。可以实现命令的撤销等操作。
理解:
  • 不同的命令具有不同的目的,对应不同的处理逻辑,并且互相之间不可替换。
  • 命令模式用到最核心的实现手段,就是将函数封装成对象。我们知道,在大部分编程语言中,函数是没法作为参数传递给其他函数的,也没法赋值给变量。借助命令模式,我们将函数封装成对象,这样就可以实现把函数像对象一样使用。
应用:
  • 命令模式的主要作用和应用场景,是用来控制命令的执行。
  • 比如,异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等。
  • 比如遥控器、执行某项命令
访问者访问者模式是一种将数据操作和数据结构分离的设计模式。
理解:访问者模式针对的是一组类型不同的对象(PdfFile、PPTFile、WordFile)。不过,尽管这组对象的类型是不同的,但是,它们继承相同的父类(ResourceFile)或者实现相同的接口。在不同的应用场景下,我们需要对这组对象进行一系列不相关的业务操作(抽取文本、压缩等),但为了避免不断添加功能导致类(PdfFile、PPTFile、WordFile)不断膨胀,职责越来越不单一,以及避免频繁地添加功能导致的频繁代码修改,我们使用访问者模式,将对象与操作解耦,将这些业务操作抽离出来,定义在独立细分的访问者类中。
策略模式定义:策略模式包含一个策略接口和一组实现这个接口的策略类。
理解:在策略模式中,不同的策略具有相同的目的。不同的策略互相之间可以替换。比如,BubbleSort、SelectionSort 都是为了实现排序的,只不过一个是用冒泡排序算法来实现的,另一个是用选择排序算法来实现的。
应用:
  • 所有的策略类都实现相同的接口,客户端代码基于接口编程,可以灵活地替换不同的策略。
  • 策略的创建由工厂类来完成,封装策略创建的细节。
  • 利用策略模式来避免冗长的 if-else 或 switch 分支判断
状态模式总结:当判断一个对象状态的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用各个不同的状态类表示、系统处于哪种情况,直接使用相应的状态类对象进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-else、switch-case 等冗余语句,代码更有层次性,并且具备良好的扩展力。
迭代器如果我们的容器对象是用不同的方式实现的,有数组,还有java的集合类, 或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。 统一封装遍历的方式,只需要调用统一的方法遍历就可以了。
备忘录在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态
解释器
定义:定义一个语言或语法规则,然后使用代码来解释这个规则。
理解:比如xx城市的xx人坐公交车。如果是上海的就可以提供免费服务,如果是儿童就可以提供免费服务。基于这个规则,我们就可以使用解释器通过代码来实现这个规则。
  • 比如编译器、运算表达式计算、正则表达式、机器人等
中介者
  • 定义:在编程的时候,一个类必然会与其他类产生依赖关系。当这种依赖关系如网状错综复杂时,我们可以采用中介者模式。用一个中介对象类封装一系列对象的交互。从而使各个对象接触耦合。
应用:
中介模式的设计思想跟中间层很像,通过引入中间层,将一组对象之间的交互关系(或者说依赖关系)从多对多(网状关系)转换为一对多(星状关系)。原来一个对象要跟 n 个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低了代码的复杂度,提高了代码的可读性和可维护性。
  • 比如:mvc、mvp

一、✔️设计模式简介

1、设计模式的目的

  1. 可读性: 代码是否符合编码规范、命名是否达意、注释是否详尽、函数长短合适、是否便于阅读和理解。

2)代码简洁性:代码保持简单、逻辑清晰。

3)可复用性:尽量减少重复代码的编写,复用已有的代码。

  1. 可扩展性:可扩展性表示我们代码应对未来需求变化的能力。符合开闭原则,对修改关闭,多扩展开放。比如:代码预留了一些功能扩展点,你可以把新功能代码,直接插到扩展点上,而不需要因为要添加一个功能而大动干戈,改动大量的原始代码。

  2. 可维护性:所谓“代码易维护”就是指,在不破坏原有代码设计、不引入新的 bug 的情况下,能够快速地修改或者添加代码。所谓“代码不易维护”就是指,修改或者添加代码需要冒着极大的引入新 bug 的风险,并且需要花费很长的时间才能完成。

2、设计模式总结

设计模式可以分创建型设计模式、结构型设计模式和 行为型设计模式。

Android开发常用设计模式有:单例模式、简单工厂模式、工厂方法模式、建造者模式、

代理模式、动态代理、观察者模式、享元模式、适配器模式、责任链模式

3、设计模式原则

1) 单一职责原则

一个类应该只负责一项职责。

一、单一职责介绍

对类来说,即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。 当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为 A1,A2。

二、单一职责原则备注

  • 降低类的复杂度,一个类只负责一项职责。
  • 提高类的可读性,可维护性
  • 降低修改代码引起的风险

2) 接口隔离原则

客户端不应该依赖他不需要的接口,如果我们依赖的接口的方法我们用不到,那就应该把这个接口分拆分。

一、接口隔离原则介绍

客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。

二、使用场景

类A通过接口Interface1依赖类B,类C通过接口Interface1依赖类D,如果接口 Interface1对于类A和类C来说不是最小接口,那么类B和类D必须去实现他们不需要的方法。如下图,类A依赖B只使用1、2、3三个方法,然而类B却需要额外实现4、5两个方法,实现了它不需要的方法。

三、解决方法

将接口Interface1拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O4qrwLKF-1611410677048)(https://uploader.shimo.im/f/TgWZ3dffpQcKfL51.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8XxQ7tpc-1611410677057)(https://uploader.shimo.im/f/NJSavPwLTIbXkr2P.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

3) 依赖倒置原则

高层模块不应该依赖低层模块,高层应该依赖低层的抽象。使用抽象的目的是制定好规范,把展现细节的任务交给他们的实现类去完成。

一、依赖倒置原则介绍

  1. 高层模块不应该依赖低层模块,高层应该依赖低层的抽象。

classA里面使用了classB,classA是高层,classB是低层。

  1. 抽象不应该依赖细节,细节应该依赖抽象

  2. 依赖倒转(倒置)的中心思想是面向接口编程

  3. 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象 指的是接口或抽象类,细节就是具体的实现类

  4. 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成

二、依赖倒置原则总结

  1. 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好.

  2. 变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化

  3. 继承时遵循里氏替换原则

代码:

https://shimo.im/docs/kPQr3YxJdckHgYPP/read

4) 里氏替换原则

里式替换原则的作用是:如何正确的使用继承。在子类中尽量不要重写父类的方法,父类的方法可以使用final修饰。如果使用继承,那么父类修改方法必须要考虑子类。如果必须使用其方法,则可以使用依赖的方式。就是在B类中创建A类的对象a,通过a调用A类的方法。

一、里式替换原则介绍

是一名姓里的美国女士提出的,所以叫里式替换原则。里式替换原则的作用是:如何正确的使用继承。在子类中尽量不要重写父类的方法,父类的方法可以使用final修饰。

二、为什么要提出里式替换原则

继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承, 则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子 类的功能都有可能产生故障。

解释:我们使用A类,写了sum方法。

如果B类想使用A类的方法,B类可以继承A类去使用sum方法。但是这样会导致A类如果方法修改了,要去考虑给B类造成的影响。B类也可能会因为人为原因去重写sum方法,那么sum方法就会被错误使用。

所以,如果B类想使用A类的方法,可以使用依赖的方式。就是在B类中创建A类的对象a,通过a调用sum方法。

5) 开闭原则

开闭原则的要求是:当功能需要变化时,尽量通过扩展代码来实现变化,而不是通过修改已有的代码来实现变化。

所以我们在搭建框架的时候, 用抽象构建框架,用实现扩展细节。

一、开闭原则介绍

当功能需要变化时,尽量通过扩展代码来实现变化,而不是通过修改已有的代码来实现变化。

所以我们在搭建框架的时候, 用抽象构建框架,用实现扩展细节。

解释:我们增加功能的时候,要新增代码,而不是修改代码。因为原有的代码可能被多处使用使用,这样就会造成影响。原则是:我们新写的代码,不要对之前的功能产生影响。

二、解释

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7DVxRqs2-1611410677059)(https://uploader.shimo.im/f/syzzonOW2KITnWjz.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

上图代码缺点:当我们增加新功能时,比如增加一个新图形。这样会修改原来的GraphicEditor这个类,这样会对之前的功能产生影响。

解决:创建Shape类做成抽象类,并提供一个抽象的draw方法,让子类去实现即可。这样我们有新的图形种类时,只需要让新的图形类继承Shape,并实现draw方法即可,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G5CefdPa-1611410677061)(https://uploader.shimo.im/f/q15tEIMvQQjEJPs5.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

6) 迪米特法则

迪米特法则的核心是降低类之间的耦合,不要让陌生的类出现在局部变量中。

一、迪米特法则介绍

  • 迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。
  • 迪米特法则的核心是降低类之间的耦合,由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系, 并不是要求完全没有依赖关系。
  • 迪米特法则还有个更简单的定义:只与直接的朋友通信。直接的朋友:我们成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。

7) 合成复用原则

原则是尽量使用合成/聚合的方式,而不是使用继承

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iqAzv0Iy-1611410677062)(https://uploader.shimo.im/f/AHZD7rd5si9YH11r.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

4、如何 在Android源码寻找设计模式

比如我们想找中介者设计模式,只需要再源码中搜索关键词“Mediator”,就会找到相关类。比如KeyguardViewMediator。

-----创建型设计模式-----

二:✔️单例模式

1、单例模式定义

保证一个类仅有一个实例,提供一个全局访问这个类的方法。

2、为什么使用单例

(1)单例模式在内存中只生成一个实例,减少了内存开支。

(2)单例模式避免了对资源的多重占用,例如写文件操作,避免对同一个文件的同时写操作。

(3)单例模式设置了系统的全局访问点,优化了共享了资源访问。

缺点:

(1)单例模式会常驻内存,如果不使用会浪费内存。这时候我们可以使用懒加载方式。

(2)单例对象如果持有context,容易造成内存泄漏。此时最好使用Application Context。

3、单例模式的实现

1)饿汉模式

  • 实现:可以使用静态常量的方法和静态代码块的方法实现。
  • 优点:写法简单,在类加载的时候就完成了实例化避免了线程同步的问题。
  • 缺点:在类加载的时候就完成实例化,没有达到懒加载的效果。如果从始至终未使用这个实例,则会造成内存的浪费。
    public class Singleton {
        private static Singleton instance = new Singleton;
        private Singleton () {
        }
        public static Singleton getInstance() {
            return instance;
        }
    }

2)懒汉模式

一、介绍

  • 懒汉式是线程不安全的,但保证了懒加载的效果。
  • 为了解决线程不安全,我们使用同步锁。如果在方法上添加同步锁synchronized,每次调用的时候,都会去调用synchronized,造成不必要的开销,效率低下。
   public class Singletion {
        private static Singleton instance;
        private Singleton () {
        }
        public static synchronized Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }

3)双重检查模式(DCL)

一、简介

  • DCL进行了两次判断, 第一次判空,省去了不必要的同步。第二次是在Singleton等于空时才创建实例。使用volatile保证了实例的可见性。 DCL在一定程度上解决了资源的消耗和多余的同步、线程安全等问题,但是在某些情况下会失效。
  • DCL优点:资源利用率高,第一次执行getInstance时单例对象才会被实例化,效率高。
  • 缺点:第一次加载稍慢,当代码在并发场景比较复杂或低于JDK1.5版本下使用,会出现失效。因为无序性问题。在jdk1.5之后,我们可以通过添加volatile关键字来解决这个问题。

代码:

  public class Singleton {
        private static volatile Singleton instance;
        private Singleton {
        }
        public static Singleton getInstance() {
            if (instance == null) {//1、多线程情况下,会有多个线程进到此步骤
                synchronized (Singleton.class) {//2、这里加锁,保证只有一个线程进入
                    if (instance == null) {//3、这里保证了只有一个线程,对其判断是否为空
                       instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }

2、解释
假设线程A执行到instance = new Singleton()语句,看起来只有一行代码,但实际上它并不是原子操作,这句代码最终会被编译成多条汇编指令,它大致做了3件事:

1)给instance的实例分配内存。

2)调用Singleton()构造函数,初始化成员字段。

3)将instance对象指向分配的内存空间(此时instance就不是null了)。

但是,由于Java编译器允许处理器乱序执行,以及JDK1.5之前JMM中的Cache、寄存器到主内存回写顺序的规定,上面的2和3的顺序是无法保证的,也就是说,执行顺序可能是1-2-3也可能是1-3-2。如果是后者,并且在3执行完毕、2未执行之前,被切换到线程B上,这时候instance因为已经在线程A内执行过了3,instance已经是非空了,所以,线程B直接取走instance,再使用时就会出错,这就是DCL失效问题,而且这种难以跟踪难以重现的错误可能会隐藏很久。

在JDK1.5之后,SUN官方已经注意到这种问题,调整了JVM,具体化了volatile关键字,因此,如果JDK1.5或之后的版本,只需要将instance的定义改成private volatile static Singleton instance = null就可以保证instance对象每次都是从主内存中读取,就可以使用DCL的写法来完成单例模式。当然,volatile或多或少也会影响到性能,但考虑到程序的正确性,这点牺牲也是值得的。

3、并发编程中经常遇到三个问题

1:并发编程中经常遇到三个问题:原子性问题、可见性问题和有序性问题。

原子性问题:即一个操作或者多个操作,要么全部执行并且执行的过程中不会被任何因素打断,要么就都不执行。比如转账。

可见性问题:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。

有序性:程序执行的顺序按照代码的先后顺序执行。

4:volatile关键字

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

java提供了volatile()关键字来保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

2)禁止进行指令重排序。保证有序性。

3)但无法保证原子性

volatile关键字能保证可见性没有错,但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。

4)静态内部类单例模式

  • 外部类的加载并不会导致内部类的加载,而是在调用getInstance方法时才会加载内部类,保证了懒加载。
  • JVM帮助我们保证了线程的安全性,在类初始化的时候是只有一个线程可以进入,保证线程安全。
  public class Singleton() {
        private Singleton() {
        }
        public static Singleton getInstance() {
            return SingletonHolder.sInstance;
        }
        private static class SingletonHolder {
            private static final Singleton sInstance = new Singleton();
        }
    }

6、枚举单例

  • 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
  • 枚举实例的创建是线程安全的,并且在任何情况下都是单例。
  • 简单、可读性不高。
    public enum Singleton {
        INSTANCE;
        public void doSomeThing() {
        }
    }

4、补充

1)上面的几种单例模式创建的单例对象被反序列化时会重新创建实例,可以重写readReslove方法返回当前的单例对象。

5、使用静态方法or单例模式

静态方法:在程序启动时就分配了静态区域的内存,也会常驻内存。静态方法方式调用方法的方式比单例调用更简洁一些。

单例模式:在堆中创建了实例加上静态变量的引用,只要app不销毁就会常驻内存。(针对个别单例模式,如果是静态内部类,则是静态方法区)。单例中可以存储一些常驻内存的变量(如果不用单例,就要写成静态变量)。

三、✔️工厂模式

一、工厂模式小结

工厂模式是创建型设计模式,在任何需要生成复杂对象的地方都可以使用工厂方法模式。

  1. 工厂模式的意义

将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。

  1. 三种工厂模式 (简单工厂模式、工厂方法模式、抽象工厂模式)

  2. 设计模式的原则

  • 创建对象实例时,不要直接 new 类, 而是把这个new 类的动作放在一个工厂的方法中,并返回。
  • 不要让类继承具体类,而是继承抽象类或者是实现interface(接口)
  • 不要覆盖基类中已经实现的方法。

二、简单工厂模式

1、简单工厂模式定义

简单工厂模式有一个具体的工厂类,可以生成多个不同的产品。

2、简单工厂模式优缺点

优点:
  • 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
  • 客户端无需知道所创建具体产品的类名,只需知道参数即可。
  • 也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。
缺点:
  • 简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。
  • 使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度
  • 系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂

3、使用示例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bi5Wh6hY-1611410677063)(https://uploader.shimo.im/f/U5rqIwyOS9vuTIaG.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

1)简单工厂模式的主要角色如下:
  • 简单工厂(SimpleFactory):是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
  • 抽象产品(Product):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。
  • 具体产品(ConcreteProduct):是简单工厂模式的创建目标。
2)代码示例
1 创建抽象产品类,定义公共接口:
//抽象产品类
public abstract class Product {
public abstract void show();
}
2 创建具体产品类,继承Product类:
//具体产品类A
public class ProductA extends Product {
@Override
public void show() {
System.out.println(“product A”);
}
}
//具体产品类B
public class ProductB extends Product {
@Override
public void show() {
System.out.println(“product B”);
}
}
 3 创建工厂类,创建具体的产品:
public class Factory {
public static Product create(String productName) {
    Product product = null;
    //通过switch语句控制生产哪种商品
    switch (productName) {
        case "A":
            product = new ProductA();
            break;
        case "B":
            product = new ProductB();
            break;
    }
    return product;
}
}

三、工厂方法模式

1、UML图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-960G7n5g-1611410677064)(https://uploader.shimo.im/f/CyncHhGxdKtPKMj7.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

2、定义

工厂方法模式需要创建:抽象工厂、具体工厂。为每一个产品都需要创建一个具体的工厂类。

1)如果工厂里面的逻辑简单或者只需要使用一个具体工厂,我们可以使用简单工厂模式。

2)为了简化需要创建多个具体工厂,我们可以使用反射的方式来更简洁的生成具体产品对象。见代码-3

3、代码示例

优缺点:

  • 由于简单工厂模式新增产品时需要直接修改工厂类,违反了开放封闭原则。因此可以使用反射来创建实例对象,确保能够遵循开放封闭原则。

  • 相比简单工厂,如果我们需要新增产品类,无需修改工厂类,直接创建产品即可。

反射实现工厂类
public static <T extends Product> T create(Class<T> clz) {
    Product product = null;
    try {
        product = (Product) Class.forName(clz.getName()).newInstance();//反射出实例
    } catch (Exception e) {
        e.printStackTrace();
    }
    return (T) product;
}
//调用
public void test() {
Factory.create(ProductA.class).show();//生产ProductA
Factory.create(ProductB.class).show();//生产ProductB
}

四、✔️原型模式

一、定义

Java中Object类是所有类的根类,Object类提供了一个clone()方法,该方法可以 将一个Java对象复制一份,但是需要实现clone的Java类必须要实现一个接口Cloneable, 该接口表示该类能够复制且具有复制的能力

二、浅拷贝和深拷贝

1)浅拷贝

  1. 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。

  2. 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类 的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成 员变量值。

3)浅拷贝是使用默认的 clone()方法来实现 sheep = (Sheep) super.clone();

2)深拷贝

  1. 复制对象的所有基本数据类型的成员变量值

  2. 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变 量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对 整个对象进行拷贝

  3. 深拷贝实现方式1:重写clone方法来实现深拷贝

  4. 深拷贝实现方式2:通过对象序列化实现深拷贝(推荐)

三、原型模式的注意事项和细节

  1. 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率

  2. 不用重新初始化对象,而是动态地获得对象运行时的状态

  3. 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化, 无需修改代码

  4. 在实现深克隆的时候可能需要比较复杂的代码

  5. 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则。

五、✔️建造者模式

一、定义

建造者模式将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象 的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

二、使用场景

1)相同的方法不同的执行顺序,产生不同的事件结果时。

2)当初始化一个对象时,如参数特别多,且很多参数都有默认值时。

三、UML类图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z7txS8eP-1611410677065)(https://uploader.shimo.im/f/P9SBXXPxzwJFlwEh.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

  1. Product(产品角色): 一个具体的产品对象。

  2. Builder(抽象建造者): 创建一个Product对象的各个部件指定的 接口/抽象类。

  3. ConcreteBuilder(具体建造者): 实现接口,构建和装配各个部件。

  4. Director(指挥者): 构建一个使用Builder接口的对象。它主要是用于创建一个 复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是: 负责控制产品对象的生产过程。

四、建造者模式的注意事项和细节

  1. 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解 耦,使得相同的创建过程可以创建不同的产品对象

  2. 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替 换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同 的产品对象

  3. 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法 中,使得创建过程更加清晰,也更方便使用程序来控制创建过程

  4. 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程, 系统扩展方便,符合 “开闭原则”

  5. 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间 的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。

  6. 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化, 导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式.

  7. 抽象工厂模式VS建造者模式 抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不 同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品 由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要 目的是通过组装零配件而产生一个新产品

五、代码实现

六、AlertDialog解析

AlertDialog的builder同时扮演了builder、ConCreateBuilder和Director的角色。对建造者模式进行了精简。

-----结构型设计模式-----

六、✔️代理模式

一、定义

为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象。这样做的好处是:

可以在目标对象实现的基础上,增加额外的功能操作,即扩展目标对象的功能。

通过方代理类,将客户端和目标类关系解耦,客户端只需要了解代理类的即可。

二、静态代理和动态代理

代理模式可以分为两种:

(1)静态代理:代理类和被代理类在运行前关系已经确定了。由程序员创建源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。

(2)动态代理:代理类和被代理类的关系在程序运行时才确定。在程序运行时,运用反射机制动态创建而成。

三、代理模式角色说明

Subject(抽象主题类):接口或者抽象类,作用是声明真实主题类与代理类的共同的方法,这样任何使用真实主题类的地方都可以使用代理类。

RealSubject(真实主题类):也称作被代理类,负责具体业务逻辑的执行。客户端可以通过代理类间接的调用真实主题类的方法。

Proxy代理类:持有真实主题类的引用,从而可以在任何时候操作真实主题对象;

Client客户端类:使用代理模式的地方。

四、静态代理

1)UML图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OpjMstJX-1611410677066)(https://uploader.shimo.im/f/o2GypVVMdrjxCF51.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

2)代码示例

以图片加载为例

/**
 * Subject(抽象主题类)
 */
public interface ImageLoad {
    void loadImage();
}
 
/**
 * RealSubject(真实主题类)
 */
public class GlideImageLoad implements ImageLoad {
    @Override
    public void loadImage() {
        System.out.println("Glide库 加载图片");
    }
}
 
/**
 * Proxy代理类,持有真实主题类的引用
 */
public class ImageLoadProxy implements ImageLoad {
    private ImageLoad imageLoad;
 
    public ImageLoadProxy() {
        imageLoad = new GlideImageLoad();
    }
 
    @Override
    public void loadImage() {
        System.out.println("代理类加载图片");
        if (imageLoad != null) {
            imageLoad.loadImage();
        }
    }
}
 
/**
 * Client客户端类:使用代理模式的地方
 */
public class ImageLoadDemo {
    public static void main(String[] args) {
        ImageLoadProxy imageLoadProxy = new ImageLoadProxy();
        imageLoadProxy.loadImage();
    }
}

3)静态代理优缺点

  • 优点:1)在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展。

2)通过增加代理类,将使用类和目标类解耦。

  • 缺点:1)因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类。

2)一旦接口增加方法,目标对象与代理对象都要维护

五、动态代理

1、动态代理简介

静态代理扩展和维护困难,因为代码写的太固定,没有可替换的余地。针对这种问题,我们可以通过反射来解决。我们利用反射机制返回一个动态代理对象,然后通过动态代理调用目标对象方法。

反射可以在程序运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

2、如何实现动态代理

  1. 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理

  2. 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象。

  3. 创建动态代理,需要使用Proxy的newProxyInstance方法。

3、代码实现

public class ProxyFactory {
    //维护目标对象
    private Object target;
    //通过构造器传入目标对象
    //目标对象也可以不需要传入,在当前类中直接创造目标对象。
    //比如:target=new GlideImageLoad();这样就不需要在使用类创建目标对象了。
    public ProxyFactory(Object target) {
        this.target = target;
    }
    //给目标对象生成动态代理对象
    public Object getProxyInstance() {
        /**
         * 1、ClassLoader:指定当前目标对象使用的类加载器。
         *   获取类加载器的方法固定:target.getClass().getClassLoader()
         * 2、interfaces:目标对象实现的接口类型,使用泛型方法确认类型。
         *    方法固定:target.getClass().getInterfaces()
         *3、InvocationHandler:事件处理,执行目标对象方法时,会触发invoke方法。会把目标对象方法作为参数传入。
         */
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /**
                 *  proxy:指的是我们所代理的那个真实对象
                 *  method:指的是我们所要调用真实对象的某个方法的Method对象
                 *  args:指的是调用真实对象某个方法时接受的参数
                 */
                Object val = method.invoke(target, args);
                return val;
            }
        });
    }
}

class ProxyDemo {
    public static void main(String[] args) {
        //创建目标对象
        IImageLoad imageLoad = new GlideImageLoad();
        //给目标对象创建动态代理对象
        IImageLoad imageLoadProxy = (IImageLoad) new ProxyFactory(imageLoad).getProxyInstance();
        //调用
        imageLoadProxy.loadImg("");
        或者
        我们在ProxyFactory直接创建new GlideImageLoad(),然后在客户端就不需要创建了。直接调用getProxyInstance方法获取代理对象。
    }
}

七、✔️外观模式

一、定义

外观模式通过定义外观类,用以屏蔽内部子系统的细节,使得调用端只需跟外观类发生调用,而无需关心子系统的内部细节

二、UML图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-02KtEkWK-1611410677067)(https://uploader.shimo.im/f/XVPiNmRMRrGxj4jz.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

角色说明:

  1. 外观类(Facade): 为调用端提供统一的外观类, 外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象

  2. 调用者(Client): 外观类的调用者

  3. 子系统的集合:指模块或者子系统,处理外观Facade对象指派的任务,他是功能的实际提供者。

三、注意事项和细节

  1. 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性

  2. 外观模式降低客户端与子系统的耦合,让子系统内部的模块更易维护和扩展

  3. 通过合理的使用外观模式,可以帮我们更好的划分访问的层次

  4. 当系统需要进行分层设计时,可以考虑使用Facade模式

  5. 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口, 让新系统与Facade类交互,提高复用性

八、✔️享元模式

一、定义

享元顾名思义是共享的单元,享元模式的作用是复用对象,节省内存。

当一个系统中存在着大量的重复对象,我们就可以利用享元模式,在内存中只保留一份实例,供多处代码调用。通过工厂模式,在工厂类中,定义一个map来存储已经创建过的享元对象,来达到复用的目的。

举个例子:围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对 象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用 享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解 决了对象的开销问题

二、UML图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iXTBFkbI-1611410677067)(https://uploader.shimo.im/f/2ahBEWI187Q4CsJx.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

  1. FlyWeight 是抽象的享元角色, 他是产品的抽象类, 同时定义出对象的外部状态和内部状态的接口或实现

  2. ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务

  3. UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂。

三、注意事项

  1. 享元模式提出了两个要求:细粒度和共享对象。即将对象的信息分为两个部分:内部状态和外部状态

  2. 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变

  3. 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。

4、 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。

四、享元模式vs单例、缓存、对象池

1、享元模式vs单例

单例模式中,一个类只能创建一个对象。单例模式的目的是保证一个类只有一个实例,避免重复创建节省内存。

而在享元模式中一个类可以创建多个对象,每个对象被多处代码共享引用。应用享元模式是为了对象复用,节省内存。

2、享元模式vs缓存

享元模式的缓存意思是存储的意思。我们平时所讲的缓存主要是为了提高访问效率,而非复用。

3、享元模式vs对象池

对象池的目的是:为了避免频繁地进行对象的创建和释放导致内存碎片,我们可以预先申请一篇连续的内存空间。就是我们说的对象池。每次创建对象时,从对象池取出一个空闲对象使用,对象使用完成之后,再放回对象池以供后续复用。此时,使用的对象,是不能被同时复用的。这里的复用可以理解为重复使用,主要目的是节省时间,避免频繁的gc。

享元模式的复用可以理解为共享使用,就是同一个对象可以同时被多处引用。目的是节省空间,不需要重复创造相同的对象。

五、示例

我们制作网站。网站有三种类型,新闻、视频、博客。当客户需要新闻网站时,我们就返回新闻网站。需要视频网站时,我们就返回视频网站。那么网站就是根据类型区分,形成可以服用的对象。假如现在多了用户使用网站,用户是不一样的不可服用。

所以本需求中:网站是可复用的属于内部状态,用户是外部状态。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YOC5qZX4-1611410677068)(https://uploader.shimo.im/f/WjuvG8Dj12Ui4jZi.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

思想:我们通过WebSiteFactory创建website,然后通过依赖的方式引入用户。

1)创建抽象类

public abstract class WebSite {
   public abstract void use(User user);
}

2)实现类

public class ConcreteWebSite extends WebSite {
   
   private String type = "";
   
   public ConcreteWebSite(String type) {
      this.type = type;
   }
   @Override
   public void use(User user) {
      System.out.println("网站类型:" + type + "使用者:" + user.getName());
   }
   
}

3、工厂类

public class WebSiteFactory {
   private HashMap<String, ConcreteWebSite> pool = new HashMap<>();
   public WebSite getWebSiteCategory(String type) {
      if(!pool.containsKey(type)) {
         pool.put(type, new ConcreteWebSite(type));
      }
      
      return (WebSite)pool.get(type);
   }
}

4、使用

//获取享元对象,网站
WebSite webSite1 = factory.getWebSiteCategory("新闻");
webSite1.use(new User("tom"));

九、✔️适配器模式

一、定义

适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。适配器也可以理解为转换器。

二、使用场景

1)系统需要使用的类,而此类的接口不符合系统的需要。

2)需要一个统一的输出接口,而输入端的类型不可预知。

三、UML类图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VxmREsEt-1611410677069)(https://uploader.shimo.im/f/31JQ5jgGuyvvQtk2.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

Target:目标角色,也是客户端所期待的得到的接口。

Adaptee:现在需要适配的接口,不符合客户端的需求。但他是具体的执行者。

Adapter:适配器角色。将源接口Adaptee转换成目标接口Target。

四、适配器模式分类

适配器模式可以分为三类:类适配器模式、对象适配器模式、接口适配器模式。

1)类适配器模式

基本介绍:Adapter类,通过继承 src类,实现 dst 类接口,完成src->dst的适配。

类适配器模式注意事项和细节

  1. Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点, 因为这要求dst必须是接口,有一定局限性;

  2. src类的方法在Adapter中都会暴露出来,也增加了使用的成本。

  3. 由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aIEsZvOZ-1611410677070)(https://uploader.shimo.im/f/tFcHsdtBkwuhzbTl.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

代码

//被适配者,src类。
public class Voltage220V {
   public int output220V() {
      int src = 220;
      return src;
   }
}
//Target,客户端期待的接口
public interface IVoltage5V {
   public int output5V();
}
//适配器
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
   @Override
   public int output5V() {
      int srcV = output220V();
      int dstV = srcV / 44 ; //调用src类的方法,进行实际操作
      return dstV;
   }
}
现在就可以通过调用VoltageAdapter的output5V方法,实际是src去处理。

2)对象适配器模式

基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而是持有src类的实例,以解决兼容性的问题。 即:持有 src类,实现 dst 类接口, 完成src->dst的适配。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BF6wZGpk-1611410677070)(https://uploader.shimo.im/f/2A9qwgNv9UxUeFTk.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

//和类适配器的区别在于,VoltageAdapter不继承src类,而是持有src的对象。
public class VoltageAdapter  implements IVoltage5V {
    //src对象的引用
   private Voltage220V voltage220V;
   public VoltageAdapter(Voltage220V voltage220v) {
      this.voltage220V = voltage220v;
   }
   @Override
   public int output5V() {
      int dst = 0;
      if(null != voltage220V) {
         int src = voltage220V.output220V();
         dst = src / 44;
      }
      return dst;
   }
}

3)接口适配器模式

当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。 接口适配器适用于一个接口不想使用其所有的方法的情况。

//这是我们的目标target接口
public interface Interface4 {
   public void m1();
   public void m2();
   public void m3();
   public void m4();
}
//我们可能不需要接口提供的所有方法,所以我们定义一个抽象类。并为每一个方法提供空实现。子类有选择的去实现方法。
public abstract class AbsAdapter implements Interface4 {
   public void m1() {
   }
   public void m2() {
   }
   public void m3() {
   }
   public void m4() {
   }
}
//比如我们常用的listener

十、✔️桥接模式

一、定义

将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

二、使用场景

1)比如图形,有不同的形状和不同的颜色。那么就有m*n中图形,不但子类众多,而且扩展困难。

2)不同性别和职业的男女、支持不同平台和不同文件格式的媒体播放器等

3)不同品牌类型的手机。

三、UML类图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i08MNuQ2-1611410677071)(https://uploader.shimo.im/f/il26R0dXTLwXJ3Q0.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

  1. 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
  2. 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
  3. 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
  4. 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。

四、代码应用

一、场景

假如:我们现在有折叠、直立的手机。手机有华为、vivo、小米等品牌。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QyeKAmWe-1611410677072)(https://uploader.shimo.im/f/cen8jkeRwGk0gYYH.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HeswZlPK-1611410677073)(https://uploader.shimo.im/f/tASt26Q2iMd4c0BB.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

二、使用普通解决方案的问题

  1. 扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。

  2. 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这 样增加了代码维护成本.

三、解决方案-使用桥接模式[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dILOVOIC-1611410677074)(https://uploader.shimo.im/f/8wAklCAQIReAulHv.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

四、代码

https://shimo.im/docs/vPGqVcV38hjJxGkT/read

十一、✔️装饰者模式

一、定义

装饰者模式:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)。

通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰模式的目标。

二、装饰者结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SzFASq3t-1611410677075)(https://uploader.shimo.im/f/Cj4OGCvH50zNsMob.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

角色解析:

  • Component角色:抽象构件,定义一个抽象接口用来规范准备接收附加责任的对象。
  • ConCreateComponent:具体构件角色,实现抽象构件,通过装饰者角色为其添加一些职责功能。
  • Decorator:抽象装饰角色,继承抽象构件,并包含具体构件的实例。可以通过其子类扩展具体构件的功能。
  • ConCreateDecorator:具体装饰者角色,实现抽象装饰的相关方法,并给具体构件对象添加功能。

三、使用场景

  • 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
  • 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰模式却很好实现。
  • 当对象的功能要求可以动态地添加,也可以再动态地撤销时。

比如:星巴克有美式咖啡、意大利咖啡,咖啡里可以添加调料:豆浆、牛奶、巧克力。我们要实现顾客可以点单品咖啡、也可以单品咖啡+调料的组合。

这里我们就可以使用装饰者模式,这里的主体是单品咖啡,装饰者就是各调料。

四、装饰者优缺点

装饰(Decorator)模式的主要优点有:

  • 装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用
  • 通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果
  • 装饰器模式完全遵守开闭原则

其主要缺点是:装饰模式会增加许多子类,过度使用会增加程序得复杂性。

五、具体实现

https://shimo.im/docs/YdjYrY6YkQKTjh8w/read

十二、✔️组合模式

一、定义

组合模式也叫整体-部分(Part-Whole)模式,它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性,属于结构型设计模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-42itmNN1-1611410677077)(https://uploader.shimo.im/f/c0iFkC5LxsSaQQLX.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

由上图可以看出,其实根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶子节点与树枝节点在语义上不属于用一种类型。但是在组合模式中,会把树枝节点和叶子节点看作属于同一种数据类型(用统一接口定义),让它们具备一致行为。

这样,在组合模式中,整个树形结构中的对象都属于同一种类型,带来的好处就是用户不需要辨别是树枝节点还是叶子节点,可以直接进行操作,给用户的使用带来极大的便利。

二、使用场景

例如:组织架构,可以描述成一棵树的时候。

Android中view和viewgroup。

  1. 在需要表示一个对象整体与部分的层次结构的场合。
  2. 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。

三、组合模式结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lSjlHAP0-1611410677080)(https://uploader.shimo.im/f/IcpIkljhfKzCgcHx.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

组合模式包含以下主要角色。

  1. 抽象构件(Component)角色:这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component 子 部件, Component 可以是抽象类或者接口
  2. 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。
  3. 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。

四、组合模式优缺点

组合模式的主要优点有:

  • 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
  • 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;

其主要缺点是:

  • 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
  • 不容易限制容器中的构件;
  • 不容易用继承的方法来增加构件的新功能;

五、具体实现

https://shimo.im/docs/w9xd6wjwCQCyPHKx/read

-----行为型设计模式-----

十三、✔️责任链模式

http://c.biancheng.net/view/1383.html

一、定义

责任链模式也叫职责链模式。它的定义是:使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理这个请求为止。

二、使用场景

多个对象可以处理同一个请求,但具体由哪个对象处理则在运行时动态决定。

例如:事件分发、okhttp的拦截器链

三、优缺点

1)优点

  • 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
  • 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
  • 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
  • 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
  • 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

2)缺点

  • 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
  • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  • 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

四、UML图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eZfMqK05-1611410677084)(https://uploader.shimo.im/f/F7esziBlYayqg9I1.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

  1. Handler : 抽象的处理者, 定义了一个处理请求的接口, 同时含义另外Handler

  2. ConcreteHandlerA , B 是具体的处理者, 处理它自己负责的请求, 可以访问它的后继者(即下一个处 理者), 如果可以处理当前请求,则处理,否则就将该请求交个 后继者去处理,从而形成一个职责链

  3. Request ,表示一个请求

五、职责链模式的注意事项和细节

  1. 将请求和处理分开,实现解耦,提高系统的灵活性

  2. 简化了对象,使对象不需要知道链的结构

  3. 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般 通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值, 超过则不允许该链建立,避免出现超长链无意识地破坏系统性能

  4. 调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂

  5. 最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪 等审批流程、Java Web中Tomcat对Encoding的处理、拦截器

十四、✔️模板方法模式

一、定义

定义一个操作中算法的框架,而将一些具体的实现细节在子类中实现。

比如:我们知道了一个算法的关键步骤,并确定了这些步骤的执行顺序。但这些步骤的具体实现是未知的,或者某些步骤的实现会动态改变的。这样可以使用模板方法模式。

二、使用场景

  • 多个子类有公有的方法,并且逻辑基本相同时
  • 重要复杂的算法,可以把核心的算法设计为模板方法,周边相关细节功能由各个子类去实现。
  • 重构时,把相同的代码抽取到父类中,可以通过钩子函数约束其行为。

例如:AsyncTask、Activity的启动、图片加载的时候我们定义好图片的加载流程,但是加载不同的资源就由子类去实现。我们写的BaseActivity等

三、UML类图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cbiT5Fae-1611410677086)(https://uploader.shimo.im/f/1F5NIhyS5c5a5YPe.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

  1. AbstractClass 抽象类, 类中实现了模板方法(template),定义了算法的骨 架,具体子类需要去实现 其它的抽象方法operationr2,3,4

  2. ConcreteClass 实现抽象方法operationr2,3,4, 以完成算法中特定子类的步骤

四、模板方法模式的注意事项和细节

  1. 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改

  2. 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。

  3. 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不 变,同时由子类提供部分步骤的实现。

  4. 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加, 使得系统更加庞大

  5. 一般模板方法都加上final关键字, 防止子类重写模板方法.

十五、✔️观察者模式

一、定义

建立一个被观察者(Observable)对观察者(Observer)的依赖关系,并且做到观察者数据发生变化的时候,依赖这个被观察者的观察者也能够同步改变。 通俗的说就是:被观察者变化的时候,观察者也同步做出改变。

源码:Observable

二、UML图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MsfcuxeS-1611410677087)(https://uploader.shimo.im/f/aDlkGMGowVyxeM7t.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

Subject:被观察者抽象类

ConCreateSubject:具体被观察者

Observer:观察者

十六、✔️命令模式

一、定义

将请求封装为一个对象,从而让用户使用不同的请求把客户端参数化;对请求排队或记录请求日志,以及支持可撤销操作。

作用:使发出请求的责任者和执行请求的责任者分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。

二、使用场景

  • 需要支持取消操作
  • 需要支持事务操作
  • 需要支持修改日志功能,这样当系统崩溃时,这些修改可以被重新执行一次。
  • 请求调用者需要与请求接收者解耦时,命令模式可以使调用者和接收者不直接交互。

Android的事件分发机制,Android的每一个事件在屏幕产生后,经底层逻辑将其转换为一个NotifyArgs对象。

命令模式在菜单之类的设计应用广泛,比如执行某项命令,并支持撤销或重做等功能。

三、优缺点和注意事项

命令模式的主要优点如下。

  1. 通过引入中间件(抽象接口)降低系统的耦合度。
  2. 扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。
  3. 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
  4. 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
  5. 可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。

其缺点是:

  1. 可能产生大量具体的命令类。因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性。
  2. 命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。不过这也是设计模式的通病,抽象必然会额外增加类的数量,代码抽离肯定比代码聚合更加难理解。

四、UML图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ki8wKUqB-1611410677088)(https://uploader.shimo.im/f/hIYnw7Yl8g6FjFkz.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

  1. 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
  2. 具体命令类(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
  3. 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
  4. 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。

五、具体使用

https://shimo.im/docs/g3V6vCWwhWYwTYR9/read

十七、✔️访问者

一、定义

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下,定义作用于这些元素的新的操作。

理解:访问者模式是一种将数据操作和数据结构分离的设计模式。软件系统中拥有一个由许多对象Element构成的、比较稳定的对象结构ObjectStructure。这些对象Element都拥有一个accept方法用来接收访问者对象的访问。访问者Visitor是一个接口,它拥有visit方法,这个方法对访问到的对象结构ObjectStructure中不同类型的元素Element作出不同的操作处理。在对象结构的一次访问过程中,我们会遍历整个对象结构,对每一个元素都调用accept方法。在每一个元素的accept方法中会调用访问者的visit的方法。从而使访问者得以处理对象结构中的每一个元素。我们可以针对对象结构设计不同的访问者来完成不同的操作,以达到区别对待的效果。

二、使用场景

需要对一个对象结构中的对象进行很多不同操作 (这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决。

通常在以下情况可以考虑使用访问者(Visitor)模式。

  1. 对象结构相对稳定,但其操作算法经常变化的程序。
  2. 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
  3. 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。

三、优缺点和注意事项

访问者(Visitor)模式是一种对象行为型模式,其主要优点如下。

  1. 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  2. 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
  3. 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
  4. 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

访问者(Visitor)模式的主要缺点如下。

  1. 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
  2. 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
  3. 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

四、UML图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Upfw4LzP-1611410677089)(https://uploader.shimo.im/f/K0fOMW3OPkGKZdj4.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

访问者模式包含以下主要角色。

  1. 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
  2. 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
  3. 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
  4. 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
  5. 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

五、具体使用

https://shimo.im/docs/WHXQxW9kx6XKVxKv/read

十八、✔️迭代器

一、定义

  • 定义:提供统一的方法遍历容器对象的各个元素,不需要知道容器对象底层表示,即:不暴露容器对象的内部结构。在客户端和容器对象之间添加了第三者:迭代器。
  • 理解:如果我们的容器对象是用不同的方式实现的,有数组,还有java的集合类, 或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。 统一封装遍历的方式,只需要调用统一的方法遍历就可以了。

二、使用场景

遍历一个容器对象时。比如:List、Map、数据库Cursor

三、优缺点和注意事项

优点

  • 提供一个统一的方法遍历对象,客户端不用考虑聚合的类型,使用一种方法就可以遍历对象了。
  • 隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚合的具体组成。
  • 提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任 原则)。在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集 合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变 的话,只影响到了迭代器。
  • 当要展示一组相似对象,或者遍历一组相同对象时使用, 适合使用迭代器模式

缺点

  • 每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类

四、UML图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LfNW9dQx-1611410677090)(https://uploader.shimo.im/f/mScXktVvfjfsMVDl.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

迭代器模式主要包含以下角色。

  1. 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
  2. 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
  3. 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
  4. 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

五、具体使用

https://shimo.im/docs/hKpPVGv9YDvJdrh9/read

十九、✔️中介者

总结:

在编程的时候,一个类必然会与其他类产生依赖关系。当这种依赖关系如网状错综复杂时,我们可以采用中介者模式。用一个中介对象类封装一系列对象的交互。从而使各个对象接触耦合。

一、定义

中介者模式(Mediator Pattern),用一个中介对象来封装一系列的对象交互。 中介者使各个对象不需要显式地相互引用,从而使其耦合松散。当某些对象之间的作用发生改变时,不会立即影响其他对象之间的作用,保证这些作用可以彼此独立变化。它是迪米特法则的典型应用。

二、使用场景

  • 在面向对象的编程语言里,一个类必然与其他类产生依赖关系。当这种依赖关系如网状错综复杂时,可以采用中介者模式。
  • 当对象之间的交互操作很多,且每个对象的行为操作相互依赖彼此时,为防止在修改一个对象行为时,同时影响到其他对象的行为。可以采用中介者模式,来解决耦合问题。该模式将对象之间多对多的关系变成一对多的关系。中介者对象将系统从网状结构变成以中介者为中心的星形结构。降低系统的复杂性,提高可扩展性。
  • 比如MVC模式,C(Controller控制器)是M(Model模型)和V(View视图)的中介者,在前后端交互时起到了中间人的作用。

三、优缺点和注意事项

优点:

  • 多个类相互耦合,会形成网状结构, 使用中介者模式将网状结构分离为星型结构,进行解耦
  • 减少类间依赖,降低了耦合,符合迪米特原则

缺点:

  • 中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响
  • 如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,要特别注意

四、UML图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4FpMnmk7-1611410677091)(https://uploader.shimo.im/f/IkSbNOAx4PPEAgbO.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

中介者模式包含以下主要角色。

  1. 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
  2. 具体中介者(Concrete Mediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
  3. 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
  4. 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

五、具体使用

https://shimo.im/docs/QVtTxTPJYWV8xCVk/read

二十、✔️备忘录

总结:备忘录模式用来记录一个对象的某种状态或某些数据,在某些时刻可以恢复某些状态的数据。

一、定义

备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作

二、使用场景

如 Word、记事本、Photoshop、Eclipse 等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 IE 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。

三、优缺点和注意事项

备忘录模式是一种对象行为型模式,其主要优点如下。

  • 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
  • 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
  • 发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。

其主要缺点是:

  • 资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

四、UML图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A7eRLSvr-1611410677092)(https://uploader.shimo.im/f/8C0JYhzl64a2f2qi.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

备忘录模式的主要角色如下。

  1. 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
  2. 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
  3. 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

五、具体使用

https://shimo.im/docs/Yv8D3twRKPyhjqKG/read

二十一、✔️解释器

总结:定义一个语言或语法规则,然后使用代码来解释这个规则。比如xx城市的xx人坐公交车。如果是上海的就可以提供免费服务,如果是儿童就可以提供免费服务。基于这个规则,我们就可以使用解释器通过代码来实现这个规则。

一、定义

给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。

二、使用场景

应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树

一些重复出现的问题可以用一种简单的语言来表达

一个简单语法需要解释的场景

比如编译器、运算表达式计算、正则表达式、机器人等

三、优缺点和注意事项

优点:

  1. 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
  2. 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。

缺点:

  1. 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
  2. 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
  3. 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。

四、UML图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wIOYUx8g-1611410677093)(https://uploader.shimo.im/f/XknFaGu1tCvWgPwm.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

解释器模式包含以下主要角色。

  1. 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
  2. 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
  3. 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
  4. 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
  5. 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。

五、具体使用

https://shimo.im/docs/9KyX93cJwV8cwGjv/read

二十二、✔️状态模式

总结:当判断一个对象状态的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用各个不同的状态类表示、系统处于哪种情况,直接使用相应的状态类对象进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-else、switch-case 等冗余语句,代码更有层次性,并且具备良好的扩展力。

一、定义

它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的。对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

二、使用场景

1)一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变其行为。

2)代码中包含大量与对象状态判断有关的语句。例如有大量的if-else,switch-case。

比如:用户登录和未登录状态、订单待付款、已付款等状态、

3)当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状 态要求有不同的行为的时候,可以考虑使用状态模式

三、优缺点和注意事项

优点:

  1. 结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
  2. 将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
  3. 状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。

缺点:

  1. 状态模式的使用必然会增加系统的类与对象的个数。
  2. 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
  3. 状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。

四、UML图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Klf5l8zx-1611410677094)(https://uploader.shimo.im/f/uhqbVAib9l5vMthg.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

状态模式包含以下主要角色。

  1. 环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
  2. 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
  3. 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。

五、具体使用

https://shimo.im/docs/YGYqqk6HhjWKgWdJ/read

二十三、✔️策略模式

总结:实现某个功能时,我们可以根据不同的条件设置不同的策略或方法,此时可以使用策略模式。比如排序:有冒泡排序、选择排序、插入排序、二叉树排序等;出门选择交通工具:可以选择飞机、骑车、火车等。

一、定义

策略(Strategy)模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户端。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

二、使用场景

当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。

比如:数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等;

出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等;

超市促销可以釆用打折、送商品、送积分等方法。

三、优缺点和注意事项

  • 策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法。
  • 策略模式的关键是:分析项目中变化部分与不变部分
  • 策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的继承。更有弹性
  • 体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只 要添加一种策略(或者行为)即可,避免了使用多重转移语句(if…else if…else)
  • 提供了可以替换继承关系的办法: 策略模式将算法封装在独立的Strategy类中使得可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。

优点如下。

  1. 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if…else 语句、switch…case 语句。
  2. 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
  3. 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
  4. 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
  5. 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。

缺点如下。

  1. 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
  2. 策略模式造成很多的策略类,增加维护难度。每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大

四、UML图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pzmMZ9IF-1611410677094)(https://uploader.shimo.im/f/qhQL2sLOwTt19DBk.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

策略模式的主要角色如下。

  1. 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
  2. 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
  3. 环境(Context)类:持有一个策略类的引用,最终给客户端调用。

五、具体使用

https://shimo.im/docs/G8VhwQ89C393VP8w/read

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MohuUJGM-1611410677095)(https://uploader.shimo.im/f/zTce081KrZCf1LQG.png!thumbnail?fileGuid=dv9PCcKpryTDJ3gJ)]

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值