零:参考
- http://www.runoob.com/design-pattern/design-pattern-tutorial.html
- https://coding.imooc.com/learn/list/101.html
一、设计模式简介
(一)定义
- 设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
- 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。
- 使用设计模式是为了重用代码、增加代码可读性、保证代码可靠性。
(二)起源
- 在 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 的书,该书首次提到了软件开发中设计模式的概念。
- 四位作者合称 GOF(四人帮,全拼 Gang of Four)。
- 他们所提出的设计模式主要是基于以下的面向对象设计原则:
- 对接口编程而不是对实现编程。
- 优先使用对象组合而不是继承。
(三)使用
- 开发人员的共同平台:设计模式提供了一个标准的术语系统,且具体到特定的情景。
- 最佳的实践:设计模式已经经历了很长一段时间的发展,它们提供了软件开发过程中面临的一般问题的最佳解决方案。学习这些模式有助于经验不足的开发人员通过一种简单快捷的方式来学习软件设计。
(四)分类
- 根据设计模式的参考书 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 中所提到的,总共有 23 种设计模式。
- 这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。
1. 创建型模式(共5种)
- 工厂模式(Factory Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 单例模式(Singleton Pattern)
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
2. 结构型模式(共8种)
- 适配器模式(Adapter Pattern)
- 桥接模式(Bridge Pattern)
- 过滤器模式(Filter、Criteria Pattern)
- 组合模式(Composite Pattern)
- 装饰器模式(Decorator Pattern)
- 外观模式(Facade Pattern)
- 享元模式(Flyweight Pattern)
- 代理模式(Proxy Pattern)
3. 行为型模式(共12种)
- 责任链模式(Chain of Responsibility Pattern)
- 命令模式(Command Pattern)
- 解释器模式(Interpreter Pattern)
- 迭代器模式(Iterator Pattern)
- 中介者模式(Mediator Pattern)
- 备忘录模式(Memento Pattern)
- 观察者模式(Observer Pattern)
- 状态模式(State Pattern)
- 空对象模式(Null Object Pattern)
- 策略模式(Strategy Pattern)
- 模板模式(Template Pattern)
- 访问者模式(Visitor Pattern)
(五)关系
(六)六大原则
0. 总原则:开闭原则(Open Close Principle)
- 开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类。
1. 单一职责原则(SRP:Single Responsibility Principle)
- 不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。
2. 里式替换原则(LSP:Liskov Substitution Principle)
- 任何基类可以出现的地方,子类一定可以出现。
- LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
- 里式替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。
3. 依赖倒转原则(DIP:Dependence Inversion Principle)
- 依赖倒转原则是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。
4. 接口隔离原则(ISP:Interface Segregation Principle)
- 使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
5. 迪米特原则(最少知道原则)(DP:Demeter Principle)
- 一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
6. 合成复用原则(CRP:Composite Reuse Principle)
- 尽量使用合成/聚合的方式,而不是使用继承。
(七)UML 类图基础
1. 类图工具
- Microsoft Visio
- processon 在线作图
2. 模型元素
- 属性或方法的可见性,UML类图中表示可见性的符号有三种:
- +:表示 public
- -:表示 private
- #:表示 protected(friendly也归入这类)
- 类使用包含类名、属性(field) 和方法(method) 且带有分割线的矩形来表示
- 类的属性的表示方式:可见性 名称 :类型 [ = 缺省值]
- 类的方法的表示方式:可见性 名称(参数列表) [ : 返回类型]
- 类与类之间关系的表示方式:
- 关联关系:又可进一步分为单向关联、双向关联和自关联。
- 单向关联:单向关联用一个带箭头的直线表示。
- 双向关联:双向关联用一个不带箭头的直线表示。
- 自关联:自关联用一个带有箭头且指向自身的直线表示。
- 单向关联:单向关联用一个带箭头的直线表示。
- 聚合关系:聚合关系用带空心菱形和箭头的直线表示,聚合关系强调是“整体”包含“部分”,但是“部分”可以脱离“整体”而单独存在。
- 组合关系:组合关系用一个带实心菱形和箭头的直线表示。组合关系中的“部分”脱离了“整体”便不复存在。
- 依赖关系:依赖关系用一条带有箭头的虚线表示。
- 继承关系:继承关系对应的是extend关键字,用带空心三角形的直线表示。
- 接口实现关系:接口实现关系对应的是implements关键字,接口实现关系用带空心三角形的虚线表示。
- 关联关系:又可进一步分为单向关联、双向关联和自关联。
二、设计模式详述
(一)简单工厂模式
- 意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类。工厂模式使其创建过程延迟到子类进行。
- 主要解决:接口选择的问题。
- 何时使用:明确地计划不同条件下创建不同实例时。
- 如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
- 关键代码:创建过程在其子类执行。
- 优点:
- 一个调用者想创建一个对象,只要知道其名称就可以了。
- 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
- 屏蔽产品的具体实现,调用者只关心产品的接口。
- 缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。
- 使用场景:
- 日志记录器,可能记录到本地硬盘、系统事件、远程服务器等。
- 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
- 设计一个连接服务器的框架,需要三个协议,“POP3”、“IMAP”、“HTTP”,可以把这三个作为产品类,共同实现一个接口。
- 注意事项: 复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
(二)抽象工厂模式
- 意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
- 主要解决:接口选择的问题。
- 何时使用:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
- 如何解决:在一个产品族里面,定义多个产品。
- 关键代码:在一个工厂里聚合多个同类产品。
- 应用实例:工作后,为了参加一些聚会,肯定有两套或多套衣服吧,比如说有商务装(成套,一系列具体产品)、时尚装(成套,一系列具体产品),甚至对于一个家庭来说,可能有商务女装、商务男装、时尚女装、时尚男装,这些也都是成套的,即一系列具体产品。假设一种情况(现实中是不存在的,要不然,没法进入共产主义了,但有利于说明抽象工厂模式),在您的家中,某一个衣柜(具体工厂)只能存放某一种这样的衣服(成套,一系列具体产品),每次拿这种成套的衣服时也自然要从这个衣柜中取出了。用 OO 的思想去理解,所有的衣柜(具体工厂)都是衣柜类的(抽象工厂)某一个,而每一件成套的衣服又包括具体的上衣(某一具体产品),裤子(某一具体产品),这些具体的上衣其实也都是上衣(抽象产品),具体的裤子也都是裤子(另一个抽象产品)。
- 优点:当一个产品族中的多个对象呗设计成一起工作时,它能保证客户端始终只适用同一个产品族。
- 缺点:扩展非常困难,要增加一系列的某一产品,既要在抽象的Creator里加代码,又要在具体的Creator里面加代码。
- 适用场景:
- QQ 换皮肤,一整套一起换。
- 生成不同操作系统的程序。
- 注意事项:产品族难扩展,产品等级易扩展。
(三)单例模式
- 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- 主要解决:一个全局使用的类频繁地创建与销毁。
- 何时使用:当需要控制实例数目,节省系统资源的时候。
- 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
- 关键代码:构造函数是私有的。
- 应用实例:
- 一个班级只有一个班主任。
- Windows是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
- 优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 避免对资源的多重占用(比如写文件操作)。
- 缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎样来实例化。
- 适用场景:
- 要求生产唯一序列号。
- WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
- 注意事项:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
- getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
1. 实现方式(共6种)
- 饿汉式
- 是否 Lazy 初始化:否
- 是否多线程安全:是
- 实现难度:易
- 描述:这种方式比较常用,但容易产生垃圾对象。
- 优点:没有加锁,执行效率会提高。
- 缺点:类加载时就初始化(无法对instance实例做延时加载),浪费内存。
- 详解:它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
- 实例
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
- 懒汉式
- 是否 Lazy 初始化:是
- 是否多线程安全:否
- 实现难度:易
- 描述:这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
- 优点:饿汉式的改进,对instance实例做延时加载。
- 缺点:没有实现多线程安全,在多线程不能正常工作。
- 详解:getInstance方法不是同步的,多线程环境下会重复进行入次方法,就不能算是单例。
- 实例
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
//第一次调用的时候会被初始化
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 线程安全懒汉式
- 是否 Lazy 初始化:是
- 是否多线程安全:是
- 实现难度:易
- 描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
- 优点:第一次调用才初始化,避免内存浪费。
- 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
- 详解:getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
- 实例
public class Singleton {
private static Singleton instance;
private Singleton (){}
//实现方法一:方法中声明synchronized关键字
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
//实现方法二:同步代码块实现
public static Singleton getInstance() {
synchronized(Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
}
- 双检锁/双重校验锁(DCL,即 double-checked locking)
- JDK 版本:JDK1.5 起
- 是否 Lazy 初始化:是
- 是否多线程安全:是
- 实现难度:较复杂
- 描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
- 优点:饿汉式,线程安全的改进,提高了性能。
- 缺点:JVM的即时编译器中存在指令重排序优化,需要volatile关键字来禁止。
- 详解:getInstance() 的性能对应用程序很关键。
- 实例
public class Singleton {
//volatile关键字可以禁止JVM指令重排序优化
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
//第一次检查;避免不必要的同步
if (singleton == null) {
synchronized (Singleton.class) {
//第二次检查:在第一次调用时初始化
if (singleton == null) {
//此操作不是原子操作,如果没有volatile,下面三步有可能不会顺序进行
//第一步:给singleton分配内存;
//第二步:调用Singleton构造方法初始化变量;
//第三步:将singleton指向内存分配的内存空间;
singleton = new Singleton();
}
}
}
return singleton;
}
}
- 静态内部类/登记式
- 是否 Lazy 初始化:是
- 是否多线程安全:是
- 实现难度:一般
- 描述:对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
- 优点:JVM本身的机制(static/final)保证了线程安全,没有性能缺陷,使用能达到双检锁方式一样的功效,但实现更简单。
- 详解:这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟饿汉式不同的是:饿汉式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比饿汉式就显得很合理。
- 实例
public class Singleton {
private Singleton (){}
//第一次加载Singleton时不会初始化INSTANCE,只有调用getInstance时才会加载SingletonHolder,并初始化INSTANCE
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
//在内部类中创建对象实例
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
- 枚举
- 是否 Lazy 初始化:否
- 是否多线程安全:是
- 实现难度:易
- 描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
- 优点:这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。
- 缺点:由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
- 实例:
public enum Singleton {
//定义个枚举的元素,它就是Singleton的一个实例
INSTANCE;
public void whateverMethod() {
}
}
2. 总结
- 饿汉:无法对instance实例进行延迟加载。
- 懒汉:多线程并发情况下无法保证实例的唯一性。
- 懒汉线程安全:使用synchronized导致性能缺陷。
- DCL:JVM即时编译器的指令重排序。
- 静态内部类/枚举:延迟加载;线程安全;性能优势
3. Android实际应用
- Application
- Android提供了一个Application类,每当应用程序启动时,系统会自动将这个类进行初始化。
- 自定义Application
- 为得到一个Application对象提供便捷;
- 封装一些通用操作;
- 初始化一些全局的变量数据。
- 实例
public class MyApplication extends Applicatin{
private static MyApplication instance;
public static MyApplication getInstance(){
return instance;
}
@Override
public void onCreate(){
super.onCreate();
instance = this;
}
}
(四)建造者模式
- 意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
- 主要解决:在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
- 何时使用:一些基本部件不会变,而其组合经常变化的时候。
- 如何解决:将变与不变分离开。
- 关键代码:建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。
- 应用实例:
- 去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。
- JAVA 中的 StringBuilder。
- 优点:
- 建造者独立,松散耦合,易扩展。
- 便于控制细节风险。
- 缺点:
- 产品必须有共同点,范围有限制。
- 如内部变化复杂,会有很多的建造类,向好内存。
- 对象的构建过程暴露。
- 适用场景:
- 需要生成的对象具有复杂的内部结构。
- 需要生成的对象内部属性本身相互依赖。
- 注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
- 角色
- Builder:它为创建一个产品Product对象的各个部件指定抽象接口对象。
- ConcreteBuilder:它实现了Builder接口,实现各个部件的具体构造和装配方法。
- Product:被构建的复杂对象,包含多个组成部件。
- Director:指挥者又称为导演类,负责安排复杂对象的建造次序,指挥者与抽象者之间存在关联关系。
1. Android实际应用
- AlertDialog
- Glide/okHttp
- 初始化时设置缓存等。
(五)原型模式
- 意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
- 主要解决:在运行期建立和删除原型。
- 何时使用:
- 当一个系统应该独立于它的产品创建,构成和表示时。
- 当要实例化的类是在运行时刻指定时,例如,通过动态装载。
- 为了避免创建一个与产品类层次平行的工厂类层次时。
- 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
- 如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
- 关键代码:
- 实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。
- 原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
- 应用实例:
- 细胞分裂。
- JAVA 中的 Object clone() 方法。。
- 优点:
- 性能提高。
- 逃避构造函数的约束。
- 缺点:
- 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
- 必须实现 Cloneable 接口。
- 适用场景:
- 资源优化场景。
- 性能和安全要求的场景。
- 类初始化需要消耗非常多的资源,这个资源包括数据、硬件资源等。
- 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限。
- 一个对象多个修改者的场景。 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
- 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。
- 注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
(六)适配器模式
- 意图:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
- 主要解决:在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
- 何时使用:
- 系统需要使用现有的类,而此类的接口不符合系统的需要。
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。
- 通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
- 如何解决:继承或依赖(推荐)。
- 关键代码:适配器继承或依赖已有的对象,实现想要的目标接口。
- 应用实例:
- 美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。
- JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。
- 在 LINUX 上运行 WINDOWS 程序。
- JAVA 中的 jdbc。
- 优点:
- 可以让任何两个没有关联的类一起运行。
- 提高了类的复用。
- 增加了类的透明度。
- 灵活性好。
- 缺点:
- 过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
- 由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
- 适用场景:有动机地修改一个正常运行的系统的接口。
- 注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
1. 分类
- 类适配器
- 定义:把可适配的类的API转换成目标类的API
- UML类图:
- 总结:
- 类适配器使用对象继承的方式,是静态的定义方式。
- 适配器可以重定义Adaptee的部分行为
- 仅仅引入了一个对象,并不需要额外的引用来间接得到Adaptee。
- 由于适配器直接继承了Adaptee,使得适配器不能和Adaptee的子类一起工作。
- 对象适配器
- 定义:对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用委派关系连接到Adaptee类。
- UML类图:
- 总结:
- 对象适配器使用对象组合的方式,是动态组合的方式。
- 一个适配器可以把多种不同的源适配到 同一个目标。
- 要重定义Adaptee的行为比较困难。
- 需要额外的引用来间接得到Adaptee。
2. Android实际应用
- Listview
- ViewPager
(七)装饰器模式
- 意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
- 主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
- 何时使用:在不想增加很多子类的情况下扩展类。
- 如何解决:将具体功能职责划分,同时继承装饰者模式。
- 关键代码:
- Component 类充当抽象角色,不应该具体实现。
- 修饰类引用和继承 Component 类,具体扩展类重写父类方法。
- 应用实例:
- 孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。
- 不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
- 优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
- 缺点:多层装饰比较复杂。
- 适用场景:
- 扩展一个类的功能。
- 动态增加功能,动态撤销。
- 注意事项:可代替继承。
1. UML 类图
2. Android实际应用
- ContextWrapper
(八)组合模式
- 意图:将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
- 主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
- 何时使用:
- 您想表示对象的部分-整体层次结构(树形结构)。
- 您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
- 如何解决:树枝和叶子实现统一接口,树枝内部组合该接口。
- 关键代码:树枝内部组合该接口,并且含有内部属性 List,里面放 Component。
- 应用实例:
- 算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作符也可以是操作数、操作符和另一个操作数。
- 在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。
- 优点:
- 高层模块调用简单。
- 节点自由增加。
- 缺点:在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
- 适用场景:部分、整体场景,如树形菜单,文件、文件夹的管理。
- 注意事项:定义时为具体类。
1. UML类图
2. Android实际应用
- ViewGroup
- 抽象类,继承自View,实现了ViewManager和ViewParent两个接口。
- layout()方法递归调用自己的子View的方法。
(九)外观模式
- 意图:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
- 主要解决::降低访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口。
- 何时使用:
- 客户端不需要知道系统内部的复杂联系,整个系统只需提供一个"接待员"即可。
- 定义系统的入口。
- 如何解决:客户端不与系统耦合,外观类与系统耦合。
- 关键代码:在客户端和复杂系统之间再加一层,这一层将调用顺序、依赖关系等处理好。
- 应用实例:
- 去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。
- JAVA 的三层开发模式。
- 优点:
- 减少系统相互依赖。
- 提高灵活性。
- 提高了安全性。
- 缺点:不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。
- 适用场景:
- 为复杂的模块或子系统提供外界访问的模块。
- 子系统相对独立。
- 预防低水平人员带来的风险。
- 注意事项:在层次化结构中,可以使用外观模式定义系统中每一层的入口。
1. UML类图
2. Android实际应用
- contextImpl
- android中的外观类
- Activity 中的attach()–>attachBaseContext()–>调用了mBase——Context,而这个mBase的各种方法是具体由contextImpl来实现的。