笔记 | 设计模式之 Android 实践与案例

序言

在你接触过的安卓项目当中,如监听器、适配器、迭代器等并不陌生,然而它们无不体现着设计模式的精髓。设计与模式的结合,往往与设计能力与代码质量息息相关。同理,逆过来思考此类问题,对于一些优秀项目、源码的理解障碍往往是对其设计 (逻辑、性能、解耦等) 的理解,而非源码本身。而作为开发者,知其然知其所以然,这也正是我们深入学习设计模式的理由之一。

当然,我们还要正视学习设计模式的心态。掌握了各种设计模式,并不代表你的设计能力与代码质量就会突飞猛进,同样在项目中运用设计模式也不是生搬硬套就解决问题了,在《 Head First 设计模式 》一书中,则把设计模式的使用心智分为:初学者、中级人员和悟道者,虽有“玄学”的味道,但也恰当。即没有最好的模式,而是你综合众多因素,根据经验、方法来筛选合适的设计模式与你的项目结合,运用。

最后,对于设计模式的学习,不要局限于《 Android 源码设计模式 》本身,你可以搭配一些经典论文、综述,或者书籍,以至于怀疑一个问题的正误,多比对、多思考,以得到最精确的理解。而本笔记的作用也在于此,即一个设计模式的架构,或是借鉴学习,亦或是复习之需便于查询。

总览

零 本书架构

面向对象六大原则

  • 单一职责原则

    优化代码第一步。即就一个类而言,应该有且仅有一个引起它变化的原因。

  • 开闭原则

    让程序更稳定,更灵活。即软件中的对象(类、模块、函数等)应该对于扩展是开放的,但对于修改是封闭的。

  • 里氏替换原则

    构建扩展性更好的系统。

  • 依赖倒置原则

    让项目拥有变化能力,即依赖抽象,不依赖具体实现。

  • 接口隔离原则

    系统拥有更高灵活性。

  • 迪米特原则

    也称为「最少知识原则」。即一个对象应对其他对象有最少的了解。

二十三种设计模式

模式名称 一句话描述
单例模式 一个类只有一个实例
Build 模式 自由拓展你的项目
原型模式 使程序运行更高效
工厂方法模式 生成复杂对象
抽象工厂模式 -
策略模式 时势造英雄
状态模式 随遇则安
责任链模式 使编程更有灵活性
解释器模式 化繁为简的翻译机
命令模式 让程序畅通执行
观察者模式 解决、解耦的钥匙
备忘录模式 编程中的后悔药
迭代器模式 解决问题的第三者
模块方法模式 抓住问题的核心
访问者模式 数据结构与操作分离
中介者模式 调解者模式或调停者模式
代理模式 委托模式
组合模式 物以类聚
适配器模式 得心应手粘合剂
装饰模式 动态给对象添加额外职责
享元模式 对象共享,避免创建多对象
外观模式 统一编程接口
桥接模式 连接两地的交通枢纽

MVC 与 MVP 模式

壹 面向对象编程六大原则

单一职责原则

  • Single Responsibility Principle,SRP.
  • 即就一个类而言,应该仅有一个引起它变化的原因。

如何划分一个类,一个函数的职责?每个人的经验不同,观点看法也不同,故视具体任务而定。但它也有一些基本的知道原则:

  • 两个完全不一样的功能就不应该放到同一个类中。
  • 一个类中应该是一组相关性很高的函数、数据的封装。

开闭原则

  • Open Close Principle,OCP.
  • 即软件中的对象(类、模块、函数等)应该对于扩展是开放的,但对于修改是封闭的。
  • 勃兰特·梅耶. 《面向对象软件构造》中提倡:
    • 新的或改变的特性应通过新建不同的类实现,新建的类可通过 继承 的方式来重用原类的代码。
    • 已存在的实现类对于修改是封闭的,但新的实现类可通过 覆写父类的接口 应对变化。

开闭原则知道我们,当软件需变化时,应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。

里氏替换原则

往往开闭原则与里氏替换原则是生死相依、不离不弃的。

  • Liskov Substitution Principle,LSP。
  • 所有引用基类的地方必须能透明地使用其子类的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public abstract class View {
public abstract void draw();
public void measure(int width, int height) {
// 测量视图的大小
}
}

public class Button extends View {
public draw() {
// 绘制按钮
}
}

public class Windows {
public show(View child) {
child.draw();
}
}

上述例子中,任何继承自 View 类的子类都可以设置给 show 方法,即里氏替换。这样千变万化的 View 传递给 Window,Window 只管组织 View,并显示在屏幕上。

依赖倒置原则

  • Dependence Inversion Principle,DIP.
  • 一种特定的解耦形式,使得高层次的模块不依赖于低层次的模块的实现细节。
  • 依赖倒置原则的几个关键点:

    • 高层模块不应该依赖低层模块,两者都应以来其抽象(接口或抽象类)

      高层模块指调用端,低层模块指实现类。

    • 抽象不应该依赖细节

    • 细节应依赖抽象
  • 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系。一句话概括:面向接口编程,面向抽编程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
* 设计一款实现图片缓冲功能的接口,具体的缓冲实现方式、细节,根据实际情况编写。
*/

public interface ImageChache { // ImageCache 缓存抽象
public Bitmap get(String url);
public void put(String url, Bitmap bmp);
}

public class MemoryCache implements ImageCache {
// 根据实际需求,再实现具体细节
}

public class ImageLoader {
// 图片缓存类,依赖抽象,不依赖细节
ImageCache mCache = new MemoryCache();

public void displayImage(String url, ImageView imageView) {
Bitmap bmp = mCache.get(url);
if(null == bmp){
downloadImageAsync(url, imageView);
} else {
imageView.setImageBitmap(bmp);
}
}

public void setImageCache(ImageCache cache) {
mCache = cache;
}
}

接口隔离原则

  • Interface Segregation Principles,ISP.
  • 类间的依赖关系应建立在最小的接口上。ISP 将非常庞大、臃肿的接口拆分成更小的和更具体的接口。IPS的目的是系统解开耦合。

如上例中,ImageLoader 中的 ImageCache,ImageLoader 只需要知道该缓存对象有存、取缓存图片的接口即可,其他一概不管。

迪米特原则

  • 一个对象应对其他对象有最少的了解、即类的内部如何实现与调用者、依赖者没关系,调用者或依赖者之需知道它需要的方法即可,其他一概不管。

  • 图 1-1 展示了租客、中介与房间相互之间的需求关系。

    租客、中介与房间关系

    图 1-1 租客、中介与房间关系

    因为租客只需要房子,即把需求转达中介,对房子具体的租金、维修、签约等交由中介处理,租客不需要再了解细节。改进效果见图 1-2。

    租客、中介与房间关系

    图 1-2 改进:租客、中介与房间的关系

贰 二十三种设计模式解析

单例模式

单例模式的定义

  • 确保某 一个类只有一个实例 ,而且自行实例化并向整个系统提供这个实例。

    一个类只有一个实例 :避免产生多个对象消耗过多资源,如访问 I/O 和数据库等资源。

单例模式 UML 类图

单例模式示意图

图 2-1 单例模式示意图
  • 实现单例模式主要有如下几个关键点:

    • 构造函数 不对外开放,一般设为 私有
    • 通过一个 静态方法 或者 枚举返回 单例类对象;
    • 确保单例类的对象有且只有一个,尤其多线程环境下;
    • 确保单例类对象在 反序列化 时不会重新构建对象;

      序列化:将对象的状态信息转换为可以存储或传输的形式的过程。一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等。在网络传输过程中,可以是字节或是XML等格式。

      反序列化:而字节的或XML编码格式可以还原完全相等的对象。这个相反的过程又称为反序列化。

单例模式的简单示例

例如一个公司只有一个 CEO,一个应用只有一个 Application 对象等。下面以公司里的 CEO 为例来简单演示,即一个公司可有几个 VP,无数个员工,但 CEO 只有一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 员工的基类
public class Staff {
public void work(){
// 忽略执行细节
}
}

// 副总裁类
public class VP extends Staff {
public void work() {
// 覆写执行细节
}
}

// 饿汉单例模式:声明静态对象时已初始化
public class CEO extends Staff { // 公司保证只有一个 CEO
private static final CEO mCeo = new CEO();
private CEO(){}
public static CEO getCeo() {
return mCeo;
}
public void work() {
// 覆写执行细节
}
}

/*
* 实际中使用:
* CEO 类不能通过 new 的形式构造对象,只能通过 CEO.getCeo() 函数获取。
* CEO 对象是静态对象,并在声明时已初始化,保证 CEO 对象的唯一性。
*/

Staff ceo1 = CEO.getCeo();
Staff ceo2 = CEO.getCeo();

单例模式的其他实现方式

懒汉模式
  • 声明一静态对象;
  • 调用 getInstance() 方法初始化 ( 用时才初始化,即惰性处理机制 )
1
2
3
4
5
6
7
8
9
10
11
12
13
// 懒汉单例模式:用时才初始化,即惰性处理机制
public class Singleton {
private static Singleton instantce;
private Singleton() {}

// 添加 synchronized 关键字,即 getInstance() 是一个同步方法
public static synchronized Singleton getInstance() {
if( null == instance ) {
instance = new Singleton();
}
return instance;
}
}
  • 懒汉单例模式的优缺点:
    • 优点 - 使用时才实例化,一定程度上节约资源。
    • 缺点 - 每次调用 getInstance() 都进行同步,造成不必要同步开销。
Double CheckLock (DCL)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Singleton {
// private static Singleton sInstance = null;
private volatile static Singleton sInstance = null; // 保证 sInstance 对象每次都是从主内存存、读取。

private Singleton() {}

public void doSomething() {
System.out.println("do sth.");
}

public static Singleton getInstance() {
if( null == sInstance ) { // 避免不必要同步
synchronized(Singleton.class) {
if( null == sInstance ) { // 此处判空操作,是因为 Java 编译器允许处理器乱序执行,具体解析见注解
sInstance = new Singleton();
}
}
}
return sInstance;
}
}

DCL 又称「丑陋的优化」?

DCL 虽一定程度解决了资源消耗,多余同步、线程安全等问题,但某种情况下还是会出现失效问题 ( 双重检查锁定(DCL) ),即称「丑陋的优化」。

  • 线程A,执行 sInstance = new Singleton();,编译器会编译成多条汇编指令,具体汇编指令的分工为:

    • Step.01 给 Singleton 实例分配内存;
    • Step.02 调用 Singleton() 的构造函数;
    • Step.03 将 sInstance 对象指向分配的内存空间;
  • 然而 Java 编译器允许处理器乱序执行,即有「1-2-3」或「1-3-2」的执行顺序。

  • 若执行「1-3-2」的顺序,这样会使 DCL 的优化失效,即第三步执行完毕,sInstance 非空,线程B取走 sInstance。再使用时就会报错。
静态内部类单例模式
1
2
3
4
5
6
7
8
9
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.sInstance;
}
private static class SingletonHolder { // 静态内部类
private static final Singleton sInstance = new Singleton();
}
}

第一次加载 Singleton 的 getInstance() 方法才会使 sInstance 被初始化。因此,第一次调用 getInstance() 方法会导致虚拟机加载 SingletonHolder 类.

内部类是延时加载的,只会在第一次使用时加载,不使用不加载。这样,即保证了线程安全,又保证单例对象唯一性,同时也延迟单例的实例化。

枚举单例
1
2
3
4
5
6
7
// 默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
System.out.println("do sth.");
}
}

总结

  • 不管以哪种形式实现单例模式,它们的核心原理都是将 构造函数私有化 ,并通过 静态方法获取一个唯一的实例

    获取实例的过程须保证线程安全,防止反序列化导致重新生成实例对象等。

  • 选择哪种实现形式取决项目本身,如是否是复杂的并发环境、JDK 版本是否过低、单例对象的资源消耗等。

  • 单例模式的优缺点
    • 优点
      1) 只生成一个实例,减少系统的性能开销;
      2) 当一对象的产生需要较多资源时,如读取配置、产生其他依赖对象时,可通过应用启动时直接产生一个单例对象,永久驻留内存。
    • 缺点
      1) 单例模式一般没有接口,扩展性难;
      2) 单例对象若持有 Context,那么很容易引发内存泄漏,此时需注意传递给单例对象的 Context 应该是 Application.Context

Bulider 模式

Bulider 模式的定义

  • 创建型设计模式。
  • 将一个复杂对象的 构建 与它的 表示 分离,使得同样的构建过程可以创建不同的表示。
  • Builder 模式是一步步创建一个复杂对象的,它允许用户在不知内部构建细节的情况下,可以更精细地控制对象的构造流程。

Builder 模式的使用场景

  • 产品类非常复杂,或产品类中调用顺序不同产生不同的作用,这时需要使用 Builder 模式。
  • 初始化一个对象特别复杂,如参数多,且很多参数都具有默认值。

Builder 模式的UML类图

Builder模式示意图

图 2-2 Builder 模式示意图

Builder 模式的简单实现

便于理解,本示例的 UML 类图见图 2-3。

计算机组装过程

图 2-3 计算机组装过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/*
* 下述程序以计算机组装过程简化为:构建主机,设置操作系统,设置显示器。
*/

// 计算机抽象类,即 Product 角色
public abstract class Computer {
protected String mBoard;
protected String mDisplay;
protected String mOS;
protected Computer() {}
public void setBoard(String board) { // 设置 CPU 核心数
mBoard = board;
}
public void setDisplay(String display) { // 设置内存
mDisplay = display;
}
public abstract void setOS();
@override
public String toString() {
return "Computer[...]";
}
}

// 具体的 Computer 类 - MacBook
public class Macbook extends Computer {
protected Macbook() {}
@override
public void setOS() {
mOS = "MAC OSX 10.10";
}
}

// 抽象 Builder 类
public abstract class Builder {
public abstract class Builder {
public abstract void buildBoard(String board); // 设置主机
public abstract void buildOS(); // 设置操作系统
public abstract void buildDisplay(String display); // 设置显示器
public abstract Computer create(); // 创建 Computer
}
}

// 具体的 Builder 类 - MacbookBuilder
public class MacbookBuilder extends Builder {
private Computer mComputer = new Macbook();
public void buildBoard(String board){
mComputer.setBoard(board);
}
public void buildDisplay(String display) {
mComputer.setDisplay(display);
}
public void buildOS() {
mComputer.setOS();
}
public Computer create() {
return mComputer;
}
}

// Director 类,负责构造 Computer
public class Director {
Builder mBuilder = null;
public Director(Builder builder) {
mBuilder = builder;
}
public void construct(String board, String display) {
mBuilder.buildBoard(board);
mBuilder.buildDisplay(display);
mBuilder.buildOS();
}
}

// 客户端实现
public class Test {
public static void main(String[] args) {
Builder builder = new MacbookBuilder(); // 构造器
Director pcDirector = new Director(builder); // Director
// 封装构建过程
pcDirector.construct("英特尔主板", "Retina 显示器");
System.out.println("Computer Info: " + builder.create().toString());
}
}

Builder 模式实战

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
* 知名图片加载库:Universal-Image-Loader
*/

ImageLoaderConfiguration config = new ImageLoaderConfiguration
.Builder(context) // 用户只能通过 Builder 对象构建 ImageLoaderConfiguration 对象,这就是构建和表示相分离
.threadPriority(Thread_NORM_PRIORITY_2)
.denyCacheImageMultipleSizesInMemory()
.discCacheFileNameGenerator( new MD5FileNameGenerator() )
.tasksProcessingOrder(QueueProcessingType.LIFO)
.bulider();

ImageLoader.getInstance().init(config);

总结

  • Builder 模式,通过作为配置类的构建器将配置的构建和表示分离开来,同时也将配置从目标类中隔离出来,避免过多的 Setter 方法暴露在目标类当中。
  • Builder 模式的优缺点
    • 优点
      1) 良好的封装性,不必知道产品内部组成的细节;
      2) 建造者独立,易于扩展。
    • 缺点
      1) 产生多余 Builder 对象及 Director 对象,消耗内存。

原型模式

原型模式的定义

  • 创建型的模式。
  • 定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
    • 原型拥有样板实例,可克隆内部属性一致的对象。
    • 原型模式多用于创建复杂的或构建耗时的实例,即复制一个已经存在的实例可使程序运行更高效。

原型模式的使用场景

  • 类初始化需消耗非常多的资源 ( 数据、硬件资源等 )。
  • 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限。
  • 一个对象需提供其他对象访问,且各调用者可能都需修改其值时,可考虑用原型模式或拷贝多个对象以供调用者使用,即 保护性拷贝

原型模式的 UML 类图

Builder模式示意图

图 2-4 原型模式的 UML 类图

原型模式的简单实现

便于理解,本示例的 UML 类图见图 2-5。

WordDocument文档编辑器

图 2-5 WordDocument 文档编辑器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/*
* WordDocument 具有文字、图片编辑功能的简单文档处理类。且为保护源文件,其可在克隆对象上作内容修改。
*/

public class WordDocument implements Cloneable {
// WordDocument 扮演 ConcretePrototype 角色
// Cloneable 扮演 Prototype 角色
private String mText; // 文本
private ArrayList<String> mImages = new ArrayList<String>(); // 图片名列表
public WordDocument() {
// 忽略实现细节
}
@Override
protected WordDocument clone() {
try {
WordDocument doc = (WordDocument) super.clone();
doc.mText = this.mText;
doc.mImages = this.mImages;
return doc;
} catch(Exception e) {
}
return null;
}
public String getText() {
return mText;
}
public void setText(String mText) {
this.mText = mText;
}
public List<String> getImage() {
return mImages;
}
public void addImage(String img) {
this.mImages.add(img);
}
public void showDocument() {
System.out.println("Text:" + mText);
System.out.println("Images List:");
for(String imgName:mImages) {
System.out.println("image name:" + imgName);
}
}
}

// 客户端实现
public class Client {
public static void main(String[] args) {
WordDocument originDoc = new WordDocument();

/* Partion A start */
originDoc.setText("This is a Aircle");
originDoc.addImage("Image A");
/* Partion A end */
originDoc.showDocument();

WordDocument secDoc = originDoc.clone();
secDoc.showDocument();
secDoc.setText("This is a Paper"); // 只是改变了引用指向
secDoc.showDocument();
originDoc.showDocument(); // 还是输出 Partion A 的结果
}
}

注:通过 clone() 拷贝对象时并不会执行构造函数。如果在构造函数中需要一些特殊的初始化操作类型,在使用 Cloneable 实现拷贝时,注意构造函数不会执行的问题。

浅拷贝和深拷贝

  • 浅拷贝:上述例子实际上只是一个浅拷贝,也称 影子拷贝,即只是副本文档引用原始文档的字段。

    浅拷贝示意图

    图 2-6 浅拷贝示意图
    1
    2
    3
    4
    5
    6
    secDoc.setText("This is a Paper.");
    secDoc.addImage("Image B");
    secDoc.showDocument(); // Case 1
    originDoc.showDocument(); // Case 2

    // Case 1,2 都增加了图片 “Image B”,原因是 secDoc 只是单纯指向了 this.mImages
  • 深拷贝:为了解决浅拷贝所带来的“问题” ( 视具体问题而定 ),引入深入拷贝。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Override
    protected WordDocument clone() {
    try {
    WordDocument doc = (WordDocument) super.clone();
    doc.mText = this.mText;
    // doc.mImages 指向 mImages 的一份拷贝,而不是 this.mImages 本身
    doc.mImages = (ArrayList<String>) this.mImages.clone();
    return doc;
    } catch(Exception e) {
    }
    return null;
    }

原型模式实战

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/*
* 在线用户信息修改 (需登录后修改用户信息)
*/

// 用户实体类
public class User {
public int age;
public String name;
public String phoneNum;
public Adress,adress;

@Override
public String toString() {
return "User[age=...]";
}
}

// 登录接口
public interface Login (
void login();
)

public class LoginImpl implements Login {
@Override
public void login() {
User loginedUser = new User(); // 登录服务器,获取用户信息
loginedUser.age = 12;
loginedUser.name = "Mr.Sample";
loginedUser.address = new Address("BeiJing", "HaiDing", "Garden Rd");

// 登录完成后,将用户信息设置到 Session:
LoginSession.getLoginSession()
.setLoginedUser(loginedUser)

}
}

// 登录 Session
public class LoginSession {
static LoginSession sLoginSession = null;
private User longinedUser; // 已登录用户
private LoginSession() {}
public static LoginSession getLoginSession() {
if(null == sLoginSession) {
sLoginSession = new LoginSession();
}
return sLoginSession;
}

// 包级私有:即不加任何修饰符,该模式(默认访问模式)下,只允许在同一包中进行访问
void setLoginedUser(User user) { // 设置已登录用户信息,不对外开放
loginedUser = user;
}

public User getLoginedUser() {
return loginedUser;
}
}

/* Partion A : 以下是实际执行部分,可能在不同包的某个类下执行 */

// 获取已登录的 User 对象
User newUser = LoginSession.getLoginSession().getLoginedUser();
// 更新用户信息
newUser.address = new Adress("BeiJing", "ChaoYang", "DaWang Rd");

用户信息的更新,限定于与 LoginSession 类在同一包下才能执行,即 Partion A 的操作,使这样的限定失效。我们可作以下改进:

  • 在 User 类中实现 Cloneable 接口。
  • 在 LoginSession 中将 getLoginedUser() 改为 return loginedUser.clone() ,即在任何地方调用,获得的都是用户拷贝的对象,修改只是作用于拷贝的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 实现 Cloneable 接口
public class User implements Cloneable { // 用户实体类
public int age;
public String name;
public String phoneNum;
public Adress,adress;

@Override
public User clone() {
User user = null;
try {
user = (User) super.clone();
} catch(CLoneNotSupportedException e) {
e.printStackTrace();
}
return user;
}

@Override
public String toString() {
return "User[age=...]";
}
}

public class LoginSession {
// 即管在任何地方调用,获得的都是用户拷贝的对象
public User getLoginedUser() {
return loginedUser.clone();
}
}

总结

  • 原型模式的优缺点
    • 优点 - 原型模式是在内存中二进制的拷贝,比 new 一个对象性能更优。
    • 缺点 - 内存中拷贝,构造函数是不会执行的。

工厂方法模式

工厂方法模式的定义

  • 创建型设计模式。
  • 定义一个用于创建对象的接口,让子类决定实例化哪个类。

工厂方法模式的使用场景

  • 复杂对象的创建,而用 New 就可以完成创建的对象则不必使用工厂方法了。

工厂方法模式的 UML 类图

工厂方法模式的 UML 如图 2-7 所示。

浅拷贝示意图

图 2-7 工厂方法模式 UML 类图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public abstract class Product {
public abstract void method();
}
public class ConcreteProductA extends Product { // 具体产品 A
@Override
public void method() {
System.out.println("我是具体的产品A.");
}
}
public class ConcreteProductB extends Product { // 具体产品 B
@Override
public void method() {
System.out.println("我是具体的产品B.");
}
}

public abstract class Factory { // 抽象工厂类
/*
* @return 具体的产品对象
*/
public abstract Product createProduct();
}
public class ConcreteFactory extends Factory {
@Override
public Product createProduct() {
// 返回具体产品 A 或者具体产品 B
// return new ConcreteProductA();
// return new ConcreteProductB();
}
}

// 客户端实现
public class Client {
public static void main(String[] args) {
Factory factory = new ConcreteFactory();
Product p = factory.createProduct();
p.method();
}
}

另外,我们可以利用反射的方式实现多工厂方法模式,具体见下述代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public abstract class Factory {

/*
* 具体生产什么由子类去实现
* @param clz 产品对象类类型
* @return 具体的产品对象
*/
public abstract<T extends Product> T createProduct(class<T> clz);
}

public class ConcreteFactory extends Factory {
@Override
public <T extends Product> T createProduct(class<T> clz) {
Product p = null;
try {
p = (Product) class.forName(clz.getName()).newInstance();
} catch(Exception e) {
e.printStackTrace();
}
return (T) p;
}
}

// 客户端中实现
public class Client {
public static void main(String[] args) {
Factory factory = new ConcreteFactory();
Product p = factory.createProduct(ConcreteProductA.class);
p.method();
}
}

工厂方法模式的简单实现

某汽车厂主要就是组装某款 SUV 车型,比如 Q3、Q5、Q7,对于这类车型来说,内部结构差异并不是很大,因此一条生产线足以应付 3 种车型,对于该类生产线可提供一抽象类定义。

便于理解,本示例的 UML 类图见图 2-8。

某工厂生产某车型的工厂方法

图 2-8 某工厂生产某车型的工厂方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public abstract class AudiFactory {

/*
* 某工厂生产某种车型的工厂方法
* @param clz 具体的 SUV 型号类型
* @return 具体型号的 SUV 车对象
*/
public abstract<T extends AudiCar> T createAudiCar(class<T> clz);
}

public class AudiCarFactory extends AudiFactory {
@Override
public <T extends AudiCar> T createProduct(class<T> clz) {
Product p = null;
try {
p = (AudiCar) class.forName(clz.getName())
.newInstance();
} catch() {
e.printStackTrace();
}
return (T) p;
}
}

// 汽车的抽象产品类
public abstract class AudiCar {
public abstract void drive();
public abstract void selfNavigation();
}

// 具体车型:Q3
public class AudiQ3 exntends AudiCar {
@Override
public void drive() {
System.out.println("Q3 Launched!");
}
@Override
public void selfNavigation() {
System.out.println("Q3 starts a auto-navigation!");
}
}

// 客户端实现
public class Client {
public static void main(String[] args) {
AudiFactory factory = new AudiFactory();
AudiQ3 audiQ3 = factory.createProduct(AudiQ3.class);
audiQ3.dirve();
audiQ3.selfNavigation();
}
}

工厂方法模式的实战

Android 数据持久化有很多方式,如 SharedPreferences (XML)、SQLite (关系数据库)。对数据操作的方法无非就是增、删、改、查,若我们将每种数据储存的方式作为一个产品类,在抽象产品类中定义对数据操作的方法,即我们宏观层面把握操作的逻辑,具体的实现逻辑由储存数据的方式决定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public abstract class IOHandler {
public abstract void add(String id, String name);
public abstract void remove(String id);
public abstract void update(String id, String name);
public abstract String query(String id);
}

public class FileHandler extends IOHandler { // 普通文件存储
@Override
public void add(String id, String name) {
/* 业务逻辑 */ }
@Override
public void remove(String id) {
/* 业务逻辑 */ }
@Override
public void update(String id, String name) {
/* 业务逻辑 */ }
@Override
public String query(String id) {
/* 业务逻辑 */
return "AigeStudio";
}
}

public class XMLHandler extends IOHandler { // XML 文存储
public void add(String id, String name) {
/* 业务逻辑 */ }
public void remove(String id) {
/* 业务逻辑 */ }
public void update(String id, String name) {
/* 业务逻辑 */ }
public String query(String id) {
/* 业务逻辑 */
return "SMBrother";
}
}

public class DBHandler extends IOHandler { // SQLite 数据库存储
public void add(String id, String name) {
/* 业务逻辑 */ }
public void remove(String id) {
/* 业务逻辑 */ }
public void update(String id, String name) {
/* 业务逻辑 */ }
public String query(String id) {
/* 业务逻辑 */
return "Android";
}
}

public class IOFactory {

/*
* 获取 IO 处理者
* @param clz IOHandler 类型的类类型
* @return IOHandler 对象
*/

public static <T extends IOHandler> T getIOHandler(class<T> clz) {
IOHandler handler = null;
try {
handler = (IOHandler) class.forName(clz.getName())
.newInstance();
} catch(Exception e) {
e.printStackTrace();
}
return (T) handler;
}
}

// 客户端实现
public class FactoryActivity extends Activity {
@override
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_factory);

// 获取显示查询内容的 TextView 对象
final TextView tvContent = (TextView) this.findViewById(R.id.factory_content_tv);
// 获取查询普通文件数据的按钮对象,并设置监听
Button btnFile = (Button) this.findViewById(R.id.factory_file_btn);
btnFile.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
IOHandler handler = IOFactory.getIOHanHandler(FileHandler.class);
Log.d("AigeStudio", handler.query("4455645646"));
tvContent.setText(handler.query("4455645646"));
}
});
}
}

抽象工厂模式

抽象工厂模式的定义

  • 创建型设计模式。
  • 为创建一组相关或者是相互依赖的对象 提供一个 接口 ,而不需要指定它们的具体类。

抽象工厂模式的使用场景

一个对象族有相同约束时可以使用抽象工厂模式。如:
Android、iOS、Window Phone 下都有短信软件和拨号软件,两者属于软件范畴,但由于操作系统平台不一样,其代码实现细节也是有差异的,则我们可考虑使用抽象工厂方法模式去产生不同平台下的同款软件。

抽象工厂模式的 UML 类图

抽象工厂方法模式的 UML 如图 2-9 所示。

抽象工厂方法模式UML类图

图 2-9 抽象工厂方法模式 UML 类图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public abstract class AbstractProductA { // 抽象产品类 A
public abstractvoid method();
}

public abstract class AbstractProductB { // 抽象产品类 B
public abstractvoid method();
}

public class ConcreteProducxtA1 extends AbstractProductA { // 具体产品类 A1
@Override
public void method() {
System.out.println("具体产品 A1 的方法.");
}
}

public class ConcreteProducxtA2 extends AbstractProductA { // 具体产品类 A2
@Override
public void method() {
System.out.println("具体产品 A2 的方法.");
}
}

public class ConcreteProducxtB1 extends AbstractProductB { // 具体产品类 B1
@Override
public void method() {
System.out.println("具体产品 B1 的方法.");
}
}

public class ConcreteProducxtB2 extends AbstractProductB { // 具体产品类 B2
@Override
public void method() {
System.out.println("具体产品 B2 的方法.");
}
}

public abstract class AbstractFactory { // 抽象工厂类

/**
* 创建产品 A 的方法
* @return 产品 A 的对象
*/
public abstract AbstractProductA createProductA();

/**
* 创建产品 B 的方法
* @return 产品 B的对象
*/
public abstract AbstractProductB createProductB();
}

public class ConcreteFactory1 extends AbstractFactory { // 具体工厂类1
@Override
public abstract AbstractProductA createProductA1() {
return new ConcreteProductA1();
};
@Override
public abstract AbstractProductB createProductB1() {
return new ConcreteProductB1();
};
}

public class ConcreteFactory2 extends AbstractFactory { // 具体工厂类2
@Override
public abstract AbstractProductA createProductA2() {
return new ConcreteProductA2();
};
@Override
public abstract AbstractProductB createProductB2() {
return new ConcreteProductB2();
};
}

抽象工厂模式的简单实现

在简单工厂模式的简单实现中,我门以车厂生产汽车为例。虽 Q3、Q5、Q7 同为一车系,但三者之间的零部件产别却很大,如 Q3、Q7 当中,Q3 装配的是国产发动机,普通轮胎和普通制动系统;Q7 则装配的是进口发动机,全尺寸越野轮胎和制动性能极好的制动系统。

即同为一系列车,大家共有部件有发动机、轮胎和制动系统等,由于具体的部件品质不同,装配的细节又不同。故我们可将抽象工厂模式应用当中,化繁为简。具体的架构如图 2-10 的 UML 类图所示。

车厂生产同系列汽车的抽象工厂模式

图 2-10 车厂生产同系列汽车的抽象工厂模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
public abstract class CarFactory {
/**
* 生产轮胎
* @return ITire 轮胎
*/
public abstract ITire createTire();

/**
* 生产发动机
* @return IEngine 发动机
*/
public abstract IEngine createEngine();

/**
* 生产制动系统
* @return IBrake 制动系统
*/
public abstract IBrake createBrake();
}

public interface ITire { // 轮胎
void tire();
}

public class NormalTire implements ITire {
@Override
public void tire() {
System.out.println("普通轮胎");
}
}

public class SUVTire implements ITire {
@Override
public void tire() {
System.out.println("越野轮胎");
}
}

public interface IEngine() { // 发动机
void engine();
}

public class DomesticEngine implements IEngine {
@Override
public void engine() {
System.out.println("国产发动机");
}
}

public class ImportEngine implements IEngine {
@Override
public void engine() {
System.out.println("进口发动机");
}
}

public interface IBrake { // 制动系统
void brake();
}

public class NormalBrake implements IBrake {
@Override
public void brake() {
System.out.println("普通制动");
}
}

public class SeniorBrake implements IBrake {
@Override
public void brake() {
System.out.println("高级制动");
}
}

public class Q3Factory extends CarFactory { // Q3工厂类
@Override
public ITire createTire() {
return new NormalTire();
}
@Override
public IEngine createEngine() {
return new Domestic Engine();
}
@Override
public IBrake createBrake() {
return new NormalBrake();
}
}

public class Q3Factory extends CarFactory { // Q3工厂类
@Override
public ITire createTire() {
return new NormalTire();
}
@Override
public IEngine createEngine() {
return new DomesticEngine();
}
@Override
public IBrake createBrake() {
return new NormalBrake();
}
}

public class Q7Factory extends CarFactory { // Q7 工厂类
@Override
public ITire createTire() {
return new SUVTire();
}
@Override
public IEngine createEngine() {
return new ImportEngine();
}
@verride
public IBrake createBrake() {
return new SeniorBrake();
}
}

// 客户端实现
public class Client {
public static void main(String[] args) {
// 构造一个生产 Q3 的工厂
CarFactory factoryQ3 = new Q3Factory();
factoryQ3.createTire().tire();
factoryQ3.createEngine().engine();
factoryQ3.createBrake().brake();

// 构造一个生产 Q7 的工厂
CarFactory factoryQ7 = new Q7Factory();
factoryQ7.createTire().tire();
factoryQ7.createEngine().engine();
factoryQ7.createBrake().brake();
}
}

总结

  • 抽象工厂方法模式的优缺点
    • 优点 - 分离接口与实现,即客户端使用抽象工厂的创建对象,客户端不知具体实现是谁,客户端只是面向产品的接口编程而已,使其从具体的产品实现中解耦。
    • 缺点
      1) 类文件的爆炸性增加。
      2) 不太容易扩展新的产品类,因为每当增加一个产品类,就需修改抽象工厂,故所有具体工厂类均会被修改。

策略模式

策略模式的介绍

实现某功能,可以有多种算法或策略选择,例如排序算法,有插入排序、归并排序、冒泡排序等。

思考:多种排序算法,可以写在一个类中,一个方法对应一种具体排序。但是缺点也是很明显,即臃肿;维护成本高,且容易引发错误;每增加一种排序需修改封装类的源码。

改进:提供一个统一接口,不同的算法或策略有不同的实现类。

策略模式的使用场景

  • 针对同类问题的多种处理方式,仅仅是 具体行为 有差别。
  • 需要安全地封装多种 同类型 的操作。
  • 出现同一抽象类,有多个子类,而又需使用 if-elseswitch-case 来选择具体子类。

    但缺点也明显,耦合性高;代码臃肿难维护。

策略模式的 UML 类图

策略模式的 UML 如图 2-11 所示。

策略模式UML类图

图 2-11 策略模式 UML 类图

策略模式的简单实现

下面以在北京乘坐公共交通工具的费用计算来演示一简单示例。在 2014 年 12 月 20 号之后,北京提高公交价格,不在是单一票价制,而是分段计费。显然,公交车和地铁的价格计算方式是不一样的。但是,我们的示例中是需要计算乘不同出行工具的成本,故我们采用策略模式进行设计、编码。

便于理解,本示例的 UML 类图如图 2-12 所示。

交通方案UML类图

图 2-12 交通方案的 UML 类图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public interface CalculateStragety {
/**
* 按距离来计算价格
* @param km 公里
* @return 返回价格
*/
int calculatePrice();
}

public class BusStragety implements CalculateStragety {
@Override
public int calculatePrice(int km) {
// 公交车价格计算策略
}
}

public class SubwayStragety implements CalculateStragety {
@Override
public int calculatePrice(int km) {
// 地铁价格计算策略
}
}

// 客户端实现: 出行价格计算器
public class TranficCalculator {
CalculateStrategy mStrategy = null;

public static void main(String[] args) {
TranficCalculator calculator = new TranficCalculator();
// 设置计算策略
calculator.setStrategy( new BusStrategy() );
// 计算价格
System.out.println("公交车乘16公里的价格: " + calculator.calculatePrice(16));
}

public void setStrategy(CalculateStrategy mStrategy) {
this.mStrategy = mStrategy;
}

public int calculatePrice(int km) {
return mStrategy.calculatePrice(km);
}
}

策略模式的实战应用

对于默认情况下,ImageLoader 会按照先后顺序加载图片,但在实际算法当中,相反顺序加载图片也是有可能的,即反序列加载图片。当然加载方式可看作多种策略,共同的目标是实现加载图片。图 2-13 是 ImageLoader 的 UML 类图。

ImageLoader的UML类图

图 2-13 ImageLoader 的 UML 类图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public interface LoadPolicy { // 加载策略接口
public int compare(BitmapRequest request1, BitmapRequest request2);
}

/**
* 顺序加载策略
*/
public class SerialPolicy implements LoadPolicy {
@Override
public int compare(BitmapRequest request1, BitmapRequest request2) {
// 按照添加到队列的序列号顺序来执行
return request1.serialNum - request2.serialNum;
}
}

/**
* 逆序加载策略,即从最后加入队列的请求进行加载
*/
pulbic class ReversePolicy implements LoadPolicy {
@Override
public int compare(BitmapRequest request1, BitmapRequest request2) {
// 注意: Bitmap 请求要先执行最晚加入队列的请求,ImageLoader 的策略
return request2.serialNum - request1.serialNum;
}
}

/**
* 因每个请求都有一序列号,序列号以递增形式增加,越晚加入队列的请求序列号越大。
* 而请求队列是优先级队列,因此我们需要在图片加载请求类中实现 Comparable 接口,以实现对这些请求的排序处理。
*/
public class BitmapRequest implements Comparable<BitmapRequest> { // 加载策略
LoadPolicy mLoadPolicy = new SerialPolicy();
@Override
public int compareTo(BitmapRequest another) {
// 委托给 LoadPolicy 进行处理,实现按照策略模式
return mLoadPolicy.compare(this, another);
}
}

/**
* 用户在配置 ImageLoader 时可以设置加载策略,
* 这个策略会被设置给每个图片加载请求对象,具体如下:
*/
public void displayImage(final ImageView imageView, final String uri, final DisplayConfig config, final ImageListener listener) {
BitmapRequest request = new BitmapRequest(imageView, uri, config, listener);

// 加载的配置对象,如果没有设置则使用 ImageLoader 的配置
request.displayConfig = request.displayConfig != null ?
request.displayConfig : mConfig.displayConfig;
// 设置加载策略
request.setLoadPolicy(mConfig.loadPolicy);
// 添加到队列中
mImageQueue.addRequest(request);
}

总结

  • 策略模式的优缺点
    • 优点
      1) 很好地演示了开闭原则,也就定义了抽象。
      2) 耦合度相对较低,扩展方法。
    • 缺点 - 随着策略的增加,子类会变得繁多。

状态模式

  • 状态模式和策略模式和结构几乎一样,但它们的目的本质完全相异。
    • 状态模式:行为是平行的,不可替换的。
    • 策略模式:行为彼此独立,可相互替换。

状态模式的定义

当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。

状态模式的使用场景

代码中包含大量与对象状态有关的条件语句。如操作中含有庞大的多分支语句 ( if-elseswitch-case ),且这些分支依赖与该对象的状态。

若使用状态模式来优化架构,即每一条件分支放于独立的类。

状态模式的 UML 类图

状态模式的UML类图

图 2-14 状态模式的 UML 类图

状态模式的简单示例

下面以电视遥控器为例演示状态模式的实现。便于理解,本示例的 UML 类图如图 2-15 所示。

电视遥控器UML类图

图 2-15 电视遥控器 UML 类图

状态模式实战

在新浪微博中,用户在未登录的情况下点击转发按钮,此时会先让用户登录,然后再执行转发操作;如果已登录的情况下,那么用户输入转发的内容后就可以直接进行操作。

新浪微博用户状态管理UML类图

图 2-16 新浪微博用户状态管理 UML 类图

总结

  • 状态模式的优缺点
    • 优点 - 将所有与一个特定的状态相关的行为都放入一个状态对象中,它提供了一个更好的方法来组织与特定状态相关的代码,将繁琐的状态判断转为结构清晰的状态类族。
    • 缺点 - 必然增加系统类和对象的个数。

责任链模式

责任链模式的定义

  • 行为型设计模式。
  • 通俗定义:每个节点看作一对象,每一对象拥有不同的处理逻辑,将一请求从链式的首端发出,沿着链的路径一次传递每个节点对象,直至有对象处理这个请求为止。
  • 标准定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成链,并沿着这条链传递该请求,直至有对象处理它为止。

责任链模式的使用场景

  • 多个对象可以处理同一请求,但具体由哪个对象处理则在运行时动态决定。
  • 在请求处理者不明确的情况下向多个对象中的其一提交一个请求。
  • 需要动态指定一组对象处理请求。

责任链模式的 UML 类图

责任链模式的 UML 类图如图 2-17 所示。

责任链模式UML类图

图 2-17 责任链模式 UML 类图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 抽象处理者
public abstract class Handler {
protected Handler successor; // 下一节点的处理者
/**
* 请求处理
* @param condition 请求条件
*/
public abstract void handleRequest(String condition);
}

// 具体的处理者1
public class ConcreteHandler1 extends Handler {
@Override
public void handleRequest(String condition) {
if(condition.equals("ConcreteHandler1")) {
System.out.println("ConcreteHandler1 handled");
return;
} else {
successor.handleRequest(condition);
}
}
}

// 具体的处理者2
public class ConcreteHandler1 extends Handler {
@Override
public void handleRequest(String condition) {
if(condition.equals("ConcreteHandler2")) {
System.out.println("ConcreteHandler2 handled");
return;
} else {
successor.handleRequest(condition);
}
}
}

// 客户端实现
public class Client {
public static void main(String[] args) {
// 构造一个 ConcreteHandler1 对象
ConcreteHandler1 handler1 = new ConcreteHandler1();
// 构造一个 ConcreteHandler2 对象
ConcreteHandler2 handler1 = new ConcreteHandler2();
// 设置 handler1 的下一个节点
handler1.successor = handler2;
// 设置 handler2 的下一个节点
handler2.successor = handler1;
// 处理请求
handler1.handleRequest("ConcreteHandler2");
}
}

责任链模式的简单实现

在公司中报销费用中,审批的流程其实就是一个类似责任链的实例。例如,小明是请求的发起者,而处理者有组长、部门主管、经理和老板,对于不同额度的报销费用需要不同级的处理者审批,准确地说,每一类人代表这条链上的一个节点。

例如小民是请求的发起者,而老板则是处于链条顶端的类,小民从链的底端开始发出一个申请报账的请求,首先由组长处理该请求,组长比对后发现自己权限不够于是将该请求转发给位于链中下一个节点的主管,主管比对后发现自己权限不足又将该请求转发给经理,经理也基于同样的原因将请求转发给老板,这样层层转达直至请求被处理。即至始至终小民关心的是报账结果,而不用在乎处理者是谁。责任链模式在这里很好地将请求的发起者与处理者解耦。

便于理解,本示例的 UML 类图如图 2-18 所示。

报账审核机制UML类图

图 2-18 报账审核机制 UML 类图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// 抽象领导者
public abstract class Leader {
protected Leader nextHandler; // 上一级领导处理者

/**
* 处理报账请求
* @param money 能批复的报账额度
*/
public final void handleRequest() {
if( money < limit() ) {
handle(money);
} else {
if( null != nextHandlder ) {
nextHandler.handleRequest(money);
}
}
}

/**
* 自身能批复的额度权限
* @return 额度
*/
public abstract int limit();

/**
* 处理报账行为
* @param money 具体金额
*/
pulbic abstract void handle(int money);
}

public class GroupLeader extends Leader {
@Override
public int limit() {
return 1000;
}
@Override
public void handle(int money) {
Ststen.out.println("组长批复报销" + money + "元");
}
}

public class Director extends Leader {
@Override
public int limit() {
return 5000;
}
@Override
public void handle(int money) {
Ststen.out.println("主管批复报销" + money + "元");
}
}

public class Manager extends Leader {
@Override
public int limit() {
return 10000;
}
@Override
public void handle(int money) {
Ststen.out.println("经理批复报销" + money + "元");
}
}

public class Boss extends Leader {
@Override
public int limit() {
return Integer.MAX_VALUE;
}
@Override
public void handle(int money) {
Ststen.out.println("老板批复报销" + money + "元");
}
}

// 小民从组长开始发起请求申请报账
public class XiaoMin {
public static void main(String[] args) {
// 构造各个领导对象
GroupLeader groupLeader = new GroupLeader();
Director director = new Director();
Manager manager = new Manager();
Boss boss = new Boss();

// 设置上一级领导处理者对象
groupLeader.nextHandler = director;
director.nextHandler = manager;
manager.nextHandler = boss;

// 发起报账申请
groupLeader.handleRequest(50000);
}
}

责任链模式实战

Android 中我们可以借鉴责任链模式的思想来优化 BroadcastReceiver 使之成为一个全局的责任链处理者。

我们知道 Broadcast 可以被分为两种:

  • Normal Broadcast:普通广播,异步广播,发出时可被所有的接收者收到。
  • Ordered Broadcast:有序广播,依优先级依次传播的,直到有接收者将其终止或所有接收者都不终止它。

有序广播这一特性与我们的责任链模式很相近,通过它可实现一种全局的责任链事件处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// 具体的实现思路是,通过 Intent 的限制值来限定最终的广播权归谁所有
public class FirstReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 获取 Intent 中附加的限制值
int limit = intent.getIntExtra("limit", -1001);

// 如果限定值等于 1000 则处理,否则继续转发给下一个 Receiver
if( 1000 == limit ) {
// 获取 Intent 中附加的字符串消息并 Toast
String msg = intent.getStringExtra("msg");
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
// 终止广播
abortBroadcast();
} else {
// 添加信息发送给下一个 Receiver
Bundle b = new Bundle();
b.putString("new", "Message from FirstReceiver");
setResultExtras(b);
}
}
}

public class SecondReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 获取 Intent 中附加的限制值
int limit = intent.getIntExtra("limit", -1001);

// 如果限定值等于 100 则处理,否则继续转发给下一个 Receiver
if( 100 == limit ) {
// 获取 Intent 中附加的字符串消息
String msg = intent.getStringExtra("msg");

// 获取上一个 Receiver 增加的消息
Bundle b = getResultExtras(true);
String str = b.getString("new");

Toast.makeText(context, msg + str, Toast.LENGTH_SHORT).show();
// 终止广播
abortBroadcast();
} else {
// 添加信息发送给下一个 Receiver
Bundle b = new Bundle();
b.putString("new", "Message from FirstReceiver");
setResultExtras(b);
}
}
}

public class ThirdReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 获取 Intent 中附加的限制值
int limit = intent.getIntExtra("limit", -1001);


// 如果限定值等于 10 则处理,否则继续转发给下一个 Receiver
if( 10 == limit ) {
// 获取 Intent 中附加的字符串消息
String msg = intent.getStringExtra("msg");

// 获取上一个 Receiver 增加的消息
Bundle b = getResultExtras(true);
String str = b.getString("new");

Toast.makeText(context, msg + str, Toast.LENGTH_SHORT).show();
// 终止广播
abortBroadcast();
} else {
// 添加信息发送给下一个 Receiver
Bundle b = new Bundle();
b.putString("new", "Message from FirstReceiver");
setResultExtras(b);
}
}
}

// 客户端实现
public class OrderActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_order);

Button btnSend = (Button) findViewById(R.id.order_send_btn);
btnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent();
i.setAction("com.aigestudio.action.ORDER_BROADCAST");
i.putExtra("limit", 100);
i.putExtra("msg", "Message from OrderActivity");
sendOrderedBroadcast(i, null);
}
});
}
}

// 这里我们设置 limit = 100,即只有 SecondReceiver 才会处理它。

总结

  • 责任链模式的优缺点
    • 优点 - 对请求者和处理者关系解耦,提高代码灵活性。
    • 缺点 - 递归调用。特别是处理者太多,那么遍历定会影响性能。

解释器模式

解释器模式的定义

  • 行为型设计模式。
  • 概念:给定一个语言,定义它的 文法 的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
  • 文法:例如我们熟悉的「主谓宾结构」,通过下述短语举例,我们可把短语抽象看作:I am a/an [noun.]
主语 谓语 宾语
I am a designer
I am a teacher
  • 再举例:假设有如以 ab 开头 ef 结尾,中间排列 N(N>=0) 个 cd 的字符串。

    abcdcd…cdef

    在计算机科学中,我们将上述字符串中的 “a”、“b”、“c”、“d”、“e” 和 “f” 这 6 个字符称为一种形式语言的 字符表

    而这些字符组成的集合,如 “abcdcd…cdef” 这样由字符表构成的字符串则称为 形式语言。注意这里的语言不是文法。

    假设定义一个符号 S,从符号 S 出发推导上述字符串,即可得到如下推导式:

    1
    2
    S ::= abA*ef  
    A ::= cd
  • ::== 称为推导;
  • * 表示闭包,上述推导式中意思是,符号 A 可以有 0 或 N 个重复;
  • 非终结符号:SA 则称非终结符号,即它们能推导出式子右边的表达式;
  • 终结符号:”pqmn”,“ab”,“ef”,即无法再推导;

解释器模式的使用场景

  • 某个 简单语言 需要解释执行且可将该语言中的语句表示为 抽象语法树 时可考虑使用解释器模式。

    如:有非终结符号 p+q+m-n,即该数学表示式可表示为一棵抽象语法树。如图 2-19 所示。

p+q+m-n的抽象语法树

图 2-19 p+q+m-n 的抽象语法树
  • 某些特定的领域出现不断重复的问题时,可将该领域的问题转化为一种语法规则下的语句,然后构建解释器来解释该语句。

    英文字母的大小写转换;阿拉伯数字转为中文的数字…
    即它们都是一个个终结符,不同的只是具体内容。

解释器模式的 UML 类图

解释器模式的 UML 类图如图 2-20 所示。

解释器模式UML类图

图 2-20 解释器模式 UML 类图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public abstract class AbstractExpression {
/**
* 抽象的解析方法
* @param ctx 上下文环境对象
*/
public abstract void interpret(Context ctx);
}

// 终结符表达式
public class TerminalExpression extends AbstractExpression {
@Override
public abstract void interpret(Context ctx) {
// 实现文法中与终结符有关的解释操作
}
}

// 非终结符表达式
public class NonterminalExpression extends AbstractExpression {
@Override
public abstract void interpret(Context ctx) {
// 实现文法中与非终结符有关的解释操作
}
}

public class Context {
// 包含解释器之外的全局信息
}

public class Client {
public static void main(String[] args) {
// 根据文法对特定句子构建抽象语法树后解释
}
}

总结

  • 解释模式的优缺点
    • 优点 - 灵活的扩展性,即我们想对文法规则进行扩展延伸时,只需增加相应的非终结符解释器,并在构建抽象语法树时,使用到新增的解释器对象进行具体的解释即可。
    • 缺点
      1) 对于每一条文法对应至少一个解释器,其会生成大量的类,导致后期维护困难;
      2) 构建其抽象语法树会显得异常繁琐,甚至可能出现需要构建多棵抽象语法树的情况。

命令模式

命令模式的定义

  • 行为型设计模式。
  • 介绍:将一系列的方法调用封装,用户只需调用一个方法执行,那么所有这些被封装的方法就会被挨个执行调用。
  • 定义:
    • 将一个请求封装成一个对象,从而让用户使用不同的请求把客户端参数化。
    • 对请求排队或者记录请求日志,以及支持可撤销的操作。

命令模式的使用场景

  • 需要抽象出待执行的动作,然后以参数的形式提供处理 (类似过程设计中的回调机制)。
  • 在不同的时刻指定、排列和执行请求。一个命令对象可以有与初始请求无关的生存期。
  • 需要支持取消操作。
  • 需要支持事务操作。
  • 支持修改日志功能,若系统崩溃,这些修改可重做一遍。

命令模式的 UML 类图

命令模式的 UML 类图如图 2-21 所示。

命令模式UML类图

图 2-21 命令模式 UML 类图

Receiver:接收者角色
该类负责具体实施或执行一个请求,通俗地说,执行具体逻辑的角色。

Command:命令角色
定义所有具体命令类的抽象接口。

ConcreteCommand:具体命令角色
该类实现了 Command 接口,在 execute() 方法中调用接收者角色的相关方法,在接收者和命令执行的具体行为之间加以弱耦和。

Invoker:请求者角色
该类的职责就是调用命令对象执行具体的请求,相关的方法我们称为行动方法。

这里其实大家可以看到,命令模式的应用其实可用一句话概述,就是将行为调用者与实现者解耦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 接收者类
public class Receiver {
/**
* 真正执行具体命令逻辑的方法
*/
public void action() {
System.out.println("执行具体操作");
}
}

// 抽象命令接口
public interface Command {
/**
* 执行具体操作的命令
*/
void execute();
}

// 具体命令类
public class ConcreteCommand implements Command {
private Receiver receiver; // 持有一个对接受者对象的引用
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
// 调用接收者的相关方法来执行具体逻辑
receiver.action();
}
}

// 请求者类
public class Invoker {
private Command command; // 持有一个对应命令对象的引用

public Invoker() {
this.command = command;
}

public void action() {
// 调用具体命令对象的相关方法,执行具体命令
command.execute();
}
}

// 客户端实现
public class Client {
public static void main(String[] args) {
// 构造一个接受者对象
Receiver receiver = new Receiver();
// 根据接收者对象构造一个命令对象
Command command = new ConcreteCommand(receiver);
// 根据具体的对象构造请求者对象
Invoker invoker = new Invoker(command);
// 执行请求方法
invoker.action();
}
}

命令模式的简单实现

这里以古老的俄罗斯方块游戏为例,在命令模式下如何操控俄罗斯方块变换。游戏中含有 4 个按钮,即上下左右。设定玩游戏的人相当于我们的客户端,游戏上的 4 个按钮相当于请求者,而执行具体按钮命令的逻辑方法可看作命令角色。

便于理解,本示例的 UML 类图如图 2-22 所示。

命令模式实现俄罗斯方块游戏

图 2-22 命令模式实现俄罗斯方块游戏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// 接收者角色
public class TetrisMachine {

public void toLeft() { // 真正处理 “向左” 操作的逻辑代码
System.out.println(“向左”);
}

public void toRight() { // 真正处理 “向右” 操作的逻辑代码
System.out.println(“向右”);
}

public void fastToBottom() { // 真正处理 “快速落下” 操作的逻辑代码
System.out.println(“快速落下”);
}

public void transform() { // 真正处理 “改变形状” 操作的逻辑代码
System.out.println(“改变形状”);
}
}

// 命令者抽象
public interface Command {
/**
* 命令执行方法
*/
void execute();
}

// 具体命令者:向左移的命令类
public class LeftCommand implements Command {
// 持有一个接收者俄罗斯方块游戏对象的引用
private TetrisMachine machine;
public LeftCommand(TetriMachine machine){
this.machine = machine;
}
@Override
public void execute() {
// 调用游戏机里的具体方法执行操作
machine.toLeft();
}
}

// 具体命令者:向右移的命令类
public class RightCommand implements Command {
// 持有一个接收者俄罗斯方块游戏对象的引用
private TetrisMachine machine;
public RightCommand(TetriMachine machine){
this.machine = machine;
}
@Override
public void execute() {
// 调用游戏机里的具体方法执行操作
machine.toRight();
}
}

// 具体命令者:快速落下的命令类
public class FallCommand implements Command {
// 持有一个接收者俄罗斯方块游戏对象的引用
private TetrisMachine machine;
public FallCommand(TetriMachine machine){
this.machine = machine;
}
@Override
public void execute() {
// 调用游戏机里的具体方法执行操作
machine.fastToBottom();
}
}

// 具体命令者:改变形状的命令类
public class TransformCommand implements Command {
// 持有一个接收者俄罗斯方块游戏对象的引用
private TetrisMachine machine;
public TransformCommand(TetriMachine machine){
this.machine = machine;
}
@Override
public void execute() {
// 调用游戏机里的具体方法执行操作
machine.transform();
}
}

// 请求者类:命令由按钮发起
public class Buttons {
private LeftCommand leftCommand; // 向左移动的命令对象引用
private RightCommand rightCommand; // 向右移动的命令对象引用
private FallCommand fallCommand; // 快速落下的命令对象引用
private TransformCommand transformCommand; // 变换形状的命令对象引用

/**
* 设置向左移动的命令对象
* @param leftCommand 向左移动的命令对象
*/
public void setLeftCommand(LeftCommand leftCommand) {
this.leftCommand = leftCommand;
}

/**
* 设置向右移动的命令对象
* @param rightCommand 向右移动的命令对象
*/
public void setRightCommand(RightCommand rightCommand) {
this.rightCommand = rightCommand;
}

/**
* 设置快速落下的命令对象
* @param fallCommand 向左移动的命令对象
*/
public void setFallCommand(FallCommand fallCommand) {
this.fallCommand = fallCommand;
}

/**
* 设置变换形状的命令对象
* @param transformCommand 向左移动的命令对象
*/
public void setTransformCommand(TransformCommand transformCommand) {
this.leftCommand = leftCommand;
}

public void toLeft() { // 按下按钮向左移动
leftCommand.execute();
}

public void toRight() { // 按下按钮向右移动
rightCommand.execute();
}

public void fall() { // 按下按钮快速落下
fallCommand.execute();
}

public void transform() { // 按下按钮改变形状
transformCommand.execute();
}
}

// 客户端实现
public class Player {
public static void main(String[] args) {
// 首先要有俄罗斯方块游戏
TetrisMachine machine = new TetrisMachine();

// 根据游戏我们构造 4 种命令
LeftCommand leftCommand = new LeftCommand(machine);
RightCommand rightCommand = new RightCommand(machine);
FallCommand fallCommand = new FallCommand(machine);
TransformCommand transformCommand = new TransformCommand(machine);

// 按钮可以执行不同的命令
Buttons buttons = new Buttons();
buttons.setLeftCommand(leftCommand);
buttons.setRightCommand(rightCommand);
buttons.setFallCommand(fallCommand);
buttons.setTransformCommand(transformCommand);

// 具体按下哪个按钮玩家决定
buttons.toLeft();
buttons.toRight();
buttons.fall();
buttons.transform();
}
}

对于大部分开发者来说,更愿意接受的形式:

1
2
3
4
5
6
7
8
9
TetrisMachine machine = new TetrisMachine();

// 实现怎样的控制方式,直接调用相关函数
// machine.toLeft();
// machine.toRight();
// machine.fastToBottom();
// machine.transform();

machine.toLeft();

调用逻辑做得如此复杂,其实是为了开发起来方便,即每次我们增加或修改游戏功能只需修改 TetrisMachine 类即可。
当然,其实这样做是有原因的,即设计模式种有一条重要的原则:对修改关闭对扩展开放。具体好处是:

  • 如修改功能、代码的具体逻辑,以上例为例,即修改 TetrisMachine 类即可。
  • 此外,命令模式还可以实现命令记录的功能,如在 Buttons 里使用数据结构存储执行过的命令对象,需要时可恢复。

总结

  • 命令模式的优缺点
    • 优点 - 更灵活的控制性以及更好的扩展性;更弱的耦合性。
    • 缺点 - 类的膨胀,大量衍生类的创建。

观察者模式

观察者模式的定义

定义对象间一种 一对多依赖关系,使得每当一个对象改变状态,则所有依赖与它的对象都会得到通知并被自动更新。

观察者模式的使用场景

  • 关联行为场景,即关联行为是可拆分的,而不是“组合”关系。
  • 事件多级触发场景。
  • 跨系统的消息交换场景,如消息队列、事件总线的处理机制。

观察者模式的 UML 类图

观察者模式的 UML 类图如图 2-23 所示。

观察者模式的UML类图

图 2-23 观察者模式的 UML 类图

观察者模式实战

总结

  • 观察者模式主要的作用就是对象解耦,将观察者与被观察者完全隔离,只依赖于 Observer 和 Obserable 抽象。

如:ListView 就是运用了 Adapter 和观察者模式,使之它的扩展性、灵活性增强,且耦合度却很低。

  • 观察者模式的优缺点
    • 优点
      1) 增强系统灵活性、可扩展性;
      2) 将观察者与被观察者之间是抽象耦合,应对业务变换。
    • 缺点 - 应用观察者模式,需考虑开放效率和运行效率问题 (一般考虑采用异步的方式)

备忘录模式

备忘录模式的介绍

  • 行为型设计模式。
  • 用于保存对象当前状态,并在之后可再次恢复到此状态。
  • 保证被保存的对象状态不能被对象从外部访问,目的是为了保护好被保存对象状态的完整性及内部实现不向外暴露。

备忘录模式的定义

在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原生保存的状态。

备忘录模式的 UML 类图

备忘录模式的 UML 类图如图 2-24 所示。

备忘录模式的UML类图

图 2-24 备忘录模式的 UML 类图

Originator
负责创建一个备忘录,可以记录、恢复自身的内部状态。同时 Originator 还可以根据需要决定 Memoto 存储自身的哪些内部状态。

Memoto
备忘录角色,用于储存 Originator 的内部状态,并且可以防止 Originator 以外的对象访问 Memoto。

Caretaker
负责储存备忘录,不能对备忘录的内容进行操作和访问,只能够将备忘录传递给其他对象。

备忘录模式的简单实例

对于备忘录模式来说,比较贴切的场景应该是游戏中的存档功能,该功能就是将游戏进度存储到本地文件系统或者数据库中,下次再次进入时从本地加载进度,使得玩家能够继续上一次的游戏之旅。下面我们以“使命召唤”这款游戏为例简单演示备忘录模式的实现。

首先我们建立游戏类 CallOfDuty,备忘录类 Memoto 和负责管理 Memoto 的 CareTaker 类。玩游戏到某个节点对游戏进行存档,然后退出游戏,再重新进入时从存档中读取进度,并且进入存档时的进度。

便于理解,本示例的 UML 类图如图 2-25 所示。

使命召唤的存储功能实现

图 2-25 使命召唤的存储功能实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// ”使命召唤“ 游戏 ( 简化的数据模型,仅供简单演示 )
public class CallOfDuty {
private int mCheckPoint = 1;
private int mLifeValue = 100;
private String mWeapon = "沙漠之鹰";

public void play() { // 玩游戏
// 忽略实现细节
}

public void quit() { // 退出游戏
// 忽略实现细节
}

public Memoto createMemoto() { // 创建备忘录
Memoto memoto = new Memoto();
memoto.mCheckPoint = mCheckPoint;
memoto.mLifeValue = mLifeValue;
memoto.mWeapon = mWeapon;
return memoto;
}

public void restore(Memoto memoto) { // 恢复游戏
this.mCheckPoint = memoto.mCheckPoint;
this.mLifeValue = memoto.mLifeValue;
this.mWeapon = memoto.mWeapon;
System.out.println("恢复后的游戏属性: " + this.toString());
}

@Overrride
public String toString() {
return "CallOfDuty[mCheckPoint=..., mLifeValue=..., mWeapon=...]"
}
}

// 备忘录类
public class Memoto {
public int mCheckPoint;
public int mLifeValue;
public String mWeapon;
}

// Caretaker,负责管理 Memoto
public class Caretaker {
Memoto memoto = null;

public void archive(Memoto memoto) { // 存档
this.memoto = memoto;
}

public Memoto getMemoto() { // 读取存档
return memoto;
}
}

// 客户端实现
public class Client {
public static void main(String[] args) {
CallOfDuty game = new CallOfDuty();

// Step.01 游戏开始
game.play();

// Step.02 游戏存档
Caretaker caretaker = new Caretaker();
caretaker.archive( game.createMemoto() );

// Step.03 退出游戏
game.quit();

// Step.04 恢复游戏
CallOfDuty newGame = new CallOfDuty();
newGame.restore( caretaker.getMemoto() );
}
}

总结

  • 备忘录模式是在不破坏封装的条件下,通过备忘录对象 (Memoto) 存储另外一个对象内部状态的快照,在需求的时候把对象还原到存储的状态。
  • 备忘录的优缺点
    • 优点 - 恢复状态机制;信息封装
    • 缺点 - 消耗内存

迭代器模式

迭代器模式的介绍

  • 又称游标 (Cursor) 模式,行为型设计模式。
  • 迭代器模式源于对容器的访问,若我们将遍历的方法封装在容器中,则存在问题:

    • 不仅维护自身内部数据且要对外提供遍历的接口方法。
    • 不能对同一个容器同时进行多个遍历操作。
    • 不提供遍历方法,而让使用者自行实现,必暴露内部细节。

      解决方案:在客户访问类与容器直接插入一个第三者 迭代器

迭代器模式的定义

提供一种方法顺序访问一个容器对象中的各个元素,而不需暴露该对象内部细节。

迭代器模式的 UML 类图

迭代器模式的 UML 类图如图 2-26 所示。

迭代器模式的UML类图

图 2-26 迭代器模式的 UML 类图

Iterator:迭代器接口
迭代器接口,负责定义、访问和遍历元素的接口。

ConcreteIterator:具体迭代器类
具体迭代器类,实现迭代器接口,并记录遍历的当前位置。

Aggregate:容器接口
容器接口,负责提供创建具体迭代器角色的接口。

ConcreteIterator:具体容器类
具体容器类,具体迭代器角色与该容器相关联。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// 迭代器接口
public interface Iterator<T> {
/**
* 是否还有下一个元素
* @return true 表示有,false 表示没有
*/
boolean hasNext();

/**
* 返回当前位置的元素并将位置移至下一位
* @return T 返回当前位置的元素
*/
T next();
}

// 具体迭代器类
public class ConcreteIterator<T> implements Iterator<T> {
private List<T> list = new ArrayList<T>();
private int cursor = 0;

public ConcreteIterator(List<T> list) {
this.list = list;
}

@Override
public boolean hasNext() {
return cursor != list.size();
}

@Override
public T next() {
T obj = null;
if( this.hasNext()) {
obj = this.list.get(cursor++);
}
return obj;
}
}

// 容器接口
pulbic interface Aggregate<T> {
/**
* 添加一个元素
* @param obj 元素对象
*/
void add(T obj);

/**
* 移除一个元素
* @param obj 元素对象
*/
void remove(T obj);

/**
* 获取容器的迭代器
* @return 迭代器对象
*/
Iterator<T> Iterator();
}

// 具体容器类
public class ConcreteAggregate<T> implements Aggregate<T> {
private List<T> list = new ArrayList<T>();

@Override
public void add() {
list.add(obj);
}

@Override
public void remove() {
list.remove(obj);
}

@Override
public Iterator<T> iterator() {
return new ConcreteIterator<T>(list);
}
}

// 客户端实现
public class Client {
public static void main(String[] args) {
Aggregate<String> aggregate = new ConcreteAggregate<>();

aggregate.add("Aige ");
aggregate.add("Studio\n");
aggregate.add("SM ");
aggregate.add("Brother\n");

Iterator<String> iterator = aggregate.iterator();
while( iterator.hasNext() ) {
System.out.println(iterator.next());
}
}
}

总结

  • 迭代器模式的优缺点
    • 优点 - 支持以不同的方式遍容器对象,也可以有多个遍历,弱化了容器类与遍历算法之间的关系。
    • 缺点 - 类文件的增加。
  • 当然几乎每一种高级语言都有相应的内置迭代器实现,故本章的内容在于了解而非应用。

模板方法模式

模板方法模式的介绍

若我们知道一个算法所需的关键步骤,并确定了这些步骤的执行顺序,但某些步骤的具体实现是未知的,或实现是随着环境变化的。

例如,执行程度的流程大致为:
Step.01:检查代码正确性
Step.02:链接相关类库
Step.03:编译
Step.04:执行程序
即上述步骤不一样 ( 实现细节 ),但执行流程是固定的。

模板方法模式的定义

  • 定义一个操作的 算法框架
  • 将步骤延迟到子类,使子类不改变算法结构即可重定义该算法的某些特定步骤。

模板方法模式的使用场景

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

    钩子函数:普通的抽象类多态,即它在模板方法模式中提供了改变原始逻辑的空间。

模板方法模式的 UML 类图

模板方法模式的 UML 类图如图 2-27 所示。

模板方法模式的UML类图

图 2-27 模板方法模式的 UML 类图

总结

  • 模板方法模式:流程封装,即把某个固定的流程封装到一个 final 函数中,并让子类能够定制这个流程中的某些或者所有步骤。

    要求父类提供共同代码,即提高代码复用性、可扩展性。

  • 模板方法的优缺点

    • 优点
      1) 封装不变部分,扩展可变部分。
      2) 提取公共部分代码。
    • 缺点:代码阅读有难度?

访问者模式

访问者模式的介绍

  • 数据操作数据结构 分离的设计模式。
  • 软件系统拥有由许多对象构成的对象结构,这些对象类拥有一 accept() 方法接受访问者对象访问。
  • 访问者是一接口,拥有一 visit() 方法对访问到的对象结构中不同类型的元素作出不同的处理。

    • 在对象结构的一次访问中,遍历整个对象结构,对每个元素实施 accept() 方法。
    • 每一元素的 accept() 方法会调用访问者的 visit() 方法,即访问者可针对对象结构设计不同的访问类来完成不同操作。

访问者模式的定义

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

访问者模式的使用场景

  • 对一对象结构中的对象进行不同且不相关的操作。
  • 需避免操作“污染”对象的类。
  • 增加新操作是不修改这些类。
  • 对象结构稳定,但经常需在对象结构上定义新操作。

访问者模式的 UML 类图

访问者模式的 UML 类图如图 2-28 所示。

访问者模式的UML类图

图 2-28 访问者模式的 UML 类图

访问者模式的简单示例

公司给员工进行业绩考核,评定由公司高层负责。但不同领域的管理人员对与员工的评定标准不一样。即我们把员工分为工程师和经理,评定员工分为 CTO 和 CEO。

假定 CTO 关注工程师的代码量,经理的新产品数量;CEO 关注工程师的 KPI,经理的 KPI 及新产品数量。

便于理解,本示例的 UML 类图如图 2-29 所示。

公司员工业绩考核

图 2-29 公司员工业绩考核
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// 员工基类
public abstract class Staff {
public String name;
public int kpi; // 员工 KPI
public Staff(String name) {
this.name = name;
kpi = new Random().nextInt(10); // 随机生成 10 内的值.
}

// 接受 Visitor 的访问
public abstract void accept(Visitor visitor);
}

// 工程师类型
public class Engineer extends Staff {
private int codes; // 代码量

public Engineer (String name) {
super(name);
codes = new Random().nextInt(100000)
}

@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}

public int getCodeLines() { // 工程师一年内写的代码量
return codes;
}
}

// 经理类型
public class Manager extends Staff {
private int products; // 产品数量

public Manager (String name) {
super(name);
products = new Random().nextInt(10);
}

@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}

public int getProducts() { // 经理一年内做的产品数量
return products;
}
}

public interface Visitor {
// 访问工程师类型
public void visit(Engineer engineer);

// 访问经理类型
public void visit(Manager leader);
}

// CEO 访问者
public class CEOVisitor implements Visitor {
@Override
public void visit(Engineer engineer) {
System.out.println("Name:..., KPI:...");
}

@Override
public void visit(Manager mgr) {
System.out.println("Name:..., KPI:..., Products:...");
}
}

// CTO 访问者
public class CTOVisitor implements Visitor {
@Override
public void visit(Engineer engineer) {
System.out.println("Name:..., Codes:...");
}

@Override
public void visit(Manager mgr) {
System.out.println("Name:..., Products:...");
}
}

public class BusinessReport {
List<Staff> mStaffs = new LinkedList<Staff>();

public BusinessReport() {
mStaffs.add(new Manager("Manager Wang."));
mStaffs.add(new Manager("Manager Lin."));
mStaffs.add(new Manager("Engineer Kael."));
mStaffs.add(new Manager("Engineer Chaos."));
}

/**
* 为访问者展示报表
* @param visitor 公司高层,如 CEO、CTO
*/
public void showReport(Visitor visitor) {
for( Staff staff : mStaffs ) {
staff.accept(visitor);
}
}
}

// 客户端实现
public class Client {
public static void main(String[] args) {
BusinessReport report = new BusinessReport();
report.showReport( new CEOVisitor() );
report.showReport( new CTOVisitor() );
}
}

总结

  • 对象结构足够稳定,需在对象结构上经常定义新操作,且需对对象结构中的对象进行很多不同且不相关的操作,考虑访问者模式。
  • 访问者模式的优缺点
    • 优点
      1) 单一职责原则,即各角色职责分离。
      2) 数据结构和作用于该结构上的操作解耦。
    • 缺点
      1) 具体元素对访问者公布细节。
      2) 具体元素变更导致修改成本大。
      3) 违反依赖倒置原则,即为了“区别对待”而依赖了具体类,没有依赖抽象,如上例中的 Engineer 与 Manager。

中介者模式

中介者模式的介绍

  • 又称为调节者模式或调停者模式,行为型设计模式。

中介者模式的定义

  • 包装一系列对象相互作用的方式,使这些对象不必相互明显作用。
  • 将多对多的相互作用转化为一对多的相互作用。
  • 将对象的行为和协作抽象化。

中介者模式的使用场景

  • 对象间交互操作较多且每个对象的行为操作都依赖彼此时,为防止修改其中一对象的行为同时涉及修改很多其他对象的行为。
  • 该模式将对象之间的多对多关系变成一对多关系。
  • 中介者对象将系统从网状结构变成以调停者为中心的星形结构,以降低系统复杂性,提高可扩展性作用。

中介者模式的 UML 类图

中介者模式的 UML 类图如图 2-30 所示。

中介者模式的UML类图

图 2-30 中介者模式的 UML 类图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 抽象中介者
public abstract class Mediator {
protected ConcreteColleagueA colleagueA; // 具体同事类 A
protected ConcreteColleagueB colleagueB; // 具体同事类 B

// 抽象中介方法、子类实现
public abstract void method();

public void setColleagueA(ConcreteColleagueA colleagueA) {
this.colleagueA = colleagueA;
}

public void setColleagueB(ConcreteColleagueB colleagueB) {
this.colleagueB = colleagueB;
}
}

// 具体中介者
public class ConcreteMediator extends Mediator {
@Override
public void method() {
colleagueA.action();
colleagueB.action();
}
}

// 抽象同事
public abstract class Colleague {
protected Mediator mediator; // 中介者对象

public Colleague(Mediator mediator) {
this.mediator = mediator;
}

// 同事角色的具体行为,由子类去实现
public abstract void action();
}

// 具体同事 A
public class ConcreteColleagueA extends Colleague {
public ConcreteColleagueA(Mediator mediator) {
super(mediator);
}

@Override
public void action() {
System.out.println("Colleague A 将信息递交给中介者处理.");
}
}

// 具体同事 B
public class ConcreteColleagueB extends Colleague {
public ConcreteColleagueB(Mediator mediator) {
super(mediator);
}

@Override
public void action() {
System.out.println("Colleague B 将信息递交给中介者处理.");
}
}

中介者模式的简单实现

便于理解,本示例的 UML 类图如图 2-31 所示。

电脑内部硬件的交互

图 2-31 电脑内部硬件的交互

中介者模式就是用来协调多个对象之间的交互,就像上例中的主板,没有主板这个中介者,那么电脑里的每一个零部件都要与其他零部件建立关联。

比如 CPU 要与内存交互,与显卡交互以及与 IO 设备交互,那么这样一来就会构成一个错综复杂的网状图,而中介者模式即将网状图变成一个结构清晰的星形图。

中介者模式实战

协调多个交互的对象,Android 中这么多形形色色控件也算是交互对象。其中社交、网商等应用的用户登录模块,账号框、密码框、登录按钮之间的相互制约、联系,正是中介者模式的表现,具体的实例样式可自行尝试。

总结

  • 中介者模式的优缺点
    • 优点 - 将网状般的依赖关系转化为以中介者为中心的星形结构,即使用中介者模式可对这种依赖关系进行解耦使逻辑结构清晰。
    • 缺点 - 若几个类间的依赖关系并不复杂,使用中介者模式反而会使原本不复杂的逻辑结构变得复杂。

代理模式

代理模式的定义

  • 结构型设计模式。
  • 为其他对象提供一种代理以控制对这个对象的访问。

代理模式的使用场景

  • 无法或不想直接访问某个对象或访问某对象存在困难。
  • 为保证客户端使用的透明性,委托对象与代理对象需实现相同的接口。

代理模式的 UML 类图

代理模式的 UML 类图如图 2-32 所示。

代理模式的UML类图

图 2-32 代理模式的 UML 类图

Subject:抽象主题类
该类主要职责是声明真实主题与代理的共同接口方法,其可是抽象类或接口。

RealSubject:真实主题类
该类也被称为被委托类或者被代理类,该类定义了代理所表示的真实对象,由其执行具体的业务逻辑方法,而客户类则通过代理类间接地调用真实主题类中定义的方法。

ProxySubject:代理类
该类也称为委托类或者代理类,该类持有一个对真实主题类的引用,在其所实现的接口方法中调用真实主题类中相应的接口方法执行,以起到代理的作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 抽象主题类
public abstract class Subject {
// 一个普通的业务方法
public abstract void visit();
}

// 实现抽象主题的真实主题类
public class RealSubject extends Subject {
public void visit() {
System.out.println("Real Subjetc!");
}
}

// 代理类
public class ProxySubject extends Subject {
private RealSubject mSubject; // 持有真实主题的引用

public ProxySubject(RealSubject mSubject) {
this.mSubject = mSubject;
}

@Override
public void visit() {
mSubject.visit();
}
}

// 客户端实现
public class Client {
public static void main(String args) {
// 构造一个真实主题对象
RealSubject real = new RealSubject();
// 通过真实主题对象构造一个代理对象
ProxySubject proxy = new ProxySubject(real);
// 调用代理的相关方法
proxy.visit();
}
}

代理模式的简单实现

以生活中常有的例子,老板拖欠工资甚至克扣工资的情况,而最恰当的途径就是通过法律诉讼解决问题。一旦选择走法律途径解决该纠纷,那么不可避免地需请一个律师来作为自己的诉讼代理人。

便于理解,本示例的 UML 类图如图 2-33 所示。

律师作为诉讼代理人

图 2-33 律师作为诉讼代理人
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 诉讼接口类
public interface ILawsuit {
void submit(); // 提交申请
void burden(); // 进行举证
void defend(); // 开始辩护
void finish(); // 诉讼完成
}

// 具体诉讼人
public class XiaoMin implements ILawsuit {
@Override
public void submit() {
System.out.println("老板拖欠工资,特此申请仲裁!");
}
@Override
public void burden() {
System.out.println("这是合同书和过去一年的银行工资流水!");
}
@Override
public void defend() {
System.out.println("证据确凿!");
}
@Override
public void finish() {
System.out.println("诉讼成功!");
}
}


// 代理律师
public class Lawyer implements ILawsuit {
private ILawsuit mLawsuit; // 持有一个具体被代理者的引用

public Lawyer(ILawsuit lawsuit) {
mLawsuit = lawsuit;
}
@Override
public void submit() {
mLawsuit.submit();
}
@Override
public void burden() {
mLawsuit.burden();
}
@Override
public void defend() {
mLawsuit.defend();
}
@Override
public void finish() {
mLawsuit.finish();
}
}

// 客户端实现
public class Client {
public static void main(String[] args) {

ILawsuit xiaomin = new XiaoMin();
ILawsuit lawyer = new Lawyer(xiaomin);

lawyer.submit();
lawyer.burden();
lawyer.defend();
lawyer.finish();
}
}
  • 静态代理
    代码由程序员自己或者通过一些自动化工具生成固定的代码再对其进行编译,即说在我们的代码运行前代理类的 Class 编译文件就已经存在。上述例子即为静态代理的实现模式。

  • 动态代理
    通过反射机制动态地生成代理者的对象,即我们在编译阶段不需要知道代理者是谁,代理谁我们将在执行阶段决定。Java 提供了便捷的动态代理接口 InvocationHandler。
    同样,以动态代理方式实现上述例子,本示例的 UML 类图如图 2-34 所示。

动态代理的实现案例

图 2-34 动态代理的实现案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class DynamicProxy implements IncocationHandler {
private Object obj; // 被代理的类引用

public DynamicProxy(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(
Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(obj, args);
return result;
}
}

// 客户端实现
public class Client {
public static void main(String[] args) {
ILawsuit xiaomin = new XiaoMin();
DynamicProxy proxy = new DynamicProxy(xiaomin);

// 获取被代理类小民的 ClassLoader
ClassLoader loader = xiaomin.getClass().getClassLoader();
// 动态构造一个代理者律师
ILawsuit lawyer =(ILawsuit) Proxy.newProxyInstance(loader,
new Class[] {ILawsuit.class}, proxy);
)
// 律师提交诉讼申请
lawyer.submit();
// 律师进行举证
lawyer.burden();
// 律师代替小民进行辩护
lawyer.defend();
// 完成诉讼
lawyer.finish();
}
}

总结

  • 代理模式的优缺点
    • 优点 - 代理模式可看作一种针对性优化。
    • 缺点 - 暂没有明显的缺点。

组合模式

组合模式的介绍

  • 又称部分整体模式,结构型设计模式。
  • 它将一组相似的对象看作一个对象处理,并根据一个树状结构来组合对象,然后提供一个统一的方法去访问相应对象。

    例如公司组织结构的树状图,如图 2-35 所示。

    公司组织结构的树状图

    图 2-35 公司组织结构的树状图

    在组合模式中,我们将这样的一个拥有分支的节点称之为枝干构件,位于树状结构顶部的枝干结构比较特殊,我们称为根结构件,因其为整个树状图的始端。同样对于像行政部和研发部这样没有分支的结构,我们称之为叶子结构,这样的一个结构就是组合模式的雏形。如图 2-36 所示。

    组合模式的雏形

    图 2-36 组合模式的雏形

组合模式的定义

将对象组合成 树状结构 以表示 “部分-整体” 的 层次结构,使得用户对单个对象和组合对象的使用具有一致性。

组合模式的使用场景

  • 表示对象的 部分-整体 层次结构时。
  • 从一个整体中能够独立出部分模块或功能的场景。

组合模式的 UML 类图

组合模式的 UML 类图如图 2-37 所示。

组合模式的UML类图

图 2-37 组合模式的 UML 类图

上述所讲与依赖倒置原则相违背,既然是面向接口编程,则我们就该把焦点放在接口设计上,即在 Composite 的一些实现方法定义到 Component 中。

这样,我们会得到一个不一样的组合模式,也称为安全的组合模式,该安全组合模式的 UML 类图见图 2-38 所示。

透明组合模式不管是叶子还是枝干节点都有相同的结构,那么意味着不能单一的 getChildren() 方法得到子字节的类型 (已是叶子节点),则必须在方法实现的内部进行判断。

安全的组合模式的UML类图

图 2-38 安全的组合模式的 UML 类图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// 透明的组合模式抽象根节点
public abstract class Component {
protected String name; // 节点

pulbic Component(String name) {
this.name = name;
}

/**
* 具体的逻辑方法由子类实现
*/
public abstract void doSomething();

/**
* 添加子节点
* @child 子节点
*/
public abstract void addChild(Component child);

/**
* 移除子节点
* @child 子节点
*/
public abstract void removeChild(Component child);

/**
* 获取子节点
* @param index 子节点对应下标
* @return 子节点
*/
public abstract Component getChildren(int index);
}

// 透明的组合模式具体枝干节点
public class Composite extends Component {

// 存储节点的容器
private List<Component> components = new ArrayList<>();

public Composite(String name) {
super(name);
}

@Override
public void doSomething() {
System.out.println(name);
if( null != components ) {
for(Component c : components) {
c.doSomething();
}
}
}

@Override
public void addChild(Component child) {
components.add(child);
}

@Override
public void removeChild(Component child) {
components.remove(child);
}

@Override
public Component getChildren(int index) {
return components.get(index);
}
}

// 透明的组合模式叶子节点
public class Leaf extends Component {
public Leaf(String name) {
super(name);
}

@Override
public void doSomething() {
System.out.println(name);
}

@Override
public void addChild(Component child) {
throw new UnsupportedOperationException("叶子节点没有子节点");
}

@Override
public void removeChild(Component child) {
throw new UnsupportedOperationException("叶子节点没有子节点");
}

@Override
public Component getChildren(int index) {
throw new UnsupportedOperationException("叶子节点没有子节点");
}
}

// 客户端实现
public class Client {
public static void main(String[] args) {
// 构造一个根节点
Component root = new Composite("Root");

// 构造两个枝干节点
Component branch1 = new Composite("Branch1");
Component branch2 = new Composite("Branch2");

// 构造两个叶子节点
Component leaf1 = new Leaf("Leaf1");
Component leaf2 = new Leaf("Leaf2");

// 将叶子节点添加至枝干节点中
branch1.addChild(leaf1);
branch2.addChild(leaf2);

//将枝干节点添加到根节点中
root.addChild(branch1);
root.addChild(branch2);

// 执行方法
root.doSomething();
}
}

组合模式的简单实现

在操作系统中,文件系统其实就是一种典型的组合模式例子。

具体地,文件系统中文件就是可被具体程序执行的对象,文件夹就是可存放文件和文件夹的对象。文件系统的组合模式表示如图 2-39 所示。

文件系统的组合模式表示

图 2-39 文件系统的组合模式表示

便于理解,本实例的 UML 类图如图 2-40 所示。

文件系统的组合模式实现

图 2-40 文件系统的组合模式实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// 表示文件或文件夹的抽象类
public abstract class Dir {
/**
* 声明一个 List 成员变量存储文件夹下的所有元素
*/
protected List<Dir> dirs = new ArrayList<>();
private String name; // 当前文件或文件夹的名称
public Dir(String name) {
this.name = name;
}

/**
* 添加一个文件或文件夹
* @param dir 文件或文件夹
*/
public abstract void addDir(Dir dir);

/**
* 移除一个文件或文件夹
* @param dir 文件或文件夹
*/
public abstract void rmDir(Dir dir);

/**
* 清空文件夹下所有元素
*/
public abstract void clear();

/**
* 输出文件夹目录结构
*/
public abstract void print();

/**
* 获取文件夹下所有的文件或文件夹
* @return 文件夹下所有的文件或文件夹
*/
public abstract List<Dir> getFiles();

/**
* 获取文件或文件夹的名称
* @return 文件或文件夹的名称
*/
public String getName() {
return name;
}
}

// 表示文件夹的类
public class Folder extends Dir {
public Folder(String name) {
super(name);
}
@Override
public void addDir(Dir dir) {
dirs.add(dir);
}
@Override
public void rmDir(Dir dir) {
dirs.remove(dir);
}
@Override
public void clear() {
dirs.clear();
}
@Override
public void print() {
System.out.println( getName() + "(" );
Iterator<Dir> iterator = dirs.iterator();
while( iterator.hasNext() ) {
Dir dir = iterator.next();
dir.print();
if( iterator.hasNext() ) {
System.out.println(",\t");
}
}
System.out.println(")");
}
@Override
public List<Dir> getFiles() {
return dirs;
}
}

// 表示文件的类
public class File extends Dir {
public File(String name) {
super(name);
}
@Override
public void addDir(Dir dir) {
throw new UnsupportedOperationException(
"文件类不能作为文件夹类来使用,即文件不支持添加也不支持删除");
}
@Override
public void rmDir(Dir dir) {
throw new UnsupportedOperationException(
"文件类不能作为文件夹类来使用,即文件不支持添加也不支持删除");
}
@Override
public void clear() {
throw new UnsupportedOperationException(
"文件类不能作为文件夹类来使用,即文件不支持添加也不支持删除");
}
@Override
public void print() {
System.out.println(getName());
}
@Override
public List<Dir> getFiles() {
throw new UnsupportedOperationException(
"文件类不能作为文件夹类来使用");
}
}

// 客户端实现
public class Client {
public static void main(String[] args) {
// 构造一个目录对象表示 C 盘根目录
Dir diskC = new Folder("C");

// C 盘根目录下有一个文件 FileLog.txt
diskC.addDir( new File("Filelog.txt") );

// C 盘根目录下有3个子目录 windows、perflogs 和 Program File
Dir dirWin = new File("windows");
Dir dirPerf = new File("perflogs");
Dir dirProg = new File("Program File");

// windows 目录下有文件 explorer.exe
// perflogs 目录下有文件 perflogs.txt
// Program File 目录下有文件 syslogs.txt
dirWin.addDir( new File("explorer.exe") );
dirPerf.addDir( new File("perflogs.txt") );
dirProg.addDir( new File("syslogs.txt") );

diskC.add(dirWin);
diskC.add(dirPerf);
diskC.add(dirProg);

diskC.print();
}
}

总结

  • 组合模式与解释器模式有一定的类同,两者在迭代对象时都涉及递归的调用,但组合模式所提供的属性层次结构使我们能 一视同仁 对待单个对象的对象集合。
  • 组合模式的优缺点
    • 优点
      1) 清楚定义分层次的复杂对象,表示对象的全部或部分层次,让高层模块忽略了层次差异,方便对整个层次结构进行控制。
      2) 高层模块可一致地使用一个组合结构或其中单个对象。
      3) 在组合模式中增加新的枝干结构和叶子构件很方便,无须对类库进行修改。
      4) 通过叶子对象和枝干对象的递归组合,形成复杂的树形结构,但对其控制却非常简单。
    • 缺点
      1) 新增构件时,不好对枝干中的构件类型进行限制,不能依赖类型系统来施加这些约束,因为大多数情况下他们来自相同的抽象层。
      2) 因此必须进行类型检查来实现。

适配器模式

适配器模式的介绍

  • ListView、GirdView、RecyclerView 都需要使用 Adapter。
  • 两个没有关系的类型之间交互,一种解决方法是修改各自类接口;另一种情况是使用一个 Adapter,在两种接口间创建一个 “混血儿” 接口,将两接口兼容。

适配器模式的定义

把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法一起工作的两个类能一起工作。

适配器模式的使用场景

  • 系统需要使用现存类,而此类接口不符系统需求,即接口不兼容。
  • 需一个统一的输出接口,而输入端的类型不可预知。

适配器模式的 UML 类图

适配器模式也分两种,即类适配器模式和对象适配器模式。

  • 类适配器模式,如图 2-41 所示。

类适配器的UML类图

图 2-41 类适配器的 UML 类图

目标接口需要的是 operation2(),而 Adaptee 对象中有一个 operation3(),因此不兼容。故通过 Adapter 实现一个 operation2() 将 Adapter 的 operation3() 转换为 Target() 需要的 operation2()。

  • 对象适配器模式,如图 2-42 所示。

对象适配器的UML类图

图 2-42 对象适配器的 UML 类图

这种实现方式直接将要被适配的对象传递到 Adapter 中,使用组合的形式实现接口兼容的效果。即带来的好处有,在被适配的对象中不暴露方法细节;且相对类适配器,由于继承了被适配对象,在 Adapter 类中出现一些奇怪接口。因此对象适配器模式的实现更加灵活。

适配器模式的简单示例

以电源适配器为例,分别以类适配器和对象适配器模式阐述具体情况。

  • 5V 电压是 Target 接口。
  • 220V 电压是 Adaptee 类。
  • 将电压 220V 转换到 5V 是 Adapter 类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/**
* 以类适配器模式实现
*/

// Target 角色
public interface FiveVolt {
public int getvolt5();
}

// Adaptee 角色,需被转换的对象
public class Volt220 {
return 220;
}

// Adapter 角色,将 220V 的电压转换为 5V 电压
public class VoltAdapter extends Volt220 implements FiveVolt {
@OVerride
public int getVolt5() {
return 5;
}
}

// 客户端实现
public class Test {
public static void main(String[] args) {
VoltAdapter adpter = new VoltAdapter();
System.out.println(输出电压:adapter.getVolt5);
}
}


/**
* 以对象适配器模式实现
*/

// Target 角色
public interface FiveVolt {
public int getvolt5();
}

// Adaptee 角色,需被转换的对象
public class Volt220 {
return 220;
}

// Adapter 角色,将 220V 的电压转换为 5V 电压
public class VoltAdapter implements FiveVolt {

Volt220 mVolt220;

public VoltAdapter(Volt220 adaptee) {
mVolt220 = adaptee;
}

public int getVolt220() {
return mVolt220.getVolt220();
}

@OVerride
public int getVolt5() {
return 5;
}
}

// 客户端实现
public class Test {
public static void main(String[] args) {
VoltAdapter adpter = new VoltAdapter( new Volt220() );
System.out.println(输出电压:adapter.getVolt5);
}
}

总结

  • 适配器模式的优缺点
    • 优点
      1) 更好的复用性:系统需使用现有的类,而此类的接口不符系统需求,则通过适配器模式可让这些功能得到更好的复用。
      2) 更好的扩展性。
    • 缺点 - 若可对系统重构,尽可能不使用适配器,过多使用适配器,容易让系统凌乱,不易整体把握。

装饰模式

装饰模式的介绍

  • 又称为包装模式,结构性设计模式。
  • 使用一种对客户端透明的方式来动态地扩展对象的功能,同时它也是继承关系的一种替代方案。

装饰模式的定义

动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式相比生成子类更为灵活。

装饰模式的使用场景

需要透明地、动态地扩展类的功能时,装饰模式不失一种理想方案。

装饰模式的 UML 类图

装饰模式的 UML 类图如图 2-43 所示。

装饰模式的UML类图

图 2-43 装饰模式的 UML 类图

Component:抽象组件
可以是一个接口或者抽象类,充当被装饰的原始对象。

ConcreteComponent:组件具体实现类
该类是 Component 类的基本实现,也是我们装饰的具体对象。

Decorator:抽象装饰者
其承担的职责是为了装饰我们的组件对象,其内部一定要有一个指向组件对象的引用。

ConcreteDecorator:抽象装饰者
对抽象装饰者做出具体实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 抽象组件类
public abstract class Component {
/**
* 抽象的方法:自由增加你需要的抽象方法
*/
public abstract void operate();
}

// 组件具体实现类
public class ConcreteComponent extends Component {
@Override
public void operate() {
// 忽略实现细节
}
}

// 抽象装饰者
public abstract class Decorator extends Component {
private Component component; // 持有一个 Component 对象的引用

/**
* 必要的构造方法,需要一个 Component 类型的对象
* @param component Component 对象
*/
public Decorator(Component component) {
this.component = component;
}

@Override
public void operate() {
component.operate();
}
}

// 装饰者具体实现类
public class ConcreteDecoratorA extends Decorator {
protected ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operate() {
operateA();
super.operate();
operateB();
}

public void operateA() { // 自定义的装饰方法 A
// 装饰方法逻辑
}
public void operateB() { // 自定义的装饰方法 B
// 装饰方法逻辑
}
}

// 客户端实现
public class Client {
public static void main(String[] args) {

// 构造被装饰的组件对象
Component component = new ConcreteComponent();

// 根据组件对象构造装饰者对象并调用,即给组件对象增加装饰者的功能方法
Decorator.decoratorA = new ConcreteDecoratorA(component);
decoratorA.operate();

Decorator.decoratorB = new ConcreteDecoratorB(component);
decoratorB.operate();
}
}

装饰模式实战

其实装饰模式并不复杂,也不陌生,它就是一种 类间的封装,例如我们常在 Activity 的 onCreate() 方法中做一些相关的初始化操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class DecoratorActivity extends Activity {
@Override
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);

initViews();
findViews();
setListeners();
}

private void initViews() {
setContentView(R.layout.activity_main);
}
private void findViews() {
// 匹配组件
}
private void setListeners() {
// 设置组件的监听器
}
}

总结

  • 装饰模式与代理模式的区别 (容易混淆)

    • 装饰模式:以对客户端透明的方式 扩展对象的功能,即继承关系的一个替代方案。
    • 代理模式:给一个对象提供一个代理对象,并由 代理对象 来控制对原有对象引用。

      代理模式,即对代理的对象施加控制。

享元模式

享元模式的介绍

  • 又称 FlyWeight,代表轻量级的意思,结构型设计模式。
  • 对象池 的一种实现。
  • 享元模式用来是尽可能减少内存使用量,它适用于可能存在大量重复对象的场景。

    目的:缓存可共享的对象,达到对象共享,避免过多创建对象,即提升性能、避免内存移除。

  • 享元对象
    • 内存状态:可共享,不随环境变化
    • 外部状态:不可共享,随环境变化
    • 对象容器:在经典的享元模式中,对象容器为一 Map,它的 是享元对象的内部状态,它的 享元对象本身
  • 客户端通过这个内部状态从享元工厂中获取享元对象,若有缓存则使用缓存对象,否则创建一个享元对象并存入容器中。

享元模式的定义

使用共享对象可有效地支持大量的细粒度的对象。

享元模式的使用场景

  • 系统中存在大量的 相似对象
  • 细粒度的对象都具备较接近的外部状态,且内部状态与环境无关,即对象没有特定身份。
  • 需要 缓冲池 的场景。

享元模式的 UML 类图

享元模式的 UML 类图如图 2-44 所示。

享元模式的UML类图

图 2-44 享元模式的 UML 类图

享元模式的简单示例

例1. 过年回家买火车票,无数人在客户端上订票 (有多次购票、刷票的情况),即不断向服务端发送请求。

而每次查询,服务器必须做出回应,具体地,用户查询输入出发地和目的地,查询结构返回值只有一趟列车的车票。而数以万计的人有同样需求,即不间断请求数据,每次重新创建一个查询的车票结果,即造成大量重复对象创建、销毁,使得服务器压力加重。

享元模式正好适合解决该情形的问题,例如 A 到 B 地的车辆是有限的,车上铺位分硬卧、软卧和坐票三种,将这些可公用的对象缓存起来。用户查询时优先使用缓存,反之则重新创建。

便于理解,本示例的 UML 类图如图 2-45 所示。

网上订票系统的UML类图

图 2-45 网上订票系统的 UML 类图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public interface Ticket {
public void showTicketInfo(String bunk);
}

// 火车票
public class TrainTicket implements Ticket {
public String from; // 始发地
public String to; // 目的地
public String bunk; // 铺位

TrainTicket(String from, String to) {
this.from = from;
this.to = to;
}

@Override
public void showTicketInfo(String bunk) {
price = new Random().nextInt(300);
System.out.println("From:" + from
+ "To:" + to
+ "Bunk:" + bunk
+ "Price:" + price);
}
}

public class TicketFactory {
static Map<String, Ticket> sTicketMap =
new ConcurrentHashMap<String, Ticket>();

public static Ticket getTicket(String from, String to) {
String key = from + "-" + to;

if( sTicketMap.containsKey(key) ) { // 使用缓存
return sTicketMap.get(key);
} else { // 创建对象
Ticket ticket = new TrainTicket(from, to);
sTicketMap.put(key, ticket);
return ticket;
}
}
}

例2. 我们知道 Java 中 String 是存在于常量池中,即一个 String 被定义之后它就被缓存到了常量池中,当其他地方使用同样的字符串,则直接使用缓存,而非创建。

1
2
3
4
5
6
7
8
9
10
11
public void testString() {
String str1 = "Hello World";
String str2 = new String("Hello World");
String str3 = "Hello " + "World";

System.out.println( "\nStr1 - Str3: " + str1.equals(str3)
+ "\nStr2 - Str3: " + str2.equals(str3)
+ "\nStr1 - Str2: " + str1.equals(str1));

// 输出的结果分别是:true,false,false
}

总结

  • 享元模式的优缺点
    • 优点 - 大幅度地降低内存中对象的数量。
    • 缺点
      1) 为了使对象可共享,需将一些状态外部化,使程序的逻辑复杂化。
      2) 将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。

外观模式

外观模式的介绍

  • 又称门面模式 (Facade模式),结构型设计模式。
  • 通过一个外观类使得整个系统中接口只有一个 统一的高层接口,即这样降低用户使用成本,也对用户屏蔽了很多实现细节。
  • 外观模式是 封装API 的常用手段。

外观模式的定义

  • 要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。
  • 外观模式提供一个高层次接口,使得子系统更易于使用。

外观模式的使用场景

  • 为一个复杂的子系统提供一个简单接口。

    对于系统进行定制、修改,这种易变性,使得隐藏子系统的具体实现变得尤为重要,对外隐藏子系统的具体实现,隔离变化。

  • 构建一层次结构的子系统,子系统间相互依赖,则通过 Facade 接口进行通信,从而简化他们的依赖关系。

外观模式的 UML 类图

外观模式的 UML 类图如图 2-46 所示。

外观模式的UML类图

图 2-46 外观模式的 UML 类图

总结

  • 外观模式的精髓在于 封装。通过一高层次结构为用户提供统一的 API 入口,使得用户通过一个类就基本能够操作整个系统。
  • 外观模式的优缺点
    • 优点
      1) 对客户端隐藏子系统细节,因而减少客户对于子系统的耦合。
      2) 外观类对子系统的接口封装,使得系统更易于使用。
    • 缺点
      1) 外观类没有遵循开闭原则,当业务出现变更时,可能需要直接修改外观类。

桥接模式

桥接模式的介绍

  • 又称桥梁模式,结构型设计模式。
  • 承接者连接 两边 的作用,两边指抽象部分和实现部分。

桥接模式的定义

抽象部分实现部分 分离,使它们都可以独立地进行变化。

桥接模式的使用场景

  • 对于不希望使用继承或因多层次继承导致系统类的个数急剧增加的系统,考虑使用桥接模式。
  • 需要在构件的抽象化角色和具体角色之间增加更多灵活性,避免两层次间建立静态的继承关系,可通过桥接模式使它们在抽象层建立一个关联关系。
  • 一个类存在两个独立变化的维度,且这两个维度都需进行扩展。
  • 任何多维度变化类或多个树状类之间的耦合可通过桥接模式解耦。

桥接模式的 UML 类图

桥接模式的 UML 类图如图 2-47 所示。

桥接模式的UML类图

图 2-47 桥接模式的 UML 类图

Abstraction:抽象部分
该类保持一个对实现部分对象的引用,抽象部分中的方法需要调用实现部分的对象来实现。该类一般为抽象类。

RefinedAbstraction:优化的抽象部分
抽象部分的具体实现,该类一般是对抽象部分的方法进行完善和扩展。

Implementor:实现部分
可以为接口或抽象类,其方法不一定要与抽象部分中的一致,一般情况下是由实现部分提供基本的操作,而抽象部分定义的则是基于实现部分这些基本操作的业务方法。

ConcreteImplementorA/B:实现部分的具体实现
完成实现部分中定义的具体逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 实现部分的抽象接口
public interface Implementor {
/**
* 实现抽象部分的具体方法
*/
public void operationImpl();
}

// 实现部分具体的实现
public class ConcreteImplementorA implements Implementor {
@Override
public void operationImpl() {
// 忽略实现逻辑
}
}

// 抽象部分
public abstract class Abstraction {
// 声明一私有成员变量引用实现部分的对象
private Implementor mImplementor;

/**
* 通过实现部分对象的引用构造抽象部分的对象
* @param implementor 实现部分对象的引用
*/
public Abstraction(Implementor implementor) {
mImplementor = implementor;
}

/**
* 通过调用实现部分具体的方法实现具体的功能
*/
public void operation() {
mImplementor.operationImpl();
}
}

// 优化的抽象部分
public class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(Implementor implementor) {
super(implementor);
}

/**
* 对 Abstraction 中的方法进行扩展
*/
public void refinedOperation() {
// 忽略实现逻辑
}
}

// 客户端实现
public class Client {
public static void main(String[] args) {
RefinedAbstraction abstration =
new RefinedAbstraction( new ConcreteImplementorA );

abstraction.operation();
abstraction.refinedOperation();
}
}

桥接模式实战

View 的视图层级与执行真正的硬件绘制相关类之间的关系可看作是一种桥接模式。即模仿这种行为,我们可自定义控件以桥接的方式提供多种不同的实现机制。

以进度条为例,我们可继承 View 类来实现进度条控件,自定义水平、垂直和圆形等不同形式的进度条。

便于理解,本示例的 UML 类图如图 2-48 所示。

进度条框架的UML类图

图 2-48 进度条框架的 UML 类图

总结

桥接模式,分离抽象与实现,其优点毋庸置疑,即灵活的扩展以及对客户来说透明的是实现。但不足之处在于运用桥接模式进行设计,是有一定难度的,需多加推敲与研究。

叁 MVC 与 MVP 模式

MVC

  • 模型 - 视图 - 控制器 (Model - View - Controller,MVC),是一种 框架模式 ,而非设计模式。

    GOF 把 MVC 看作3种设计模式,即观察者模式、策略模式与组合模式的合体,也就是一个基于 发布/订阅者模型 的框架。

  • 软件开发领域的3种级别重用:

    • 内部重用:同一应用中能公共使用的抽象块。
    • 代码重用:将通用模块组合成库或工具集,以便在多个应用和领域都能使用。
    • 应用框架重用:为专用领域提供通用的或者现成的基础结构,以获得最高级别的重用性。

      平时开发过程中,常见的框架模式除了 MVC 外,还有 MVVC、MTV、CBD、ORM 和 MVP。

  • MVC 模式的优缺点
    • 优点 - 表现层与业务层分离实现,各司其职。若在实际项目中,即前端工程师专注界面的研发,后端工程师致力于业务逻辑。
    • 缺点:
      1) Model 和 View 严格分离,在调试应用程序时较困难。
      2) 小规模项目,采用 MVC 模式反而显得工序更加繁琐。

MVP

  • 数据的存取 - 用户界面 - 交互中间人 (Model - View - Presenter,MVP),分离显示层和逻辑层,它们之间通过接口进行通信,降低耦合。

  • MVP 模式的三个角色

    • Presenter:交互中间人
      View 和 Model 沟通的桥梁。它从 Model 层检索数据后,返回给 View 层,使得 View 和 Model 之间没有耦合,也将业务逻辑从 View 角色上抽离。

    • View:用户界面
      在 Android 中,通常指 Activity、Fragment 或某个 View 控件,它含有一个 Presenter 成员变量,且 View 需实现以逻辑接口。将 View 上操作通过转交给 Presenter 实现,最后 Presenter 调用 View 逻辑接口将结果返回给 View。

    • Model:数据的存取
      主要提供数据的存取功能。Presenter 需通过 Model 层存储、获取数据。

MVP模式的三个角色

图 3-1 MVP模式的三个角色
  • 与 MVC 模式的区别

    • MVC 特点
      1) 用户可向 View 发送指令,再由 View 直接要求 Model 改变状态。
      2) 用户可向 Controller 发送指令,再由 Controller 发送给 View。
      3) Controller 起到时间路由的作用,同时业务逻辑都部署在 Controller 中。

      View 可直接访问 Model,即 MVC 模式的耦合性还是相对较高的。

MVC模式的三个角色

图 3-2 MVC模式的三个角色