37.设计模式(1)创建型

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文本转换为HTMLWord的服务,它的接口(抽象工厂)定义如下:

public interface AbstractFactory {
    // 创建Html文档:
    HtmlDocument createHtml(String md);
    // 创建Word文档:
    WordDocument createWord(String md);
}

同样的,因为HtmlDocumentWordDocument都比较复杂,现在我们并不知道如何实现它们,所以只有接口(两个抽象产品):

// 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必须要有实际的产品,即FastHtmlDocumentFastWordDocument:

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)是使用多个“小型”工厂来最终创建出一个完整对象。
MarkdownHTML看作一行一行的转换,每一行根据语法,使用不同的转换器:

  • 如果以#开头,使用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构造方法从而创建出多个实例,而枚举类就没有这个问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容