对创建型模式的总结

创建型模式关注类的创建过程。它专注分离对象的创建和使用(非常粗略地来说,就是不使用 new 来创建对象 w),在使用对象时无需在乎其创建过程,创建对象时无需在乎其使用,从而降低系统耦合性,使其更容易扩展。创建型模式中有如下模式——单例模式,2(+1)个工厂模式,原型模式,建造者模式,现在挨个过堂。

单例模式

单例模式是名副其实的——确保类只能生成单个实例。它的适用范围也是和其性质直接对应的——只需要一个实例的场合。单例模式有三种实现——饿汉式(EagleSingleton):类加载时即生成所需单例,请求时直接返回该单例;懒汉式(LazySingleton):每次请求时都检查是否已经初始化,从而在第一次请求的时候进行创建,之后则直接返回;IoDH:利用 Java 的类加载机制,用一个静态内部类(称为持有类)持有所需对象,使其在第一次请求时加载该持有类。

三种实现各有优劣——饿汉式可能造成资源的浪费,因为其可能在未被使用时就初始化了;懒汉式是线程不安全的,需要所谓的“双重检查锁定”来保证线程安全,使代码比较复杂,性能也会有损失;IoDH 让 JVM 来保证线程安全性(这一点是很好的,额外的细节什么的,交给编程语言去处理便好,程序员应该专注更加抽象,更加偏向业务的层面),但是它是局限于编程语言的。

简单工厂模式

简单工厂模式的实质是使用一个所谓的工厂类来接管一类对象的创建。其一般形式是——

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Factory {
// 注意,Product 是一个抽象基类或接口
public static Product getProduct(String type) {
Product res = null;
if (type.equalsIgnoreCase("ProductA")) {
res = new ProductA();
// 各种其他处理……
return res;
}
else if (type.equalsIgnoreCase("ProductB")) {
res = new ProductB();
// 各种其他处理……
return res;
}
else if (type.equalsIgnoreCase("ProductC")) {
res = new ProductC();
// 各种其他处理……
return res;
}
// ...
return res; // 或者抛个异常之类的
}
}

它的目的是将创建所需对象(也就是产品)的职责从客户端转交给工厂类,从而将类的创建和使用的职责相分离。客户端将不再自己手动 new 产品,而是使用Product product = Factory.getProduct("ProductA");这样的形式。参数字符串还可写入配置文件中,从而使其更加灵活,更改产品也不需要重新编译。

简单工厂模式的缺点在于,其中有大量 if-else 逻辑,导致可能的性能问题;且增加,修改和删除产品都需要修改工厂类,违反开闭原则。

工厂方法模式

工厂方法模式为简单工厂模式增加了一个抽象层并解决了简单工厂模式的问题。对于工厂方法模式,每一个工厂都为一个所谓的抽象工厂的子类(或实现,取决于这个抽象工厂是抽象类还是接口),这个抽象工厂中将定义生成抽象产品的方法的接口。每一个具体工厂只负责生产一种具体产品。

其一般形式如下——

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
// 抽象层
abstract class Factory {
public abstract Product getProduct();
public static Factory getFactory(String type) {
// 通过反射等手段获取一个具体工厂
}
}
// 一个具体工厂
class ConcreteFactory1 extends Factory {
public Product getProduct() {
Product res = new Product1();
// . . .
return res;
}
}
// . . .
// 客户端
public class Main {
public static void main(String[] args) {
// いつもどおり,这里获取具体工厂的参数也是能够写在配置文件里的
// 其实也可以直接在抽象工厂中定义一个取得具体产品的 static 方法以隐藏具体工厂,降低系统的复杂性
Product res = Factory.getFactory("Product1").getProduct();
// . . .
}
}

为什么简单工厂模式里不使用反射生成产品,而工厂方法模式里使用反射生成具体工厂?因为具体工厂的初始化过程是极易抽象出来的——new 就完事了(或者使用单例模式之类的?),而具体产品的初始化并不能这么简单地抽象。

一个有趣的事实是,在简单工厂模式中,参数用于生成特定产品;在工厂方法模式和抽象工厂模式中,参数用于生成特定的工厂。

工厂方法模式相较于简单工厂模式的优点在于,它去掉了繁复的 if-else 循环,每个具体产品的初始化过程交由各个具体工厂来执行,保证了单一职责原则;添加新的具体产品的时候,只需要添加成对工厂及其产品即可,不需要修改源代码,保证了开闭原则。缺点是类的数量会成对增加,且使用了反射等技术,使系统复杂性更高,更难以理解。

抽象工厂模式

抽象工厂模式相较于工厂方法模式的区别在于,工厂方法模式中的一系列工厂负责一个产品等级结构(也就是一个继承结构,如按钮,白色按钮,蓝色按钮等这样的一个继承体系)的生产,而抽象工厂模式负责一个产品族的生产。也可以说,在工厂方法模式中,一个具体工厂对应一个具体产品;在抽象工厂模式中,一个具体工厂对应一族产品。所谓产品族,就是一系列本质不同但具有相近性质,且应当一起生产的产品,比如 win 系统的各种 UI 组件,Android 系统中各种 UI 组件,就各是一个产品族。

其一般形式是——

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
abstract class Factory {
// 当然,这里的 ProductABC 全是抽象的
public abstract ProductA getProductA();
public abstract ProductB getProductB();
public abstract ProductC getProductC();
public static Factory getFactory(String type) {
// 通过反射等手段获取一个具体工厂
}
}
// 一个具体工厂
class ConcreteFactory1 extends Factory {
public ProductA getProductA() {
ProductA res = new ConcreteProductA1();
// . . .
return res;
}
// . . .
}
// 客户端
public class Main {
public static void main(String[] args) {
// 同上,也可以使用配置文件
ProductA productA = Factory.getFactory("Product1").getProductA();
ProductB productB = Factory.getFactory("Product1").getProductB();
// . . .
}
}

当用户需求一族产品时,使用抽象工厂方法模式是比较适合的,比如一个游戏的各个不同平台上的 UI。这时候如果使用工厂方法模式会导致类的数量很多,且需要配置好每个工厂的生产,配置文件也会变得复杂且容易出错。需要增加新的产品族(比如这游戏又移植到新的平台上去了)时,需要添加许多具体工厂和产品,比较麻烦。

抽象工厂方法的问题在于,当需要在产品族中增加新的产品时,需要大刀阔斧地改抽象工厂类和所有具体工厂类的源代码,而增加新的产品族则是保持开闭原则的,这里就提到了“开闭原则的倾斜性”。且一个工厂要负责许多不同产品的构建,虽然这些构建分散到不同方法中,但归根结底还是比较麻烦,职责过重的。

单例模式

单例模式没啥好说的……我觉得我现在的认识或许也不太充分。

建造者模式

建造者模式就是一步一步构建复杂对象的一种设计模式。其要点在于将复杂对象切分成一个个部分(比如游戏的捏人系统之类的),逐个击破。同时使用诸如钩子函数等手段更精确地控制对象的构建。

也没啥好说的……没看到它的特别之处(悲

无论如何,创建型模式已经告一段落,等将来实践或复习的时候再来讨教吧。现在开始下一部分:结构型模式——它关注将现有的类或对象组织在一起以形成更加……符合要求的结构。也就是关注类之间的组合


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 协议 ,转载请注明出处!