Creational Design Patterns in Core Java – 核心Java中的创意设计模式

最后修改: 2020年 10月 14日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

Design Patterns are common patterns that we use when writing our software. They represent established best practices developed over time. These can then help us to ensure that our code is well designed and well built.

设计模式是我们在编写软件时使用的常见模式。它们代表了随着时间的推移而形成的既定最佳实践。然后,这些可以帮助我们确保我们的代码是精心设计和精心构建的。

Creational Patterns are design patterns that focus on how we obtain instances of objects. Typically, this means how we construct new instances of a class, but in some cases, it means obtaining an already constructed instance ready for us to use.

娱乐模式是关注我们如何获得对象实例的设计模式。通常,这意味着我们如何构建一个类的新实例,但在某些情况下,这意味着获得一个已经构建好的实例供我们使用。

In this article, we’re going to revisit some common creational design patterns. We’ll see what they look like and where to find them within the JVM or other core libraries.

在这篇文章中,我们将重新审视一些常见的创建性设计模式。我们将看到它们是什么样子的,以及在JVM或其他核心库中哪里可以找到它们。

2. Factory Method

2.工厂法

The Factory Method pattern is a way for us to separate out the construction of an instance from the class we are constructing. This is so we can abstract away the exact type, allowing our client code to instead work in terms of interfaces or abstract classes:

工厂方法模式是一种让我们将实例的构建与我们正在构建的类分开的方法。这样我们就可以抽象出准确的类型,让我们的客户代码以接口或抽象类的方式工作。

class SomeImplementation implements SomeInterface {
    // ...
}
public class SomeInterfaceFactory {
    public SomeInterface newInstance() {
        return new SomeImplementation();
    }
}

Here, our client code never needs to know about SomeImplementation, and instead, it works in terms of SomeInterface. Even more than this, though, we can change the type returned from our factory and the client code needn’t change. This can even include dynamically selecting the type at runtime.

在这里,我们的客户代码从来不需要知道SomeImplementation,而是以SomeInterface的方式工作。不仅如此,我们可以改变从工厂返回的类型,而客户端代码不需要改变。这甚至可以包括在运行时动态地选择类型。

2.1. Examples in the JVM

2.1.JVM中的例子

Possibly the most well-known examples of this pattern the JVM are the collection building methods on the Collections class, like singleton(), singletonList(), and singletonMap(). These all return instances of the appropriate collection – Set, List, or Map – but the exact type is irrelevant. Additionally, the Stream.of() method and the new Set.of(), List.of(), and Map.ofEntries() methods allow us to do the same with larger collections.

JVM中最著名的例子是Collections类上的集合构建方法,如singleton()singletonList()singletonMap()。这些方法都返回相应集合的实例–SetListMap–但确切类型无关。此外,Stream.of()方法和新的Set.of()List.of()Map.ofEntries()方法允许我们对较大的集合做同样的处理。

There are plenty of other examples of this as well, including Charset.forName(), which will return a different instance of the Charset class depending on the name asked for, and ResourceBundle.getBundle(), which will load a different resource bundle depending on the name provided.

还有很多其他的例子,包括Charset.forName(),它将根据要求的名称返回一个不同的Charset类实例,以及ResourceBundle.getBundle(),它将根据提供的名称加载一个不同的资源包。

Not all of these need to provide different instances, either. Some are just abstractions to hide inner workings. For example, Calendar.getInstance() and NumberFormat.getInstance() always return the same instance, but the exact details are irrelevant to the client code.

也不是所有这些都需要提供不同的实例。有些只是隐藏内部工作的抽象概念。例如,Calendar.getInstance()NumberFormat.getInstance()总是返回同一个实例,但具体细节与客户端代码无关。

3. Abstract Factory

3.抽象工厂

The Abstract Factory pattern is a step beyond this, where the factory used also has an abstract base type. We can then write our code in terms of these abstract types, and select the concrete factory instance somehow at runtime.

抽象工厂模式比这更进一步,所使用的工厂也有一个抽象的基础类型。然后我们可以用这些抽象类型来编写我们的代码,并在运行时以某种方式选择具体的工厂实例。

First, we have an interface and some concrete implementations for the functionality we actually want to use:

首先,我们有一个接口和一些具体的实现来实现我们实际想要使用的功能。

interface FileSystem {
    // ...
}
class LocalFileSystem implements FileSystem {
    // ...
}
class NetworkFileSystem implements FileSystem {
    // ...
}

Next, we have an interface and some concrete implementations for the factory to obtain the above:

接下来,我们有一个接口和一些工厂的具体实现来获得上述内容。

interface FileSystemFactory {
    FileSystem newInstance();
}
class LocalFileSystemFactory implements FileSystemFactory {
    // ...
}
class NetworkFileSystemFactory implements FileSystemFactory {
    // ...
}

We then have another factory method to obtain the abstract factory through which we can obtain the actual instance:

然后我们有另一个工厂方法来获得抽象工厂,通过它我们可以获得实际的实例。

class Example {
    static FileSystemFactory getFactory(String fs) {
        FileSystemFactory factory;
        if ("local".equals(fs)) {
            factory = new LocalFileSystemFactory();
        else if ("network".equals(fs)) {
            factory = new NetworkFileSystemFactory();
        }
        return factory;
    }
}

Here, we have a FileSystemFactory interface that has two concrete implementations. We select the exact implementation at runtime, but the code that makes use of it doesn’t need to care which instance is actually used. These then each return a different concrete instance of the FileSystem interface, but again, our code doesn’t need to care exactly which instance of this we have.

在这里,我们有一个FileSystemFactory接口,它有两个具体的实现。我们在运行时选择确切的实现,但使用它的代码不需要关心实际使用的是哪个实例。然后它们各自返回FileSystem接口的不同具体实例,但我们的代码也不需要关心到底是哪个实例。

Often, we obtain the factory itself using another factory method, as described above. In our example here, the getFactory() method is itself a factory method that returns an abstract FileSystemFactory that’s then used to construct a FileSystem.

通常,我们使用另一个工厂方法来获得工厂本身,如上所述。在我们这里的例子中,getFactory()方法本身就是一个工厂方法,它返回一个抽象的FileSystemFactory,然后用于构建一个FileSystem

3.1. Examples in the JVM

3.1.JVM中的例子

There are plenty of examples of this design pattern used throughout the JVM. The most commonly seen are around the XML packages — for example, DocumentBuilderFactory, TransformerFactory, and XPathFactory. These all have a special newInstance() factory method to allow our code to obtain an instance of the abstract factory.

在整个JVM中,有很多这种设计模式的例子。最常见的是围绕着XML包–例如,DocumentBuilderFactoryTransformerFactory、XPathFactory这些都有一个特殊的newInstance()工厂方法,允许我们的代码获得抽象工厂的实例

Internally, this method uses a number of different mechanisms – system properties, configuration files in the JVM, and the Service Provider Interface – to try and decide exactly which concrete instance to use. This then allows us to install alternative XML libraries in our application if we wish, but this is transparent to any code actually using them.

在内部,该方法使用了许多不同的机制–系统属性、JVM中的配置文件以及服务提供者接口–来尝试并决定究竟使用哪个具体实例。然后,如果我们愿意,可以在我们的应用程序中安装替代的XML库,但这对实际使用这些库的任何代码都是透明的。

Once our code has called the newInstance() method, it will then have an instance of the factory from the appropriate XML library. This factory then constructs the actual classes we want to use from that same library.

一旦我们的代码调用了newInstance()方法,它就会有一个来自适当的 XML 库的工厂实例。然后这个工厂会从同一个库中构造出我们想要使用的实际类。

For example, if we’re using the JVM default Xerces implementation, we’ll get an instance of com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl, but if we wanted to instead use a different implementation, then calling newInstance() would transparently return that instead.

例如,如果我们使用JVM默认的Xerces实现,我们将得到一个com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl的实例,但如果我们想使用一个不同的实现,那么调用newInstance()将透明地返回该实例。

4. Builder

4.建筑商

The Builder pattern is useful when we want to construct a complicated object in a more flexible manner. It works by having a separate class that we use for building our complicated object and allowing the client to create this with a simpler interface:

当我们想以更灵活的方式构建一个复杂的对象时,构建者模式是非常有用的。它的工作原理是有一个单独的类,我们用它来构建我们的复杂对象,并允许客户用一个更简单的接口来创建。

class CarBuilder {
    private String make = "Ford";
    private String model = "Fiesta";
    private int doors = 4;
    private String color = "White";

    public Car build() {
        return new Car(make, model, doors, color);
    }
}

This allows us to individually provide values for make, model, doors, and color, and then when we build the Car, all of the constructor arguments get resolved to the stored values.

这允许我们为makemodeldoorscolor单独提供值,然后当我们构建Car时,所有的构造器参数都被解析为存储的值。

4.1. Examples in the JVM

4.1.JVM中的例子

There are some very key examples of this pattern within the JVM. The StringBuilder and StringBuffer classes are builders that allow us to construct a long String by providing many small parts. The more recent Stream.Builder class allows us to do exactly the same in order to construct a Stream:

在JVM中,有一些非常关键的例子说明了这种模式。StringBuilderStringBuffer类是构建器,它们允许我们通过提供许多小部件来构建一个长的String。最近的Stream.Builder类允许我们做同样的事情来构造一个Stream

Stream.Builder<Integer> builder = Stream.builder<Integer>();
builder.add(1);
builder.add(2);
if (condition) {
    builder.add(3);
    builder.add(4);
}
builder.add(5);
Stream<Integer> stream = builder.build();

5. Lazy Initialization

5.懒惰的初始化

We use the Lazy Initialization pattern to defer the calculation of some value until it’s needed. Sometimes, this can involve individual pieces of data, and other times, this can mean entire objects.

我们使用 “懒惰初始化”(Lazy Initialization)模式,将某些值的计算推迟到需要时再进行。有时,这可能涉及单个数据片断,而其他时候,这可能意味着整个对象。

This is useful in a number of scenarios. For example, if fully constructing an object requires database or network access and we may never need to use it, then performing those calls may cause our application to under-perform. Alternatively, if we’re computing a large number of values that we may never need, then this can cause unnecessary memory usage.

这在很多情况下是有用的。例如,如果完全构建一个对象需要访问数据库或网络,而我们可能永远不需要使用它,那么执行这些调用可能会导致我们的应用程序表现不佳。另外,如果我们正在计算大量可能永远不需要的值,那么这可能会导致不必要的内存使用。

Typically, this works by having one object be the lazy wrapper around the data that we need, and having the data computed when accessed via a getter method:

典型的做法是,让一个对象成为我们需要的数据的懒惰包装器,并在通过getter方法访问时对数据进行计算。

class LazyPi {
    private Supplier<Double> calculator;
    private Double value;

    public synchronized Double getValue() {
        if (value == null) {
            value = calculator.get();
        }
        return value;
    }
}

Computing pi is an expensive operation and one that we may not need to perform. The above will do so on the first time that we call getValue() and not before.

计算π是一个昂贵的操作,我们可能不需要执行这个操作。上述操作将在我们第一次调用getValue()时进行,而不是之前。

5.1. Examples in the JVM

5.1.JVM中的例子

Examples of this in the JVM are relatively rare. However, the Streams API introduced in Java 8 is a great example. All of the operations performed on a stream are lazy, so we can perform expensive calculations here and know they are only called if needed.

在JVM中这样的例子比较少。然而,Java 8 中引入的 Streams API 是一个很好的例子。在流上执行的所有操作都是懒惰的,因此我们可以在这里执行昂贵的计算,并且知道它们只在需要时才被调用。

However, the actual generation of the stream itself can be lazy as well. Stream.generate() takes a function to call whenever the next value is needed and is only ever called when needed. We can use this to load expensive values – for example, by making HTTP API calls – and we only pay the cost whenever a new element is actually needed:

然而,流的实际生成本身也可以是懒惰的Stream.generate()在需要下一个值的时候会调用一个函数,并且只在需要的时候调用。我们可以用它来加载昂贵的值–例如,通过HTTP API调用–我们只在实际需要新元素的时候支付费用。

Stream.generate(new BaeldungArticlesLoader())
  .filter(article -> article.getTags().contains("java-streams"))
  .map(article -> article.getTitle())
  .findFirst();

Here, we have a Supplier that will make HTTP calls to load articles, filter them based on the associated tags, and then return the first matching title. If the very first article loaded matches this filter, then only a single network call needs to be made, regardless of how many articles are actually present.

在这里,我们有一个Supplier,它将进行HTTP调用来加载文章,根据相关的标签来过滤它们,然后返回第一个匹配的标题。如果加载的第一篇文章与这个过滤器相匹配,那么就只需要进行一次网络调用,而不管实际有多少篇文章。

6. Object Pool

6.对象库

We’ll use the Object Pool pattern when constructing a new instance of an object that may be expensive to create, but re-using an existing instance is an acceptable alternative. Instead of constructing a new instance every time, we can instead construct a set of these up-front and then use them as needed.

当构建一个新的对象实例时,我们会使用对象池模式,该实例的创建成本可能很高,但重新使用现有的实例是一个可以接受的选择。与其每次都构造一个新的实例,我们倒不如先构造一组,然后根据需要使用它们。

The actual object pool exists to manage these shared objects. It also tracks them so that each one is only used in one place at the same time. In some cases, the entire set of objects gets constructed only at the start. In other cases, the pool may create new instances on demand if it’s necessary

实际对象池的存在是为了管理这些共享对象。它还跟踪它们,以便每个对象在同一时间只在一个地方使用。在某些情况下,整个对象集只在开始时被构建。在其他情况下,如果有必要,池子可以按需创建新的实例

6.1. Examples in the JVM

6.1.JVM中的例子

The main example of this pattern in the JVM is the use of thread pools. An ExecutorService will manage a set of threads and will allow us to use them when a task needs to execute on one. Using this means that we don’t need to create new threads, with all of the cost involved, whenever we need to spawn an asynchronous task:

JVM中这种模式的主要例子是使用线程池ExecutorService将管理一组线程,并允许我们在任务需要在其中执行时使用它们。使用这意味着我们不需要在需要生成异步任务时创建新的线程,并承担所有相关的成本。

ExecutorService pool = Executors.newFixedThreadPool(10);

pool.execute(new SomeTask()); // Runs on a thread from the pool
pool.execute(new AnotherTask()); // Runs on a thread from the pool

These two tasks get allocated a thread on which to run from the thread pool. It might be the same thread or a totally different one, and it doesn’t matter to our code which threads are used.

这两个任务从线程池中被分配到一个线程上运行。这可能是同一个线程,也可能是一个完全不同的线程,对我们的代码来说,使用哪个线程并不重要。

7. Prototype

7.原型

We use the Prototype pattern when we need to create new instances of an object that are identical to the original. The original instance acts as our prototype and gets used to construct new instances that are then completely independent of the original. We can then use these however is necessary.

当我们需要为一个对象创建与原对象相同的新实例时,我们就会使用原型模式。原始实例作为我们的原型,被用来构造新的实例,然后完全独立于原始实例。然后我们可以根据需要使用这些实例。

Java has a level of support for this by implementing the Cloneable marker interface and then using Object.clone(). This will produce a shallow clone of the object, creating a new instance, and copying the fields directly.

Java通过实现Cloneable标记接口,然后使用Object.clone()对此有一定程度的支持。这将产生一个对象的浅层克隆,创建一个新的实例,并直接复制字段。

This is cheaper but has the downside that any fields inside our object that have structured themselves will be the same instance. This, then, means changes to those fields also happen across all instances. However, we can always override this ourselves if necessary:

这比较便宜,但也有一个缺点,那就是我们对象内部任何已经结构化的字段都将是同一个实例。这就意味着对这些字段的改变也会发生在所有的实例中。然而,如果有必要,我们总是可以自己覆盖这一点。

public class Prototype implements Cloneable {
    private Map<String, String> contents = new HashMap<>();

    public void setValue(String key, String value) {
        // ...
    }
    public String getValue(String key) {
        // ...
    }

    @Override
    public Prototype clone() {
        Prototype result = new Prototype();
        this.contents.entrySet().forEach(entry -> result.setValue(entry.getKey(), entry.getValue()));
        return result;
    }
}

7.1. Examples in the JVM

7.1.JVM中的例子

The JVM has a few examples of this. We can see these by following the classes that implement the Cloneable interface. For example, PKIXCertPathBuilderResult, PKIXBuilderParameters, PKIXParameters, PKIXCertPathBuilderResult, and PKIXCertPathValidatorResult are all Cloneable.

JVM有几个这样的例子。我们可以通过跟踪实现Cloneable接口的类来看到这些。例如,PKIXCertPathBuilderResultPKIXBuilderParametersPKIXParametersPKIXCertPathBuilderResultPKIXCertPathValidatorResult都是Cloneable。

Another example is the java.util.Date class. Notably, this overrides the Object.clone() method to copy across an additional transient field as well.

另一个例子是java.util.Date类。值得注意的是,这个类重写了Object.clone()方法,以复制一个额外的暂存字段

8. Singleton

8.辛格尔顿

The Singleton pattern is often used when we have a class that should only ever have one instance, and this instance should be accessible from throughout the application. Typically, we manage this with a static instance that we access via a static method:

当我们有一个只应该有一个实例的类,并且这个实例应该可以在整个应用程序中被访问时,经常使用单子模式。通常情况下,我们用一个静态的实例来管理它,通过静态方法来访问。

public class Singleton {
    private static Singleton instance = null;

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

There are several variations to this depending on the exact needs — for example, whether the instance is created at startup or on first use, whether accessing it needs to be threadsafe, and whether or not there needs to be a different instance per thread.

根据具体的需要,这有几种变化–例如,实例是在启动时还是在第一次使用时创建,访问它是否需要线程安全,以及是否需要每个线程有不同的实例。

8.1. Examples in the JVM

8.1.JVM中的例子

The JVM has some examples of this with classes that represent core parts of the JVM itselfRuntime, Desktop, and SecurityManager. These all have accessor methods that return the single instance of the respective class.

JVM有一些这样的例子,这些类代表了JVM本身的核心部分Runtime、Desktop,SecurityManager。这些都有访问器方法来返回各自类的单一实例。

Additionally, much of the Java Reflection API works with singleton instances. The same actual class always returns the same instance of Class, regardless of whether it’s accessed using Class.forName(), String.class, or through other reflection methods.

此外,Java Reflection API的大部分工作都是与单子实例相关的。同一个实际的类总是返回同一个Class的实例,无论它是使用Class.forName()String.class,还是通过其他反射方法访问。

In a similar manner, we might consider the Thread instance representing the current thread to be a singleton. There are often going to be many instances of this, but by definition, there is a single instance per thread. Calling Thread.currentThread() from anywhere executing in the same thread will always return the same instance.

以类似的方式,我们可以认为代表当前线程的Thread实例是一个单子。通常会有很多这样的实例,但根据定义,每个线程只有一个实例。在同一个线程中的任何地方调用Thread.currentThread(),都会返回同一个实例。

9. Summary

9.摘要

In this article, we’ve had a look at various different design patterns used for creating and obtaining instances of objects. We’ve also looked at examples of these patterns as used within the core JVM as well, so we can see them in use in a way that many applications already benefit from.

在这篇文章中,我们看了用于创建和获取对象实例的各种不同设计模式。我们还看了这些模式在核心JVM中的应用实例,因此我们可以看到它们在许多应用程序中的应用,并从中受益。