1.工厂方法
工厂方法的目的是使得创建对象和使用对象是分离的,并且客户端总是引用抽象工厂和抽象产品:
// 产品接口
interface Product {
void use();
}
// 具体产品A
class ConcreteProductA implements Product {
@Override
public void use() {
System.out.println("Using Product A");
}
}
// 具体产品B
class ConcreteProductB implements Product {
@Override
public void use() {
System.out.println("Using Product B");
}
}
// 简单工厂
class SimpleFactory {
public Product createProduct(String type) {
switch (type) {
case "A":
return new ConcreteProductA();
case "B":
return new ConcreteProductB();
default:
throw new IllegalArgumentException("Unknown product type");
}
}
}
// 使用示例
public class Client {
public static void main(String[] args) {
SimpleFactory factory = new SimpleFactory();
Product productA = factory.createProduct("A");
productA.use(); // Output: Using Product A
Product productB = factory.createProduct("B");
productB.use(); // Output: Using Product B
}
}
不过,一般情况我们是使用静态方法(静态工厂方法)返回:
class SimpleFactory {
public static Product createProduct(String type) {
switch (type) {
case "A":
return new ConcreteProductA();
case "B":
return new ConcreteProductB();
default:
throw new IllegalArgumentException("Unknown product type");
}
}
}
静态工厂方法广泛地应用在Java
标准库中:
// Integer既是产品又是静态工厂
public final class Integer {
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
...
}
Integer n = Integer.valueOf(100);
new Integer(100)
和Integer.valueOf(100)
的区别:valueOf()
内部可能会使用new创建一个新的Integer
实例,但也可能直接返回一个缓存的Integer
实例。
总是引用接口而非实现类,能允许变换子类而不影响调用方,即尽可能面向抽象编程。
使用MessageDigest
时,为了创建某个摘要算法,总是使用静态工厂方法getInstance(String)
:
MessageDigest md5 = MessageDigest.getInstance("MD5");
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
调用方通过产品名称获得产品实例,不但调用简单,而且获得的引用仍然是MessageDigest
这个抽象类。
2. 抽象工厂
抽象工厂模式(Abstract Factory
)要解决的问题比较复杂,不但工厂是抽象的,产品是抽象的,而且有多个产品需要创建,因此,这个抽象工厂会对应到多个实际工厂,每个实际工厂负责创建多个实际产品:
┌────────┐
─ >│ProductA│
┌────────┐ ┌─────────┐ │ └────────┘
│ Client │─ ─>│ Factory │─ ─
└────────┘ └─────────┘ │ ┌────────┐
▲ ─ >│ProductB│
┌───────┴───────┐ └────────┘
│ │
┌─────────┐ ┌─────────┐
│Factory1 │ │Factory2 │
└─────────┘ └─────────┘
│ ┌─────────┐ │ ┌─────────┐
─ >│ProductA1│ ─ >│ProductA2│
│ └─────────┘ │ └─────────┘
┌─────────┐ ┌─────────┐
└ ─>│ProductB1│ └ ─>│ProductB2│
└─────────┘ └─────────┘
假设我们希望为用户提供一个Markdown
文本转换为HTML
和Word
的服务,它的接口(抽象工厂)定义如下:
public interface AbstractFactory {
// 创建Html文档:
HtmlDocument createHtml(String md);
// 创建Word文档:
WordDocument createWord(String md);
}
同样的,因为HtmlDocument
和WordDocument
都比较复杂,现在我们并不知道如何实现它们,所以只有接口(两个抽象产品):
// Html文档接口:
public interface HtmlDocument {
String toHtml();
void save(Path path) throws IOException;
}
// Word文档接口:
public interface WordDocument {
void save(Path path) throws IOException;
}
现在市场上有两家供应商:FastDoc Soft
的产品便宜,并且转换速度快,而GoodDoc Soft
的产品贵,但转换效果好。我们决定同时使用这两家供应商的产品,以便给免费用户和付费用户提供不同的服务。
首先FastDoc Soft
必须要有实际的产品,即FastHtmlDocument
和FastWordDocument
:
public class FastHtmlDocument implements HtmlDocument {
public String toHtml() {
...
}
public void save(Path path) throws IOException {
...
}
}
public class FastWordDocument implements WordDocument {
public void save(Path path) throws IOException {
...
}
}
然后,FastDoc Soft
必须提供一个实际的工厂来生产这两种产品,即FastFactory
:
public class FastFactory implements AbstractFactory {
public HtmlDocument createHtml(String md) {
return new FastHtmlDocument(md);
}
public WordDocument createWord(String md) {
return new FastWordDocument(md);
}
}
同理GoodDoc Soft
也要实现上述:
// 实际工厂:
public class GoodFactory implements AbstractFactory {
public HtmlDocument createHtml(String md) {
return new GoodHtmlDocument(md);
}
public WordDocument createWord(String md) {
return new GoodWordDocument(md);
}
}
// 实际产品:
public class GoodHtmlDocument implements HtmlDocument {
...
}
public class GoodWordDocument implements WordDocument {
...
}
最后将创建工厂放入到AbstractFactory
工厂中:
public interface AbstractFactory {
public static AbstractFactory createFactory(String name) {
if (name.equalsIgnoreCase("fast")) {
return new FastFactory();
} else if (name.equalsIgnoreCase("good")) {
return new GoodFactory();
} else {
throw new IllegalArgumentException("Invalid factory name");
}
}
}
客户端就可以直接通过工厂调用了`
//获取实例:
AbstractFactory factory = createFactory("fast");
// AbstractFactory factory = createFactory("good");
// 生成Html文档:
HtmlDocument html = factory.createHtml("#Hello\nHello, world!");
html.save(Paths.get(".", "fast.html"));
// 生成Word文档:
WordDocument word = factory.createWord("#Hello\nHello, world!");
word.save(Paths.get(".", "fast.doc"));
3. 生成器
生成器模式(Builder)是使用多个“小型”工厂来最终创建出一个完整对象。
把Markdown
转HTML
看作一行一行的转换,每一行根据语法,使用不同的转换器:
- 如果以
#
开头,使用HeadingBuilder
转换; - 如果以
>
开头,使用QuoteBuilder
转换; - 如果以
---
开头,使用HrBuilder
转换; - 其余使用
ParagraphBuilder
转换。
public class HtmlBuilder {
private HeadingBuilder headingBuilder = new HeadingBuilder();
private HrBuilder hrBuilder = new HrBuilder();
private ParagraphBuilder paragraphBuilder = new ParagraphBuilder();
private QuoteBuilder quoteBuilder = new QuoteBuilder();
public String toHtml(String markdown) {
StringBuilder buffer = new StringBuilder();
markdown.lines().forEach(line -> {
if (line.startsWith("#")) {
buffer.append(headingBuilder.buildHeading(line)).append('\n');
} else if (line.startsWith(">")) {
buffer.append(quoteBuilder.buildQuote(line)).append('\n');
} else if (line.startsWith("---")) {
buffer.append(hrBuilder.buildHr(line)).append('\n');
} else {
buffer.append(paragraphBuilder.buildParagraph(line)).append('\n');
}
});
return buffer.toString();
}
}
4. 原型
原型模式,即Prototype
,是指创建新对象的时候,根据现有的一个原型来创建。
使用原型模式更好的方式是定义一个copy()
方法,返回明确的类型:
public class Student {
private int id;
private String name;
private int score;
public Student copy() {
Student std = new Student();
std.id = this.id;
std.name = this.name;
std.score = this.score;
return std;
}
}
原型模式应用不是很广泛,因为很多实例会持有类似文件、
Socket
这样的资源,而这些资源是无法复制给另一个对象共享的,只有存储简单类型的“值”对象可以复制。
5. 单例
单例模式(Singleton)的目的是为了保证在一个进程中,某个类有且仅有一个实例。
实现单例需要:
-
private
构造方法,确保外部无法实例化; - 通过
private static
变量持有唯一实例,保证全局唯一性; - 通过
public static
方法返回此唯一实例,使外部调用方能获取到实例。
5.1 提供一个静态方法
public class Singleton {
// 静态字段引用唯一实例:
private static final Singleton INSTANCE = new Singleton();
// 通过静态方法返回实例:
public static Singleton getInstance() {
return INSTANCE;
}
// private构造方法保证外部无法实例化:
private Singleton() {
}
}
5.2 直接把static变量暴露给外部
public class Singleton {
// 静态字段引用唯一实例:
public static final Singleton INSTANCE = new Singleton();
// private构造方法保证外部无法实例化:
private Singleton() {
}
}
Java
的单例类:Runtime runtime = Runtime.getRuntime();
5.3 enum实现
public enum World {
// 唯一枚举:
INSTANCE;
private String name = "world";
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
String name = World.INSTANCE.getName();
5.4 延迟加载
使用Singleton模式的时候,最好不要延迟加载,因为这种写法在多线程中是错误的,在竞争条件下会创建出多个实例:
public class Singleton {
private static Singleton INSTANCE = null;
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
private Singleton() {
}
}
使用枚举实现
Singleton
还避免了第一种方式实现Singleton
的一个潜在问题:即序列化和反序列化会绕过普通类的private
构造方法从而创建出多个实例,而枚举类就没有这个问题。