设计模式:单例、原型和生成器

在这篇文章中,我们将重点介绍其余的创建模式:Singleton,Builder和Prototype。

在我看来,这些模式不如工厂重要。然而,了解它们仍然很有用。我将提供UML描述,简单的java示例(这样即使你不了解java,你也可以理解),并提出来自着名Java框架或API的真实示例。

文章目录

  • 创造模式
  • 单例模式
    • Java 实现
    • 何时需要使用单例?
    • 为什么不使用单例?
    • 使用单个实例而不是单一实例
    • 一些想法
    • 真实示例
  • 原型模式
    • 何时使用原型?
    • Java 实现
    • **真实示例**
  • 生成器模式
    • 它解决的问题
    • 一个简单的 java 示例
    • 正式定义
    • 真实示例
  • 结论

创造模式

创建模式是处理对象初始化并克服构造函数限制的设计模式。四人帮在他们的书“【设计模式:可重用面向对象软件的元素】中描述了其中的五个:

  • 单身人士,
  • 建筑工人,
  • 原型,
  • 抽象工厂,
  • 工厂模式。
    在这里插入图片描述

自本书出版(1994年)以来,已经发明了许多创造模式:

  • 其他类型的工厂(如静态工厂),
  • 池模式,
  • 惰性初始化,
  • 依赖注入,
  • 服务定位器,

在这篇文章中,我们只关注我还没有描述过的GoF的创造模式的其余部分。正如我在引言中所说,它们不如工厂重要,因为你可以没有它们(而工厂是许多应用程序和框架的支柱)。但是,它们很有用,与工厂不同,它们不会使代码更难阅读。

单例模式

这种模式是最著名的。在过去的几十年里,它被过度使用,但自那以后它的受欢迎程度有所下降。我个人避免使用它,因为它使代码更难以进行单元测试并创建紧密耦合。我更喜欢使用处理类的授权实例数的工厂(如Spring容器),我们将讨论这种方法。我认为你应该避免单例模式。事实上,这种模式最重要的用途是能够在面试官问“什么是单例?”时回答他。这种模式非常有争议,仍然有人赞成它。

话虽如此,根据GoF的说法,单例旨在:

“确保一个类只有一个实例,并提供对它的全局访问点”

因此,类是单例有 2 个要求:

  • 拥有唯一实例
  • 可从任何地方访问

有些人只考虑第一个要求(比如几年前的我)。在这种情况下,该类只是一个实例

让我们看一下如何在UML中执行单例

单例模式

在此 UML 图中,单例类有 3 项:

  • 类属性(实例):此属性包含单例类的唯一实例。
  • 一个类公共方法(getInstance()):它提供了获取类Singleton的唯一实例的唯一方法。可以从任何地方调用该方法,因为它是类方法(而不是实例方法)。
  • 私有构造函数(Singleton()):它阻止任何人使用构造函数实例化单例。

在此示例中,需要 Singleton 实例的开发人员将调用 Singleton.getInstance() 类方法。

单例类中的单例实例可以是:

  • 预初始化(这意味着在有人调用 getInstance()之前,它被实例化了)
  • lazy-initialized(这意味着它是在第一次调用getInstance()期间实例化的)

当然,真正的单例还有其他方法和属性来执行其业务逻辑。

Java 实现

这是使用预实例化方法在Java中创建单例的非常简单的方法。

public class SimpleSingleton {private static final SimpleSingleton INSTANCE = new SimpleSingleton();private SimpleSingleton() { }public static SimpleSingleton getInstance(){return INSTANCE;}
}

使用这种方式,当类装入器装入类时,仅创建一次单一实例。如果代码中从未使用过该类,则不会实例化该实例(因为 JVM 的类装入器不会加载它),因此会浪费内存。但是,如果该类出现并且您不使用它(例如,如果它仅在非常非常罕见的条件下使用),则单例将无条件初始化。除非您的单例占用大量内存,否则应以这种方式使用。

不过,如果你只需要在真正使用时创建单例(惰性初始化),这里有一种方法可以在多线程环境中执行此操作。这部分有点棘手,因为它涉及线程一致性。

public class TouchySingleton {private static volatile TouchySingleton instance;private TouchySingleton() {}public static TouchySingleton getInstance() {if (instance == null) {synchronized (TouchySingleton.class) {if (instance == null) {instance = new TouchySingleton();}}}return instance;}
}

正如我所说,它真的更难阅读(这就是为什么预先实例化的方式更好)。此单例涉及一个锁,以避免 2 个线程同时调用 getInstance() 创建 2 个实例。由于锁的成本很高,因此首先要进行没有锁的测试,然后使用锁进行测试(这是双重检查的锁定),以便在实例已经存在时不使用锁。

另一个特殊性是,实例必须是易失性的,以确保在创建实例时,其状态在不同的处理器内核上是相同的。

何时需要使用单例?

  • 当您只需要一个资源(数据库连接,套接字连接…
  • 避免无状态类的多个实例以避免内存浪费
  • 出于业务原因

您不应该使用单例在不同对象之间共享变量/数据,因为它会产生非常紧密的耦合!

为什么不使用单例?

一开始,我说你不应该使用单例,因为你获得单例的方式。它基于一个类函数,可以在代码中的任何位置调用该函数。我在stackoverflow上读到了一个很好的答案,给出了它不好的4个原因:

  • 使用单例,可以隐藏类之间的依赖关系,而不是通过接口公开它们。这意味着您需要阅读每个方法的代码,以了解一个类是否正在使用另一个类。
  • 它们违反了单一责任原则:它们控制自己的创建和生命周期(使用惰性初始化,单例在创建时选择)。一个类应该只关注它要做什么。如果你有一个管理人员的单例,它应该只管理人员,而不是如何/何时创建。
  • 它们本质上会导致代码紧密耦合。这使得伪造或嘲笑它们进行单元测试变得非常困难。
  • 它们在应用程序的生存期内携带状态(对于有状态单例)。
    • 它使单元测试变得困难,因为您可能最终会遇到需要订购测试的情况,这是一种无稽之谈。根据定义,每个单元测试都应该彼此独立。
    • 此外,它使代码的可预测性降低。

好吧,所以单例很糟糕。但是你应该使用什么呢?

使用单个实例而不是单一实例

单例只是一种特定类型的单个实例,可以使用其类方法到达任何地方。如果删除此第二个要求,则会删除许多问题。但是,如何处理单个实例呢?

一种可能的方法是使用工厂和依赖注入来管理单个实例(这将是未来文章的主题)。

让我们举个例子来理解:

  • 您有一个需要唯一数据库连接实例的 PersonBusiness 类。
  • PersonBusiness 将具有 DatabaseConnection 属性,而不是使用单例来获取此连接。
  • 此属性将由其构造函数在 PersonBusiness 的实例化时注入。当然,您可以注入任何类型的数据库连接:
    • 适用于您的开发环境的 MysqlDatabaseConnection
    • 面向生产环境的 OracleDatabaseConnection
    • 用于单元测试的模拟数据库连接
  • 在此阶段,没有任何操作可以阻止数据库连接是唯一的。这就是工厂有用的地方。您将PersonBusiness的创建委托给工厂,该工厂还负责数据库连接的创建:
    • 它选择要创建的连接类型(例如,使用指定连接类型的属性文件)
    • 它确保数据库连接是唯一的。

如果你不明白我刚才说了什么,看看下一个java示例,然后再次重读这部分,它应该更全面。否则,请随时告诉我。

下面是一个Java中的例子,其中工厂创建了一个MysqlDatabaseConnection,但你可以想象一个更复杂的工厂,它根据属性文件或环境变量决定连接的类型。

An interface that represents a database connection
public interface DatabaseConnection {public void executeQuerry(String sql);
}A concrete implementation of this interface
In this example it's for a mysql database connection
public class MysqlDatabaseConnection implements DatabaseConnection {public MysqlDatabaseConnection() {// some stuff to create the database connection}public void executeQuerry(String sql) {// some stuff to execute a SQL query// on the database}
}Our business class that needs a connection
public class PersonBusiness {DatabaseConnection connection;//dependency injection using the constructor// it is a singleton because the factory that//creates a PersonBusiness object ensure that//UniqueDatabaseConnection has only one instancePersonBusiness(DatabaseConnection connection){this.connection = connection;}//a method that uses the injected singletonpublic void deletePerson(int id){connection.executeQuerry("delete person where id="+id);}
}A factory that creates business classes
 with a unique MysqlDatabaseConnection
public class Factory {private static MysqlDatabaseConnection databaseConnection = new MysqlDatabaseConnection();public static MysqlDatabaseConnection getUniqueMysqlDatabaseConnection(){return databaseConnection;}public static PersonBusiness createPersonBusiness(){//we inject a MysqlDataConnection but we could chose//another connection that implements the DatabaseConnection//this is why this is a loose couplingreturn new PersonBusiness(databaseConnection);}}

这不是一个很好的例子,因为PersonBusiness可以有一个实例,因为它没有状态。但你可以想象,有一个ContractBusiness和一个HouseBusiness也需要那个独特的DatabaseConnection。

不过,我希望你看到,使用依赖注入+ 工厂,你最终会在你的业务类中得到一个数据库连接的单个实例,就像你使用了一个单例一样。但这一次,它是一个松散的耦合,这意味着你可以很容易地使用MockDatabaseConnection来测试PersonBusiness类,而不是使用MysqlDatabaseConnection。

此外,很容易知道PersonBusiness正在使用DatabaseConnection。你只需要查看类的属性,而不是类的2000行代码中的一行(好吧,想象一下这个类有很多函数,整体需要2000行代码)。

这种方法被大多数Java框架(Spring,Hibernate…)和Java容器(EJB容器)使用。它不是真正的单例,因为如果需要,您可以多次实例化类,并且无法从任何地方获取实例。但是,如果您仅通过工厂/容器创建实例,则最终会在代码中获得该类的唯一实例。

注意:我认为Spring框架非常令人困惑,因为它的“单例”范围只是一个实例。我花了一些时间才明白,这不是一个真正的GoF的单例。

一些想法

当涉及到全局状态时,单个实例具有与单例相同的缺点。您应该避免使用单个实例在不同类之间共享数据! 我看到的唯一例外是缓存:

  • 想象一下,您有一个交易应用程序,每秒进行数百次调用,它只需要具有最后几分钟的股票价格。您可以使用在交易业务类之间共享的单个实例 (StockPriceManager),每个需要价格的函数都将从缓存中获取它。如果价格已过时,缓存将刷新它。在这种情况下,紧密耦合的缺点值得在性能上获得收益。但是,当你因为这个全局状态而需要理解生产中的错误时,你会哭泣(我去过那里,这并不好笑)。

我告诉您使用单实例方法而不是单例,但有时当您在所有类中都需要此对象时,值得使用真正的单例。例如,当您需要记录以下内容时:

  • 每个类都需要记录,并且此日志类通常是唯一的(因为日志写在同一个文件中)。由于所有类都使用 log 类,因此您知道每个类都与此日志类具有隐式依赖关系。此外,这不是业务需求,因此对日志进行单元测试“不太重要”(我感到羞耻)。

编写单例比使用依赖关系注入编写单个实例更容易。对于快速而肮脏的解决方案,我将使用单例。对于一个长期耐用的解决方案,我将使用单个实例。由于大多数应用程序都基于框架,因此单个实例的实现比从头开始更容易(假设您知道如何使用框架)。

如果您想了解有关单例的更多信息:

  • 在 stackexchange 上,关于单例模式的缺点也有一个非常好的答案。

真实示例

单实例模式使用工厂。如果使用可实例化工厂,则可能需要确保此工厂是唯一的。更广泛地说,当您使用工厂时,您可能希望它是唯一的,以避免2个工厂实例相互混淆。你可以使用“元工厂”来构建唯一的工厂,但你最终会遇到“元工厂”的相同问题。因此,执行此操作的唯一方法是使用单例创建工厂。

旧图形库AWT中的java.awt.Toolkit就是这种情况。此类提供了一个 getDefaultToolkit() 方法,该方法提供唯一的 Toolkit 实例,这是获取实例的唯一方法。使用此工具包(这是一个工厂),您可以创建一个窗口,一个按钮,一个复选框…

但是,您也可以遇到单例以解决其他问题。当您需要用Java监视系统时,必须使用java.lang.Runtime类。我想这个类必须是唯一的,因为它表示进程的全局状态(环境变量)。如果我引用java API:

“每个 Java 应用程序都有一个类 Runtime 实例,该实例允许应用程序与运行应用程序的环境进行交互。当前运行时可以从 getRuntime 方法获取。

原型模式

我在整个春季都使用过原型,但我从来没有需要使用自己的原型。此模式旨在通过复制而不是构造函数来构建对象。以下是GoF给出的定义:

“使用原型实例指定要创建的对象类型,并通过复制此原型来创建新对象。

和GoF一样,我不理解他们的句子(这是因为英语不是我的母语吗?)。如果你像我一样,这里有另一个解释:如果你不想或不能使用类的构造函数,原型模式允许你通过复制已经存在的实例来创建这个类的新实例。

让我们看一下使用UML图的正式定义:

原型模式

在此图中

  • 原型是一个定义函数 clone() 的接口
  • 一个真正的原型必须实现这个接口,并实现 clone() 函数来返回自身的副本。

开发人员必须实例化一次 ConcretePrototype。然后,他将能够通过以下方式创建ConcretePrototype的新实例:

  • 使用 clone() 函数复制第一个实例
  • 或者使用(再次)构造函数创建混凝土原型。

何时使用原型?

根据Gof的说法,应该使用原型:

  • 当系统应该独立于其产品的创建,组合和表示方式

  • 当要实例化的类是在运行时指定的,例如,通过动态加载

  • 以避免构建与产品类层次结构平行的工厂类层次结构

  • 当类的实例可以具有只有几种不同的状态组合之一时。安装相应数量的原型并克隆它们可能比每次都使用适当的状态手动实例化类更方便。

未知类的动态加载是非常罕见的情况,如果需要复制动态加载的实例,则更是如此。

这本书写于1994年。现在,你可以通过使用依赖注入来“避免构建工厂的类层次结构”(同样,我将在以后的文章中介绍这个奇妙的模式)。

在我看来,最常见的情况是创建有状态实例比复制现有实例更昂贵,并且您需要创建大量此对象。例如,如果创建需要:

  • 从数据库连接获取数据,
  • 从系统(通过系统调用)或文件系统获取数据,
  • 从另一台服务器获取数据(使用套接字,Web服务或其他),
  • 计算大量数据(例如,如果需要对数据进行排序),
  • 做任何需要时间的事情。

该对象必须是有状态的,因为如果它没有状态,则 Singleton(或单个实例)将完成该操作。

还有另一个用例。如果您有一个可变的实例,并且您希望将其提供给代码的另一部分,出于安全原因,您可能希望提供副本而不是真实实例,因为客户端代码可以修改此实例,并对使用它的代码的其他部分产生影响。

Java 实现

让我们看一个Java中的简单示例:

  • 我们有一个汽车比较器商务舱。此类包含比较 2 辆车的函数。
  • 要实例化 CarComparator,构造函数需要从数据库中加载默认配置来配置汽车比较算法(例如,在油耗上比速度或价格更重要)。
  • 此类不能是单一实例,因为配置可以由每个用户修改(因此每个用户都需要自己的实例)。
  • 这就是为什么我们只使用昂贵的构造函数创建一次实例。
  • 然后,当客户需要 CarComparator 的实例时,他将获得第一个实例的副本。
//The Prototype interface
public interface Prototype {Prototype duplicate();
}//The class we want to duplicate
public class CarComparator implements Prototype{private int priceWeigth;private int speedWeigth;private int fuelConsumptionWeigth;//a constructor that makes costly calls to a database//to get the default weigthspublic CarComparator(DatabaseConnection connect){//I let you imagine the costly calls to the database}//A private constructor only use to duplicate the objectprivate CarComparator(int priceWeigth,int speedWeigth,int fuelConsumptionWeigth){this.priceWeigth=priceWeigth;this.speedWeigth=speedWeigth;this.fuelConsumptionWeigth=fuelConsumptionWeigth;}//The prototype method@Overridepublic Prototype duplicate() {return new CarComparator(priceWeigth, speedWeigth, fuelConsumptionWeigth);}int compareCars(Car first, Car second){//some kickass and top secret algorithm using the weigths}The setters that lets the possibility to modify the algorithm behaviourpublic void setPriceWeigth(int priceWeigth) {this.priceWeigth = priceWeigth;}public void setSpeedWeigth(int speedWeigth) {this.speedWeigth = speedWeigth;}public void setFuelConsumptionWeigth(int fuelConsumptionWeigth) {this.fuelConsumptionWeigth = fuelConsumptionWeigth;}// A factory that creates a CarComparator instance using
// constructors then it creates the others by duplication.
// When a client ask for a CarComparator
// he gets a duplicatepublic class CarComparatorFactory {CarComparator carComparator;public BusinessClass (DatabaseConnection connect) {//We create one instance of CarComparatorcarComparator = new CarComparator(connect);}//we duplicate the instance so that//the duplicated instances can be modifiedpublic CarComparator getCarComparator(){return carComparator.duplicate();}}

如果你看下一部分,你会发现我本可以使用正确的Java接口制作一个更简单的代码,但我希望你理解一个原型。

在此示例中,在启动时,将使用数据库中的默认配置创建原型,并且每个客户端将使用工厂的 getCarComparator() 方法获取此实例的副本。

真实示例

Java API提供了一个名为Cloneable的原型接口。此接口定义了一个 clone() 函数,具体原型需要实现该函数。Java API 中的许多 Java 类都实现了此接口,例如来自集合 API 的集合。使用ArrayList,我可以克隆它并获取一个新的ArrayList,其中包含与原始数组相同的数据:

// Let's initialize a list
// with 10  integers
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 10; i++) {list.add(i);
}
System.out.println("content of the list "+list);//Let's now duplicate the list using the prototype method
ArrayList<Integer> duplicatedSet = (ArrayList<Integer>) list.clone();
System.out.println("content of the duplicated list "+duplicatedSet);

此代码的结果是:

集合的内容[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
重复集合的内容[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

生成器模式

生成器模式对于分解代码非常有用。根据GoF,这种模式:

“将复杂对象的构造与其表示分开,以便相同的构造过程可以创建不同的表示。

它的目标随着时间的推移而变化,大多数时候它被用来避免创建许多构造函数,这些构造函数仅因参数数量而异。这是一种避免伸缩构造函数反模式的方法。

它解决的问题

让我们看一下此模式解决的问题。想象一个具有5个属性的类人:

  • 年龄
  • 重量
  • 高度
  • 编号
  • 名字

我们希望能够构建一个人知道:

  • 只有这个年龄,
  • 或只有这个年龄和体重,
  • 或只有这个年龄,体重和身高,
  • 或仅此年龄,体重,身高和ID
  • 或仅此年龄,体重,身高,ID和姓名

在java中,我们可以写这样的东西。

public class Person {private int age;private int weigth;private int height;private int id;private String name;//Here comes the telescopic constructorspublic Person() {//some stuff}public Person(int age) {this();//we're using the previous constructorthis.age = age;}public Person(int age, int weigth) {this(age);//we're using the previous constructorthis.weigth = weigth;}public Person(int age, int weigth, int height) {this(age, weigth);//we're using the previous constructorthis.height= height;}   public Person(int age, int weigth, int height,int id) {this(age, weigth, height);//we're using the previous constructorthis.id = id;}   public Person(int age, int weigth, int height,int id,String name) {this(age, weigth, height, id);//we're using the previous constructorthis.name = name;}   }

为了处理这个简单的需求,我们刚刚创建了5个构造函数,其中有很多代码。我知道java是一种非常冗长的语言(内部巨魔),但是如果有一种更干净的方式呢?

此外,使用这种伸缩式方法,代码很难阅读。例如,如果您阅读以下代码,您是否可以轻松理解参数是什么?他们是年龄,身份还是身高?

Person person1 = new Person (45, 45, 160, 1);Person person2 = new Person (45, 170, 150);

下一个问题:假设你现在希望能够创建一个包含你获得的每一个可能的信息部分的人,例如:

  • 一个年龄
  • 重量
  • 年龄和体重
  • 一个年龄和一个ID,
  • 一个年龄,一个体重和一个名字,

使用构造函数方法,您最终将获得 120 个构造函数 (5! = 120)。你还有另一个问题,如何处理使用相同类型的不同构造函数?例如,您如何同时拥有两者:

  • age 和 weigth(为 2 int)的构造函数,以及
  • 年龄和 id 的构造函数(也是 2 int)?

您可以使用静态工厂方法,但它仍然需要 120 个静态工厂方法。

这就是建筑商发挥作用的地方!

此模式的思想是模拟命名的可选参数。这些类型的参数在某些语言(如python)中是本地可用的。

由于UML版本非常复杂(我认为),我们将从一个简单的java示例开始,并以UML正式定义结束。

一个简单的 java 示例

在此示例中,我有一个人,但这次 id 字段是必填字段,其他字段是可选的。

我创建了一个构建器,以便开发人员可以根据需要使用可选字段。

//the person class
/if you look at its constructor
/it requieres a builder
public class Person {private final int id;// mandatoryprivate int weigth;// optionalprivate int height;// optionalprivate int age;// optionalprivate String name;// optionalpublic Person(PersonBuilder builder) {age = builder.age;weigth = builder.weigth;height = builder.height;id = builder.id;name = builder.name;}
}
//the builder that
/takes care of
/Person creation
public class PersonBuilder {// Required parametersfinal int id;// Optional parameters - initialized to default valuesint height;int age;int weigth;String name = "";public PersonBuilder(int id) {this.id = id;}public PersonBuilder age(int val) {age = val;return this;}public PersonBuilder weigth(int val) {weigth = val;return this;}public PersonBuilder height(int val) {height = val;return this;}public PersonBuilder name(String val) {name = val;return this;}public Person build() {return new Person(this);}
}//Here is how to use the builder in order to build a Person
//You can see how readable is the code
public class SomeClass {public void someMethod(int id){PersonBuilder pBuilder = new PersonBuilder(id);Person robert = pBuilder.name("Robert").age(18).weigth(80).build();//some stuff}public void someMethodBis(int id){PersonBuilder pBuilder = new PersonBuilder(id);Person jennifer = pBuilder.height(170).name("Jennifer").build();//some stuff}}

在此示例中,我假设类 Person 和 PersonBuilder 位于同一个包中,这允许构建器使用 Person 构造函数,并且包外的类必须使用 PersonBuilder 来创建 Person。

这个PersonBuilder有2种方法,一种用于构建人的一部分,另一种用于创建人。一个人的所有属性只能由同一包中的类修改。我应该使用getter和setter,但我想有一个简短的例子。您看到使用构建器的部分易于阅读,我们知道我们正在创建

  • 一个名叫罗伯特的人,他18岁,体重80岁,
  • 另一个名叫詹妮弗的人,她长170岁。

这种技术的另一个优点是,您仍然可以创建不可变的对象。在我的示例中,如果我不在 Person 类中添加公共 setter,则 Person 实例是不可变的,因为包外部的任何类都不能修改其属性。

正式定义

现在让我们看一下UML:

GoF 的生成器模式

这张图真的很抽象,GoF的构建器有:

  • 一个生成器界面,用于指定用于创建 Product 对象的部件的函数。在我的图中,只有一个方法,buildPart()。
  • 一个 ConcreteBuilder,它通过实现 Builder 接口来构造和组装产品的部件。
  • 控制器:它使用生成器界面构造产品。

根据 GoF,此模式在以下情况下很有用:

• 创建复杂对象的算法应独立于构成对象的部件及其组装方式。
•构造过程必须允许对构造的对象进行不同的表示。

GoF给出的例子是一个TextConverter构建器,它有3个实现要构建:ASCIIText或TeXText或TextWidget。3 个生成器实现(ASCIIConverter、TeXConverter 和 TextWidgetConverter)具有相同的函数,除了 createObject() 函数不同(这就是为什么此函数不在此模式的接口中)。使用此模式,转换文本的代码(控制器)使用生成器界面,因此它可以轻松地从 ASCII 切换到 TeX 或 TextWidget。此外,您可以添加新的转换器,而无需修改其余代码。在某种程度上,这种模式非常接近国家模式。

但这个问题是罕见的。

这种模式的另一个用途是由Java开发人员Joshua Bloch推广的,他领导了许多Java API的构建。他在《*有效的Java》*一书中写道:

“在面对许多构造函数参数时考虑构建器”

大多数情况下,该模式用于此用例。对于此问题,您不需要生成器接口、多个生成器实现或控制器。在我的java示例中,大多数时候你会发现只有一个具体的构建器。

然后,UML 变得更加容易:

约书亚·布洛赫的建造者模式

在此图中,ConcreteBuilder具有多个函数来创建产品的每个部分(但我只是放了一个,buildPart(),因为我懒惰)。这些函数返回 ConcreteBuilder,以便您可以链接函数调用,例如:builder.buildPart1().buildPart7().createObject()。构建器有一个 createObject() 方法,用于在您不需要添加更多部件时创建产品。

总而言之,当您的类具有许多可选参数并且不希望最终得到许多构造函数时,构建器模式是一个不错的选择。虽然这个模式不是为这个问题而设计的,但它大部分时间都用于这个问题(至少在Java中)。

真实示例

Java API中最常见的例子是StringBuilder。使用它,您可以创建一个临时字符串,向其追加新字符串,完成后,您可以创建一个真正的String对象(这是不可变的)。

StringBuilder sBuilder = new StringBuilder();
String example = sBuilder.append("this").append(" is").append(" an").append(" example").toString();
System.out.println(example);

结论

您现在应该对创建模式有了更好的了解。如果您需要记住一件事,那就是使用单个实例而不是单例。请记住生成器模式(Joshua Bloch的版本),如果您正在处理可选参数,它可能会很有用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/315235.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[linux网络编程]UDP协议和TCP协议的使用

目录 看以下内容前&#xff0c;你要先了解main函数带参数有什么用、 了解socket的相关函数接口 如果不了解socket的相关函数接口请先看我这篇文章 main函数带参数有什么用 UDP udp_server 1.生成socket文件描述符 2.填充sockaddr_in信息 3.bind 4.发&#xff08;收&…

渗透之sql注入练气篇

sql注入产生的原因&#xff1a; 由于程序过滤不严谨&#xff0c;导致用户有一些异常输入&#xff0c;最终触发数据库的查询。所以会出现sql注入这个问题。有些恶意的人就会利用这些信息导致数据库泄露。 注意&#xff1a;一般我们存在注入点我们会查询管理员的账号和密码&#…

如何30天快速掌握键盘盲打

失业后在家备考公务员&#xff0c;发现了自己不正确的打字方式&#xff0c;决定每天抽出一点时间练习打字。在抖音上看到一些高手的飞速盲打键盘后&#xff0c;觉得使用正确的指法打字是很必要的。 练习打字&#xff0c;掌握正确的键盘指法十分关键。 练习打字的第一步是找到…

ElasticSearch批处理

在刚才的新增当中&#xff0c;我们是一次新增一条数据。那么如果你将来的数据库里有数千上万的数据&#xff0c;你一次新增一个&#xff0c;那得多麻烦。所以我们还要学习一下批量导入功能。 也就是说批量的把数据库的数据写入索引库。那这里的需求是&#xff0c;首先利用mybat…

C++中布隆过滤器

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;linux &#x1f525;座右铭&#xff1a;“不要等到什么都没有了…

【单链表专题】

单链表专题 1.引入2.链表2.1链表的关系2.2链表的结构 3.代码实现链表 1.引入 对于顺序表而言 中间/头部的插⼊删除&#xff0c;时间复杂度为O(N)增容需要申请新空间&#xff0c;拷⻉数据&#xff0c;释放旧空间。会有不小的消耗。增容⼀般是呈2倍的增⻓&#xff0c;势必会有⼀…

Github进行fork后如何与原仓库同步[解决git clone 太慢的问题]

前言 fork了一个仓库以后怎么同步源仓库的代码&#xff1f; 先说一下git clone太慢的问题&#xff0c;可以通过代理拉取代码&#xff0c;具体请看&#xff1a; https://gitclone.com/ 步骤 1、执行命令 git remote -v 查看你的远程仓库的路径。 以一个实际例子说明&#x…

[Spring Cloud] (4)搭建Vue2与网关、微服务通信并配置跨域

文章目录 前言gatway网关跨域配置取消微服务跨域配置 创建vue2项目准备一个原始vue2项目安装vue-router创建路由vue.config.js配置修改App.vue修改 添加接口访问安装axios创建request.js创建index.js创建InfoApi.js main.jssecurityUtils.js 前端登录界面登录消息提示框 最终效…

微信小程序使用echarts组件实现饼状统计图功能

微信小程序使用echarts组件实现饼状统计图功能 使用echarts实现在微信小程序中统计图的功能&#xff0c;具体的实现步骤思路可进我主页查看我的另一篇博文https://blog.csdn.net/weixin_45465881/article/details/138171153进行查看&#xff0c;本篇文章主要使用echarts组件实…

SpringBoot的自动装配原理

SpringBoot自动装配原理 SpringBoot的启动类上有一个注解&#xff1a;SpringBootApplication 。该注解是三个注解的复合注解。 1.SpringBootConfiguration 注解 点进SpringBootConfiguration 注解&#xff0c;可以发现其核心注解为Configuration注解&#xff1a; Configuration…

python文件 成绩分析

‘’文件score.txt中存储了学生的考试信息,内容如下 小明,98 小刚,90 小红,91 小王,98 小刘,80 请写代码,读取文件数据,并进行如下分析 最高分和最低分分别是多少&#xff1f;得最高分的学生有几个&#xff1f; 得最低分的学生有几个平均分是多少&#xff1f; ‘’’ def rea…

Web3技术解析:区块链在去中心化应用中的角色

引言 在过去几年中&#xff0c;Web3技术已经成为了互联网领域的一个热门话题。作为区块链技术的延伸&#xff0c;Web3不仅仅是数字货币的代名词&#xff0c;更是一个能够为各种应用提供去中心化解决方案的强大工具。本文将深入探讨区块链在Web3去中心化应用中的关键角色&#…

Python_AI库 Matplotlib的应用简例:绘制与保存折线图

本文默认读者已具备以下技能&#xff1a; 熟悉Python基础语法&#xff0c;以自行阅读python代码块熟悉Vscode或其它编辑工具的应用 在数据可视化领域&#xff0c;Matplotlib无疑是一个强大的工具。它允许我们创建各种静态、动态、交互式的可视化图形&#xff0c;帮助我们更好…

企业工厂如何逆风翻盘:VR全景打破多重桎梏

现阶段&#xff0c;制造业工厂面临的困境&#xff0c;就是用着上百万的设备&#xff0c;却赚着几毛钱的利润。传统的工厂参观方式也存在着很多的局限性&#xff0c;例如时间上不方便、不能实地参访、生产线具有隐患等&#xff0c;都会使得参观者不能深入地了解工厂的生产环境和…

Android Studio实现内容丰富的安卓养老平台

获取源码请点击文章末尾QQ名片联系&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动 158安卓养老 1.开发环境 后端用springboot框架&#xff0c;安卓的用android studio开发android stuido3.6 jak1.8 idea mysql tomcat 2.功能介绍 安卓端&#xff1a; 1.注册登…

实验7:路由冗余协议HSRP配置管理(课内实验以及解答)

实验目的及要求&#xff1a; 理解首跳冗余协议&#xff08;FHRP&#xff09;的工作原理&#xff0c;掌握热备份路由器协议 (HSRP)&#xff08;思科私有协议&#xff09;原理和配置。能够实现网络终端设备虚拟网关的配置和网络故障的灵活切换&#xff0c;完成相应网络的联通性测…

斐波那契数列

感谢大佬的光临各位&#xff0c;希望和大家一起进步&#xff0c;望得到你的三连&#xff0c;互三支持&#xff0c;一起进步 个人主页&#xff1a;LaNzikinh-CSDN博客 收入专栏:初阶数据结构_LaNzikinh篮子的博客-CSDN博客 文章目录 前言一.斐波那契数二.改循环三.尾递归总结 前…

智能外呼文书送达系统,智慧检务解决方案

在全民数字化改革中&#xff0c;司法体制改革不断推进的大背景下&#xff0c;合肥高新技术产业开发区人民检察院的内设机构改革已完成落地&#xff0c;刑事案件审查办理迎来了重大改变&#xff0c;需要检察官对现有办案方式方法做出相应的调整&#xff0c;将主要精力从大量的重…

初始计算机网络

TCP/IP TCP/IP模型 TCP/IP网络模型&#xff1a;对于不同设备之间的通信&#xff0c;就需要网络通信&#xff0c;而设备是多样性的&#xff0c;所以要兼容多种多样的设备&#xff0c;就协商出了一套通用的网络协议。 TCP/IP分层 这个网络协议是分层的&#xff0c;每一层都有…

【EI会议|投稿优惠】2024年机械应用与能源动力国际会议(ICMAEP 2024)

2024 International Conference on Mechanical Applications and Energy Power 一、大会信息 会议名称&#xff1a;2024年机械应用与能源动力国际会议 会议简称&#xff1a;ICMAEP 2024 收录检索&#xff1a;提交Ei Compendex,CPCI,CNKI,Google Scholar等 会议官网&#xff1a;…