设计模式
这是比较简单的介绍,从Android源码设计模式这本书里面抄的笔记加上自己的一点理解,如果想看详细一点的,可以直接点下面的链接:
ListView适配器模式的应用
ListView观察者模式的应用
AsyncTask模板模式的应用
ListView桥接模式的应用
Iterator – 迭代器模式(游标模式)
定义:行为型设计模式之一,源于对内容的访问,比例Java中的List、Map、数组等,旨在提供一种方法顺序访问一个容器对象中的各个元素,而又不需要暴露该对象的内部表示。
使用场景:遍历一个容器对象时
UML类图:
关系说明:
- Iterator:迭代器接口,负责定义、访问和遍历元素的接口
- ConcreteIterator:具体迭代器类,实现迭代器接口,并记录遍历的当前位置
- Aggregate:容器接口,负责提供创建具体迭代器角色的接口(重点在iterator方法,将两个接口关联 )
- ConcreteAggregate:具体容器类,具体迭代器角色与该容器相关联
优点:支持以不同的方式去遍历一个容器对象,也可以有多个遍历,弱化了容器类与遍历算法之间的关系
缺点:对类文件的增加
源码:List、Map等
思想: 数据的增删改查,和遍历属于不同的职能,如果都写在相同的类中违背了单一职责原则,所以这里利用迭代器模式来实现遍历功能,然后原有的类关联相应的迭代器,起到解耦的功能,又不用开发者自己去实现
Decorator – 装饰者模式(包装模式)
定义:结构型设计模式之一,使用一种对客户端透明的方式来动态扩展对象的功能,它是继承关系的一种替代方案。简单来说就是动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
使用场景:需要动态且透明地扩展类的功能时
UML类图:
关系说明:
- Component:抽象组件,可以是一个接口或者抽象类,充当被装饰的原始对象
- ConcreteComponent:组件具体实现类,装饰的具体对象
- Decorator:抽象装饰者,职责为装饰我们的组件对象,其内部维护一个指向组件对象的引用。在大多数情况下为抽象类,根据不同需求实现不同子类,如果装饰逻辑单一,只有一个的情况下也可省略直接使用具体装饰者
- ConcreteDecoratorA:装饰者具体实现类A,对抽象装饰者作出具体的实现
- ConcreteDecoratorB:装饰者具体实现类B,对抽象装饰者作出具体的实现
源码: Context(抽象组件)、ContextImpl(具体组件)、ContextWrapper(抽象装饰者)、Activity(具体装饰者)等
思想:一种对原有功能的扩展,可以替代继承,更灵活
注意:和代理模式的区别:
- 装饰者模式是以对客户端透明的方式扩展对象的功能,是继承关系的一种替代方案;而代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有的引用
- 装饰者模式应该为所装饰的对象增强功能;代理模式对所代理的对象施加控制,但不对对象本身的功能进行增加
Flyweight – 享元模式
定义:享元模式是对象池的一种实现,用来尽可能减少内存使用量,它适合用于可能存在大量重复对象的场景,来缓存可共享的对象,达到对象共享,避免创建过多对象的效果,从而提升性能、避免内存的移除。享元模式中的享元对象包含两种状态:内部状态(可以共享的状态,不会随着环境变化),外部状态(不可共享,会变化),一般在享元模式中会建立一个对象容器,经典实现为Map,以内部状态为key,享元对象为value。
使用场景:存在大量重复对象的场景、需要缓存池的场景
UML类图:
关系说明:
- Flyweight:享元对象抽象基类或者接口
- ConcreteFlyweight:具体的享元对象
- FlyweightFactory:享元工厂,负责管理享元对象池和创建享元对象
优点:可以大大减少应用程序创建的对象,降低程序内存的占用,增强程序的性能
缺点:
- 使系统更加复杂。为了使对象可以共享,需要将一些状态外部化。
- 读取外部化状态使得运行时间稍微边长。
源码:String(使用字面值字符串来创建字符串)、Handler消息机制中Message(同时充当三个角色)对象的获取
思想:一种缓存思想,在面临大量重复相似的对象创建时可以使用,提升性能
Facade – 外观模式(门面模式)
定义:要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行
使用场景:
- 为一个复杂子系统提供一个简单接口
- 当需要构建一个层次结构的子系统时,使用Facade模式定义子系统中每层的入口点
UML类图:
关系说明:
- Facade:系统对外的统一接口,系统内部系统地工作中
- SystemA、SystemB:子系统接口
优点:
- 对客户程序隐藏子系统细节,因而减少了客户对于子系统的耦合,能够拥抱变化
- 外观类对子系统的接口封装,使得系统更易于使用
缺点:
- 外观类接口膨胀。API接口较多,在一定程度上增加了用户使用成本
- 没有遵循开闭原则,当业务出现变更的时候,可能需要直接修改外观类
源码:ContextImpl(外观类)
思想:对多层系统进行封装,提供统一访问入口,降低使用成本
Template – 模板方法模式
定义:当遇到一个问题,我们知道解决该问题所需的关键步骤,并确定了这些步骤的执行顺序,但是,某些步骤的具体实现是未知的,这类问题的解决方案就是–模板方法模式。简单来说就是:流程封装
使用场景:
- 多个子类有公有的方法,并且逻辑基本相同
- 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现
- 重构时经常使用,把相同的代码抽取到父类中,然后通过钩子函数约束其行为
UML类图:
关系说明:
- AbsTemplate:抽象类,定义了一套具体的(算法)框架。执行步骤为final(execute方法),但是每一个步骤的具体实现由子类根据需求可以重写
- ConcreteImplA:具体实现类A
- ConcreteImplB:具体实现类B
优点:
- 封装不变部分,扩展可变部分
- 提取公共部分代码,便于维护
缺点:
提升代码阅读的难度,不利于理解
源码:AsyncTask、Activity的生命周期
思想:针对一个问题,它的解决步骤是固定的,但是每一个步骤由于不同子类实现起来不同,那么我们就可以使用模板方法,定义抽象类,每一个步骤子类都可以重写,但是执行顺序方法为final
Mediator – 中介者模式
定义:在实际需求中,可能存在这种场景,多个对象相互依赖,例如:A执行actionA要调用B的actionB,B执行要调用C的ActionC,这样多个对象是互相依赖,形成一个复杂的网状图,高耦合。这个时候就可以使用中介者模式,中介者模式中中介者都会知道所有的同事(持有所有同事的引用),但是这些同事可以互不相识(互相依赖,但是不在相互之间持有引用),这样A在执行actionA的时候,所有操作都有中介者来协调,它不需要持有B的引用。中介者模式的出现将一个错综复杂的网状图编程了一个结构清晰的星型图,其中心就是中介者
使用场景:
当对象之间的交互操作很多且每个对象的行为操作都依赖彼此时,为防止在修改一个对象的行为时,同时涉及修改很多其他对象的行为,可以采用中介者模式。
UML类图:
关系说明:
- Mediator:抽象中介者,定义了同事对象到中介者对象的接口,主要职能是协调众同事的协作,实现松耦合
- ConcreteMediator:具体中介者,继承于抽象中介者,它从具体同事对象接收消息(持有具体同事的引用–>可父类实现,也可自身实现),向具体同事对象发出命令(调用具体同事的行为函数)
- Colleague:抽象同事类,定义了中介者对象的接口(持有中介者引用),它只知道中介者而不知道其他的同事对象
- ConcreteColleagueA/B:具体同事类,继承于抽象同事类,每个具体同事类都知道本身在小范围内的行为,而不知道它在大范围内的目的
优点:在类结构复杂,互相依赖时,使用中介者模式可以解耦并使逻辑清晰
缺点:在类结构不复杂时,使用会增加复杂度
源码:KeyguardViewMediator锁屏功能
思想:将一个互相依赖的复杂网状结构转变为一个星状结构,以中介者为中心,协调众对象之间的行为,实现松耦合
Vistor – 访问者模式
定义:一种将数据操作与数据结构分离的设计模式,基本想法是,软件系统中拥有一个由许多对象构成的、比较稳定的对象结构,这些对象的类都拥有一个accept方法用来接受访问者对象的访问。访问者是一个接口,它拥有一个visit方法,这个方法对访问到的对象结构中不同类型的元素作出不同的处理。在对象结构的一次访问过程中,我们遍历整个对象结构,对每一个元素都实施accept方法,在每一个元素的accept方法中会调用访问者的visit方法,从而使访问者得以处理对象结构的每一个元素,我们可以针对对象结构设计不同的访问者类来完成不同操作,达到区别对待的效果。简而言之,就是封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新操作
使用场景:
- 对象结构稳定,但经常需要在此对象结构上定义新的操作
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类
UML类图:
关系说明:
- Visitor:接口或者抽象类,定义了对每一个元素(Element)访问的行为(UML中的visitElementA等),它的参数就是可以访问的元素,它的访问数理论上来讲与元素个数一样,因为访问者模式要求元素的类族要稳定
- ConcreteVisitor:具体的访问者,需要给出对每一个元素类访问时所产生的具体行为
- Element:元素接口或者抽象类,它定义了一个接受访问者(accept)的方法,其意义是指每一个元素都要可以被访问者访问
- ElementA、ElementB:具体元素类,它提供接受访问方法的具体实现,通常情况下是使用访问者提供的访问该元素类的方法,即在具体元素类中调用相应具体访问者的visitElementX方法
- ObjectStructure:对象结构,内部管理了元素集合,并且可以迭代这些元素供访问者访问
优点:
- 各角色职责分离,符合单一职责原则
- 扩展性好
- 灵活性好
- 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化
缺点:
- 具体元素对访问者公布细节,违反迪米特原则
- 具体元素变更时导致修改成本大
- 违反了依赖导致原则,为了达到“区别对待”而依赖了具体类,没有依赖抽象
源码:编译时Annotation
思想:对象结构比较稳定,我们可能需要不定时的添加对相应元素的操作,为了避免污染元素类,可是使用访问者模式,灵活性更强,可扩展性更好
Singleton – 单例模式
定义:许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。我们需要确保某一个类(单例类)只有一个实例,而且自行实例化并向整个系统提供实例。
使用场景:
创建一个对象需要消耗的资源过多时可使用,例如:访问IO、数据库等
UML类图:
关系说明:
- Client:访问者
- Singleton:单例类
优点:
- 减少内存开支
- 减少了系统的性能开销
- 可以避免对资源的多重占用
- 可以在系统设置全局的访问点,优化和共享资源访问
缺点:
- 没有接口,扩展性差
- 单例对象如果需要持有Context,很容易引发内存泄漏,所以最好传递ApplicationContext
源码:WMS、AMS等系统服务,LayoutInflater
思想:某些配置类全局唯一,便于全局协调;或者创建一个对象需要消耗很多资源时,可以考虑使用单例
Builder – 建造者模式
定义:一个复杂对象有很多组成部分,要将这些部分组装成一个对象可能需要经过一系列的漫长、复杂操作,而对于使用者来说,可能并不关心这个,此时我们就可以使用Builder模式,它可以将一个复杂对象的构建和表示分离。需要注意的是同样的构建过程可以创建不同的表示
使用场景:
- 相同的方法,不同的执行顺序,产生不同的时间结果
- 多个部件或零件都可以装配到一个对象中,但是产生的运行结果又不相同
- 产品类复杂,或者产品类中的调用顺序不同产生了不同的作用
- 初始化一个对象特别复杂,如参数多,且很多参数都具有默认值
UML类图:
关系说明:
- Product:产品的抽象类
- Builder:抽象Builder类,规范产品的组建,一般都是由子类实现具体的组建过程
- ConcreteBuilder:具体的Builder类,配置具体的产品。常常也完成构建,简化流程
- Director:统一组装的过程,经常省略,由Builder来完成组成,简化流程
优点:
- 良好的封装,使客户端不必知道产品内部组成的细节
- 构建者独立,容易扩展
缺点:会产生多余的Builder对象以及Director对象,消耗内存
源码:AlertDialog.Builder
思想:一个类由多部分组成,过程繁琐复杂,使用者不需要知道内部具体的封装流程,此时可以使用建造者模式,提高易用性,降低耦合
Prototype – 原型模式
定义:有一个样板实例,从这个样板对象中复制出一个内部属性一致的对象称为原型模式
使用场景:
- 类初始化需要消耗非常多的资源,这个资源包括数据、硬件资源等
- 通过new产生一个对象需要非常繁琐的数据准备或者访问权限
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值,可以考虑用原型模式拷贝多个对象供调用者使用,即保护性拷贝
UML类图:
关系说明:
- Client:客户端用户
- Prototype:抽象类或者接口,声明具备clone能力,常见Cloneable接口
- ConcretePrototype:具体的原型类,Prototype的实现类
补充:
- 浅拷贝:基础类型拷贝,引用类型不拷贝指向同一个(原字段相应的地址)
- 深拷贝:基础类型和引用类型都拷贝
优点:原型模式是在内存中二进制流的拷贝,要比直接new一个对象性能好,特别是要产生大量对象时
缺点:构造函数不执行
源码:ArrayList(clone方法为拷贝)
思想:当构建一个复杂对象需要消耗大量资源的时候,可以使用原型模式提升创建效率,或者保护性拷贝
FactoryMethod – 工厂方法模式
定义:定义一个用于创建对象的接口,让子类决定实例化哪个类
使用场景:在任何需要生成复杂对象的地方,都可以使用工厂方法模式
UML类图:
关系说明:
- Product:抽象产品,是工厂方法模式所创建产品的父类
- ConcreteProduct:为实现抽象产品的某个具体产品的对象
- Factory:抽象工厂,工厂方法模式的核心
- ConcreteFactory:具体工厂,产出相应的具体产品对象
补充:在使用工厂方法模式生产产品时,如果不想为了生产多个产品定义多个具体工厂,可以使用泛型+反射来实现,这样只需要传入相应产品的class文件即可
优点:降低耦合,提升扩展性
缺点:会导致类结构复杂化
源码:List和Set实现Iterable接口
思想:当一个类有多个子类需要创建,为了避免众多的if else等脏代码,可是使用工厂方法,提升扩展性(当有新的子类时,不需要修改原有的代码,只需要新增工厂和具体子类即可),使代码更简洁
AbstractFactory – 抽象工厂模式
定义:为创建一组相关或者是相互依赖的对象提供一个接口,而不需要指定它们的具体类
使用场景:一个对象族(一组相关或者相互依赖的对象)有相同的约束时可以使用抽象工厂模式
UML类图:
关系说明:
- AbstractFactory:抽象工厂,它声明了一组用于创建一组产品的方法(例如A、B),每一个方法对应一种产品
- ConcreteFactory:具体工厂角色,实现了抽象工厂中定义的创建产品的方法,生成一组具体产品
- AbstractProduct:抽象产品角色,它为每种产品声明接口
- ConcreteProduct:具体产品角色,定义具体工厂生产的具体产品角色,实现抽象产品接口中声明的业务方法
补充:简单总结抽象工厂就是有一组相互关联的对象族(假设电脑由主板、显卡、操作系统、CPU、显示屏组成),有多个工厂(联想、戴尔、惠普等)能够生产电脑,针对上面的抽象产品(对象族中的每一个对象),不同工厂产出或者使用的具体产品不同,假设联想使用自产的联想主板,戴尔使用自产的戴尔主板,这样就构成了一个抽象工厂模式
优点:分离接口与实现
缺点:
- 类文件巨增
- 不太容易扩展新产品,每次扩展新产品都需要去修改抽象工厂
源码:MediaPlayerFactory
思想:针对一组相关的类,有多个工厂能生产,并且每一个工厂生产出来的类对象不同,此时就可以使用抽象工厂
Stragety – 策略模式
定义:实现某一个功能有多种算法或者策略,我们根据实际情况选择不同的算法或者策略来完成该功能,在这个过程中,我们会将每一个算法都封装起来,而且使它们可以互相替换
使用场景:
- 针对同一类型问题的多种处理方式,仅仅是具体行为有差别
- 需要安全地封装多种同一类型的操作
- 出现同一个抽象类有多个子类,而有需要使用if-else或者switch-case来选择具体子类时
UML类图:
关系说明:
- Context:用来操作策略的上下文环境,可以设置使用哪个策略
- Stragety:策略的抽象
- ConcreteStragetyA/ConcreteStragetyB:具体的策略实现
优点:
- 结构清晰明了,使用简单直观
- 耦合度降低,扩展方便
- 数据更安全
缺点:子类增加
源码:属性动画中的时间插值器
思想:针对一个问题有多种算法可以解决时可以使用策略模式
State – 状态模式
定义:当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
使用场景:
- 一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变其行为
- 代码中包含大量与对象状态有关的条件语句
UML类图:
关系说明:
- Context:环境类,维护一个State子类的实例,这个实例定义了对象的当前状态
- State:抽象状态类或者状态接口,定义了一个或者一组接口,表示该状态下的行为
- ConcreteStateA、ConcreteStateB:具体状态类,每一个具体状态类实现抽象State中定义的接口,从而达到不同状态下的不同行为
优点:更好的组织与特定状态相关的代码,将繁琐的状态判断转换成结构清晰的状态关联,避免代码膨胀,提升可扩展性与可维护性
缺点:增加系统类和对象个数
源码:WIFI管理
思想:当对象存在多种状态,并且其行为和状态相关联时,为了避免繁琐的if-else/switch-case导致的混乱和膨胀,可是使用状态模式
Iterator2 – 责任链模式
定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
使用场景:
- 多个对象可以处理同一请求,但具体由哪个对象处理则在运行时动态决定
- 在请求处理者不明确的情况下,向多个对象提交一个请求
- 需要动态指定一组对象处理请求
UML类图:
关系说明:
AbstractHandler:抽象处理者,主要做了两件事
(1)定义了子类需要实现的获取处理等级和具体的处理逻辑;
(2)定义了final类型的责任转发逻辑(handleRequest),在这里会将处理等级和请求等级进行匹配,对应则调用相应的handle方法,不对应则调用下一节点的handleRequest。抽象类中是定义有下一节点的引用的
ConcreteHanlder:具体处理者,定义了处理等级和相应的处理方案
AbstractRequest:抽象请求类,定义了获取请求内容和请求等级的方法
ConcreteRequest:具体请求类,主要是实现了请求等级,需要和处理等级相对应
优点:解耦,发起者并不需要知道最终处理者,从而起到解耦的作用
缺点:对链中请求处理者的遍历,处理者太多必定会影响性能
源码:Android事件分发 ,Okhttp中的网络请求拦截器
思想:针对一个事件,可能有多个处理对象能够处理,但是并不确定,此时就可以使用责任链模式
Interpreter – 解释器模式
定义:给定一个语言,定义它的文法的一种表示,并定义了一个解释器,该解释器使用该表示来解释语言中的句子。
说明一下一些比较抽象的概念:形式语言、形式语言的字符表、形式文法
- 形式语言的字符表:假设一个形式语言由123456六个数字组成,那么这六个数字称为一种形式语言的字符表(基本组成单元,和字母表的26个字母含义相同)
- 形式语言:由形式语言的字符表组成的集合,例如123、425、651我们称之为形式语言(就是将一些字符表中的元素组合起来作为一个集合整体)
- 形式文法:在上面定义的形式语言中,假设我们定义字符表为0-9和+、-,语法为* +/- *,*表示0-9中的任意数字,1+2、9-3等都满足该语法,其中0-9为终结符号(无法再推导),+/-为非终结符号(因为一旦出现它的右边肯定还有字符表中的元素,可以再推导出新的值)
使用场景:如果某个简单的语言需要解释执行而且可以将该语言中的语句表示为一个抽象语法树时(假设1+2+3,就可以表示为一个语法树,1+2->3 ,3+3->6),可以考虑使用解释器模式
UML类图:
关系说明:
- AbstractExpression:抽象表达式。一个抽象的解释操作父类,定义了一个抽象的解释方法,其具体的实现在子类解释器中完成
- TerminalExpression:终结符表达式。实现文法中与终结符有关的解释操作,文法中每一个终结符都有一个具体的终结符表达式与之对应
- NonterminalExpression:非终结符表达式。实现文法中与与终结符有关的解释操作
- Context:上下文环境类,包含解释器之外的全局信息
- Client:客户类,解析表达式,构建抽象语法树,执行具体的解释操作等
优点:灵活的扩展性
缺点:
- 生成大量的类,后期维护成本高
- 对于过于复杂的文法,构建抽象语法树会显得异常繁琐。因为复杂文法不推荐使用
源码:PackageParser解析AndroidManifest中每一个组件标签
思想:翻译简单语法的文法
Command – 命令模式
定义:一个请求可能涉及多个命令程序,例如我们点击系统的关机选项,它会先暂停处理事件,保存系统的一些配置,然后结束程序进程,最后调用内核命令关闭计算机,在应用中如果我们这么一步步自己去操作无疑体验是很不好的,所以我们一般都是将一系列的方法封装,用户只需调用一个方法执行即可,所有这些被封装的方法在内部会被挨个执行,这就是命令模式的应用。
官方定义:将一个请求封装成一个对象,从而让用户使用不同的请求把客户端参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
命令模式是回调机制的一个面向对象的替代品
使用场景:
- 需要支持取消操作
- 需要支持事务操作
- 支持修改日志的功能
可以在接收者中定义一个数据结构来保存命令,在需要的时候可以通过这个数据结构来取消、修改等
UML类图:
关系说明:
- Receiver:接收者对象,真正执行逻辑的地方,其它类都是直接或者间接调用它的方法
- Command:定义了所有具体命令的抽象接口。
- ConcreteCommand:具体命令角色。会持有接收者的应用,在调用者中调用其execute时,会调用接收者的action执行函数
- Invoker:调用者,职责为调用命令对象执行具体的请求(execute)
- Client:客户端角色,创建接收者、命令、调用者,并通过调用者来执行相应命令
优点:弱耦合,更好的灵活性和扩展性
缺点:类膨胀
源码:
思想:执行一个操作有多个步骤,利用面向对象的思想,将相应的步骤打包为一个命令供客户端使用,方便实用。支持修改、撤销、重做等
Observer – 观察者模式
定义:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新
使用场景:
- 事件多级触发
- 跨系统的消息交换,例如消息队列、事件总线等
- 关联行为场景,注意不是组合(组合的含义时两者生命周期绑定不可分割)
UML类图:
关系说明:
- Subject:抽象主题,就是被观察者,定义一个保存所有观察者引用的集合,每个主题都可以有任意数量的观察者。同时提供增加和删除观察者对象的方法
- ConcreteSubject:具体主题或者说具体被观察者,在内部状态发生改变时,给所有注册过的观察者发出通知
- Observer:抽象观察者,定义了一个更新的接口update,使得在得到主题更新通知的时候更新自己
- ConcreteObserver:具体观察者,实现更新接口,在订阅的主题状态发生变化时更新自己
优点:解耦,观察者和被观察者完全隔离
缺点:类膨胀
源码:ListView的BaseAdapter
思想:基于一种订阅思想,观察者对被观察者高度敏感
Memoto – 备忘录模式
定义:在不破坏封闭的前提下,保存对象的当前状态,并且在之后可以再次恢复到此状态
使用场景:
- 需要保存一个对象在某一个时刻的状态或部分状态
- 一个对象不希望外界直接访问其内部状态,通过中间对象可以间接访问其内部状态
UML类图:
关系说明:
- Originator:负责创建一个备忘录,可以记录、恢复自身的内部状态。同时还可以根据需要决定Memento存储自身的哪些内部状态
- Memento:备忘录角色,用于存储Originator的内部状态,并且可以防止Originator以外的对象访问Memento
- Caretaker:负责存储备忘录,不能对备忘录的内容进行操作和访问,只能够将备忘录传递给其他对象
优点:
- 给用户提供一种可以恢复状态的机制,可以使用户能够比较比较方便地回到某个历史的状态
- 实现了信息的封装,使得用户不需要关心状态的保存细节
缺点:消耗资源,如果类的成员变量过多,会占用比较大的资源,而且每一次保存都会消耗一定内存
源码:Activity的onSaveInstanceState和onRestoreInstanceState
思想:当对象的某个对象不希望被外界修改时,可以使用备忘录模式做一个备份
Proxy – 代理模式(委托模式)
定义:为其他对象提供一种代理以控制对这个对象的访问
使用场景:当无法或者不想直接访问某个对象时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性(为了更好理解),委托对象与代理对象需要实现相同的接口
UML类图:
关系说明:
- Subject:抽象主题,主要职责是声明真实主题与代理的共同接口方法,可以是接口也可以是抽象类
- RealSubject:被委托类/被代理类,定义了代理所表示的真实对象,执行具体的业务逻辑。客户类调用时,实际上是通过代理类间接调用真实主题中定义的方法
- ProxySubject:委托类/代理类,持有真实主题类的引用,在其所实现的接口方法中调用真实主题相应的接口方法。代理类可以代理多个被代理类
- Client:客户类,使用代理的类型
补充:代理分为动态代理和静态代理,静态代理代理者的代码由程序员自己或通过一些自动化工具生成固定的代码再对其编译,也就是说在我们的代码运行前代理类的class编译文件就已经存在;动态代理 通过反射机制动态地生成代理者的对象,代理谁将会在执行阶段决定。
优点:解耦,灵活性好
缺点:类膨胀
源码:ActivityManagerProxy代理ActivityManagerNative的子类ActivityManagerService
思想:控制访问,或者访问困难时可以添加间接访问类
Composite – 组合模式(部分整体模式)
定义:将一组相似的对象组合成树形结构以表示“部分-整体”的层次结构,然后提供一个统一的方法去访问相应的对象,使得用户对单个对象和组合对象的使用具有一致性。在组合模式中,拥有分支的称之为枝干构件,位于树状结构顶部的枝干结构比较特殊,称之为根结构件,对于没有分支的称之为叶子构件
使用场景:
- 表示对象的部分-整体层次结构时
- 从一个整体中能够独立出部分模块或功能的场景
UML类图:
关系说明:
- Component:抽象根节点,为组合中的对象声明接口。在该接口中有没有定义用于访问和管理Component的子节点的接口,是区分安全和透明组合模式的关键
- Composite:定义枝干节点的行为,存储子节点,并实现与子节点有关的操作(不管是安全还是透明组合模式,枝干节点都会实现)
- Leaf:叶子节点
- Client:客户端,通过Component接口操纵组合节点的对象
补充:安全的组合模式之所以安全是因为抽象出来的Component中不包含子节点的操作接口,这样叶子结点继承时就不用判断直接继承即可;如果是透明的组合模式因为Component定义有操作子节点的接口,叶子结点虽然没有子节点但是也要实现,所以在使用时要做判断
优点:
- 为树形结构的面向对象提供了一套灵活的解决方案
- 增加新的枝干和叶子构件很方便,符合开闭原则
缺点:新增构件时不好对枝干中的构件类型进行限制,因为大多数情况下他们来自相同的抽象层,此时必须通过类型检查来实现
源码:View和ViewGroup的嵌套
思想:一组相似对象,层级复杂时可以使用组合模式,构建树形结构,访问方便
Adapter – 适配器模式
定义:适配器是将两个不兼容的类融合在一起,从而使原本因接口不匹配而无法在一起工作的两个类在一起工作
使用场景:
- 系统需要使用现有的类,而此类的接口不符合系统的需要,即接口不兼容
- 想要建立一个可以重复使用的类,用于一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作
- 需要一个统一的输出接口,而输入类型不可预知
UML类图:
关系说明:
- Target:目标角色,也就是我们所期待得到的接口
- Adaptee:现在需要适配的接口
- Adapter:适配器角色(具体类),把源接口转换成目标接口
补充:适配器模式分为类适配器和对象适配器
类适配器:继承方式实现,即Adapter直接继承Adaptee,此时直接将类转换。不过由于使用继承,所以在使用Adapter时可能会出现一些调用者(Client)不关心、不需要的API
对象适配器:对象适配器与类适配器不同的是,对象适配器模式不是使用继承关系连接到Adapter类,而是使用代理关系连接到Adaptee类。这样就可以将源接口转换为目标接口,并且不会暴露源接口的实现细节和一些不关心的API,降低使用成本
优点:
- 更好的复用性
- 更好的扩展性
缺点:过多使用会让系统非常凌乱,不易整体把握
源码:ListView、RecyclerView、GridView等的Adapter
思想:现有接口和目标接口不一致,添加中间类来适配
Bridge – 桥接模式(桥梁模式)
定义:将抽象部分与实现部分分离,使它们都可以独立地进行变化。主要职责就是连接“抽象部分”与“实现部分”
使用场景:
- 如果一个系统需要在构建的抽象化角色(分类)和具体化角色(子类)之间添加更多的灵活性,避免通过继承这种静态关联,则可以使用桥接模式使他们在抽象层建立一个关联关系
- 不希望使用继承或者多层导致类的个数急剧增加的系统,可以考虑使用
- 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展
UML类图:
关系说明:
- Abstraction:抽象部分,该类保持一个对实现部分对象的引用,抽象部分中的方法需要调用实现部分的对象来实现。一般为抽象类
- RefineAbstraction:优化的抽象部分
- Implementor:实现部分,可以为接口或抽象类,其方法不一定要与抽象部分中的一致,一般情况下是由实现部分提供基础的操作,而抽象部分定义的则是基于实现部分这些基本操作的业务方法
- ConcreteImplementorA/ConcreteImplementorB:实现部分的具体实现。完善实现部分中方法定义的具体逻辑
- Client:客户端,调用者
优点:分离抽象与实现,灵活的扩展以及对客户端的透明
缺点:不容易设计,不好把握抽象和实现的度
源码:Adapter和AdapterView
思想:当一个类存在多个独立变化维度的时候可以使用桥接模式