工厂模式 构建者模式_实践中的构建者模式

工厂模式 构建者模式

我将不深入讨论该模式,因为已经有大量的文章和书籍对此进行了详细的解释。 相反,我将告诉您为什么以及何时应该考虑使用它。 但是,值得一提的是,这种模式与《 四人帮》一书中介绍的模式有些不同。 虽然原始模式着重于抽象化构造步骤,以便通过更改所使用的构建器实现可以得到不同的结果,但本文中说明的模式用于消除不必要的复杂性,该复杂性是由多个构造函数,多个可选参数以及过度使用二传手。

假设您有一个包含大量属性的类,例如下面的User类。 假设您要使该类不可变(顺便说一句,

除非有充分的理由不这样做,否则您应该始终努力。 但是我们将在另一篇文章中讨论)。

public class User {private final String firstName;    //requiredprivate final String lastName;    //requiredprivate final int age;    //optionalprivate final String phone;    //optionalprivate final String address;    //optional
...
}

现在,想象一下类中的某些属性是必需的,而其他属性是可选的。 您将如何构建此类的对象? 所有属性都声明为final,因此您必须在构造函数中全部设置它们,但您还想让此类的客户端有机会忽略可选属性。

第一个有效的选择是拥有一个仅将必需属性作为参数的构造函数,一个将所有必需属性加上第一个可选属性作为参数的构造函数,而另一个则将两个可选属性作为参数,依此类推。 看起来像什么? 像这样:

public User(String firstName, String lastName) {this(firstName, lastName, 0);}public User(String firstName, String lastName, int age) {this(firstName, lastName, age, '');}public User(String firstName, String lastName, int age, String phone) {this(firstName, lastName, age, phone, '');}public User(String firstName, String lastName, int age, String phone, String address) {this.firstName = firstName;this.lastName = lastName;this.age = age;this.phone = phone;this.address = address;}

用这种方法构造类的对象的好处是它可以工作。 但是,这种方法的问题应该很明显。 当您只有几个属性时,没什么大不了的,但是随着数量的增加,代码变得更难以阅读和维护。 更重要的是,对于客户而言,代码变得越来越难。 我应该作为客户端调用哪个构造函数? 一个带有2个参数? 一个3? 不传递显式值的那些参数的默认值是多少? 如果我想为地址设置一个值而不是年龄和电话该怎么办? 在那种情况下,我将不得不调用接受所有参数的构造函数,并为那些我不在乎的参数传递默认值。 此外,具有相同类型的几个参数可能会造成混淆。 第一个String是电话号码还是地址?

那么对于这些​​情况我们还有什么选择呢? 我们总是可以遵循JavaBeans约定,在该约定中,我们有一个默认的no-arg构造函数,并且每个属性都有设置器和获取器。 就像是:

public class User {private String firstName; // requiredprivate String lastName; // requiredprivate int age; // optionalprivate String phone; // optionalprivate String address;  //optionalpublic String getFirstName() {return firstName;}public void setFirstName(String firstName) {this.firstName = firstName;}public String getLastName() {return lastName;}public void setLastName(String lastName) {this.lastName = lastName;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}
}

这种方法似乎更易于阅读和维护。 作为客户端,我可以只创建一个空对象,然后仅设置我感兴趣的属性。那么这有什么问题呢? 该解决方案有两个主要问题。 第一个问题与使此类的实例处于不一致状态有关。 如果要创建一个具有其所有5个属性值的User对象,则在调用所有setX方法之前,该对象将不具有完整状态。 这意味着客户端应用程序的某些部分可能会看到此对象,并假定该对象已被构造,而实际上并非如此。 这种方法的第二个缺点是现在User类是可变的。 您失去了不可变对象的所有好处。

幸运的是,对于这些情况,还有第三种选择,即构建器模式。 解决方案将如下所示。

public class User {private final String firstName; // requiredprivate final String lastName; // requiredprivate final int age; // optionalprivate final String phone; // optionalprivate final String address; // optionalprivate User(UserBuilder builder) {this.firstName = builder.firstName;this.lastName = builder.lastName;this.age = builder.age;this.phone = builder.phone;this.address = builder.address;}public String getFirstName() {return firstName;}public String getLastName() {return lastName;}public int getAge() {return age;}public String getPhone() {return phone;}public String getAddress() {return address;}public static class UserBuilder {private final String firstName;private final String lastName;private int age;private String phone;private String address;public UserBuilder(String firstName, String lastName) {this.firstName = firstName;this.lastName = lastName;}public UserBuilder age(int age) {this.age = age;return this;}public UserBuilder phone(String phone) {this.phone = phone;return this;}public UserBuilder address(String address) {this.address = address;return this;}public User build() {return new User(this);}}
}

值得注意的几个要点:

  • User构造函数是私有的,这意味着不能从客户端代码直接实例化此类。
  • 该类再次是不可变的。 所有属性都是最终属性,它们是在构造函数上设置的。 此外,我们仅为他们提供吸气剂。
  • 构建器使用Fluent Interface惯用法使客户机代码更具可读性(我们稍后将看到一个示例)。
  • 构建器构造函数仅接收必需的属性,并且此属性是在构建器上唯一定义为“最终”的属性,以确保在构造函数上设置其值。

使用构建器模式具有我一开始提到的前两种方法的所有优点,但没有任何缺点。 客户端代码更易于编写,更重要的是易于阅读。 我听到的关于该模式的唯一批评是,您必须在构建器上复制类的属性。 但是,考虑到构建器类通常是它构建的类的静态成员类,因此它们可以很容易地一起进化。

现在,尝试创建新的User对象的客户端代码如何? 让我们来看看:

public User getUser() {return newUser.UserBuilder('Jhon', 'Doe').age(30).phone('1234567').address('Fake address 1234').build();}

很整洁,不是吗? 您可以用1行代码构建一个User对象,最重要的是,它很容易阅读。 此外,您要确保每当获得此类的对象时,都不会处于不完整状态。

这种模式非常灵活。 通过在对“ build”方法的调用之间更改构建器属性,可以使用单个构建器来创建多个对象。 构建器甚至可以自动完成每次调用之间生成的某些字段,例如ID或序列号。

重要的一点是,就像构造器一样,构造器可以对其参数施加不变性。 生成方法可以检查这些不变量,如果它们无效则抛出IllegalStateException
将参数从构建器复制到对象之后,必须检查它们,并在对象字段而不是构建器字段上检查它们,这一点至关重要。 这样做的原因是,由于构建器不是线程安全的,因此,如果我们在实际创建对象之前检查参数,则可以在检查参数和复制参数之间由另一个线程更改其值。 这一段时间称为“漏洞窗口”。 在我们的用户示例中,这可能类似于以下内容:

public User build() {User user = new user(this);if (user.getAge() 120) {throw new IllegalStateException(“Age out of range”); // thread-safe}return user;
}

以前的版本是线程安全的,因为我们首先创建用户,然后检查不可变对象上的不变量。 以下代码在功能上看起来相同,但不是线程安全的,因此应避免执行以下操作:

public User build() {if (age 120) {throw new IllegalStateException(“Age out of range”); // bad, not thread-safe}// This is the window of opportunity for a second thread to modify the value of agereturn new User(this);
}

这种模式的最后一个优点是可以将构建器传递给某个方法,以使该方法能够为客户端创建一个或多个对象,而该方法无需知道有关如何创建对象的任何种类的细节。 为此,通常需要一个简单的界面,例如:

public interface Builder {T build();
}

在前面的User示例中, UserBuilder类可以实现Builder <User> 。 然后,我们可能会有类似的内容:

UserCollection buildUserCollection(Builder<? extends User> userBuilder){...}

好吧,那是一篇相当长的第一篇文章。 综上所述,对于具有多个参数的类(不是一门确切的科学方法,但我通常将4个属性用作使用该模式的一个很好的指标),Builder模式是一个很好的选择,尤其是当这些参数中的大多数是可选的。 您将获得易于阅读,编写和维护的客户端代码。 此外,您的类可以保持不变,这使您的代码更安全。

更新 :如果将Eclipse用作IDE,事实证明您有很多插件可以避免该模式随附的大多数样板代码。 我见过的三个是:

  • http://code.google.com/p/bpep/
  • http://code.google.com/a/eclipselabs.org/p/bob-the-builder/
  • http://code.google.com/p/fluent-builders-generator-eclipse-plugin/

我没有亲自尝试过其中任何一个,因此我无法真正做出明智的决定。 我认为其他IDE应该也存在类似的插件。

参考:开发 人员实践中来自JCG合作伙伴 Jose Luis 的构建者模式, 它应该是博客。

翻译自: https://www.javacodegeeks.com/2013/01/the-builder-pattern-in-practice.html

工厂模式 构建者模式

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

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

相关文章

python 反复访问迭代器iter,反复使用next

python 反复访问迭代器iter&#xff0c;反复使用next test [1, 2, 3]# 定义迭代器 iter_test iter(test)# 使用for循环访问迭代器 # 由于next()只向前走&#xff0c;所以当跌代到test最后一个值3之后&#xff0c;就会爆发异常StopIteration # 这里使用了一个try语句&#xf…

PPT转PDF方法

引用&#xff1a; using Microsoft.Office.Interop.PowerPoint;方法&#xff1a; ///<summary> /// PPT转PDF ///</summary> ///<param name"sourcePath">源文件路径</param> ///<param name"targetPath&qu…

打印容器_3D打印:增材点阵结构在压力容器优化设计中的应用

“增材制造是未来制造业的发展趋势&#xff0c;其优势显而易见&#xff0c;它可以实现传统加工工艺难以制造的设计&#xff0c;比如复杂薄壁结构、点阵结构、一体化结构等。其中&#xff0c;点阵结构作为一种新型的轻量化结构&#xff0c;具有良好的比刚度、比强度等力学性能。…

Java平台模块系统公众审查未能通过

在过去的几周里&#xff0c;Java世界中的戏剧&#xff0c;阴谋和政治活动异常高涨&#xff0c;最终在本周的JSR 376 Java平台模块系统公共评审投票中达到了顶峰。 Java模块化&#xff08;包括Java平台模块系统&#xff08;JPMS&#xff09;&#xff09;可以说是JDK 9最重要的部…

区间素数个数查询

给出一个闭区间的两端l和r&#xff0c;输出[l,r]中素数的个数。 输入格式: 两个整数l和r&#xff0c;之间用空格隔开&#xff08;1<l<r<10000&#xff09;。 输出格式: 输出一个结果。 输入样例: 2 10输出样例: 4 import java.util.Scanner;public class Main {static…

jtabel 遍历_使用抽象表模型获取JTable中选定的行

在下面的示例中&#xff0c;TableModel更新Set checked在执行setValueAt()..相邻模型JList侦听表的模型并显示当前选定的行号。该示例假定所选行数与行数相比较小。注意使用TreeSet&#xff0c;其迭代器保留了元素的自然顺序。import java.awt.Dimension;import java.awt.Event…

neo4j cypher_优化Neo4j Cypher查询

neo4j cypher上周&#xff0c;我花了很多时间来尝试优化大约20个使用实时系统数据执行的灾难性的Cypher查询&#xff08;36866ms至155575ms&#xff09;。 经过一番尝试和错误&#xff0c;以及来自Michael的大量投入&#xff0c;我能够大致确定对查询进行哪些操作才能使它们性能…

Mac--PHP已经开启gd扩展验证码不显示

错误显示&#xff1a;Call to undefined function imagettftext() 原因&#xff1a; mac系统中自带的php的gd库中&#xff0c;缺少对freetype的支持&#xff0c;导致图片无法显示。 解决&#xff1a; 1 下载freetype&#xff0c;安装即可 下载地址&#xff1a;https://download…

cprintdialog预览_怎样用CPrintDialog来实现打印功能

把以下程序段加入你的程序把dc1当作Device Content.就任你打印了.注意别画错地方.自己换页.更绝的是可调用窗口的OnPrint或OnDraw成员函数直接打印.不用你去做行距多少啦!但别忘换页.// 打印// 建立打印对话框对象CPrintDialoghttp://www.gaodaima.com/?p65323怎样用CPrintDia…

容器中Java RAM的使用:不会丢失内存的5大技巧

在本文中&#xff0c;我们希望分享Java内存管理的细节和容器内部的弹性&#xff0c;这些细节乍一看并不明显。 在下面&#xff0c;您将找到要注意的问题列表以及即将发布的JDK版本中的重要更新&#xff0c;以及针对核心痛点的现有解决方法。 我们收集了5个最有趣&#xff0c;最…

G1垃圾收集器设计目标与改良手段【纯理论】

在之前已经详细对CMS垃圾回收器进行了学习&#xff0c;今天准备要学习另一个全新的垃圾收集器---G1&#xff08;Garbage First Collector 垃圾优先的收集器&#xff09;&#xff0c;说是一种全新的&#xff0c;其实G1垃圾收集器已经出现了N多年了&#xff0c;只是从发展到成熟是…

centos7安装es mysql_Centos7 安装MySQL详细步骤

Centos7 安装MySQL详细步骤首先在虚拟机中安装一个Centos7(VM虚拟机安装Centos7)1.1 MySQL安装1.1.1 下载wget命令yum -y install wget11.1.2 在线下载mysql安装包wget https://dev.mysql.com/get/mysql57-community-release-el7-8.noarch.rpm11.1.3 安装MySQLrpm -ivh mysql57…

Mac入门--通过homebrew下载过慢问题

使用国内的镜像替换homebrew镜像&#xff0c;对镜像进行加速源 原先我们执行brew命令安装的时候&#xff0c;跟3个仓库地址有关 1 brew.git 2 homebrew-core.git 3 homebrew-bottles 把三个仓库地址全部替换成国内Alibaba提供的地址 1 替换/还原brew.git仓库地址 # 替换成阿里…

在卷积层的运用_Conv 卷积层

onv 卷积层一.why CNN for image ? 1.Some Patterns are much smaller than the whole image.一些模式比起整张图片来说更小&#xff0c;即寻找模式不用看完整的图像。2.The same patterns appear in different regions.同一个模式可能会出现在不同的区域。3.Subsamping the p…

idea 编写javafx_用JavaFX编写图块引擎

idea 编写javafx随着JavaFX嵌入式版本的问世&#xff0c;我们的框架对于游戏开发变得越来越有趣&#xff0c;因为我们现在可以瞄准平板电脑和智能手机等小型消费类设备。 因此&#xff0c;我决定对JavaFX进行更多的游戏编写实验。 这次&#xff0c;我想使用Canvas对渲染进行更多…

C#/.Net操作MongoDBHelper类

先 NuGet两个程序集 1:MongoDB.Driver、 2:MongoDB.Bson namespace ConsoleApp1{ /// <summary> /// MongoDb帮助类 /// </summary> /// <summary> /// MongoDb帮助类 /// </summary> public class DB { private static readonly string connStr &q…

echo回声不能用了_已懂得用电子分频器,为何不继续加个效果器让音响效果更好?...

效果器是一种提供各种声场效果&#xff0c;并对声音信号在时间和频率等多方面多方位进行加工处理以产生特殊音响效果的周边设备&#xff0c;它广泛使用在电台、电视台的节目制作上。然而要充分发挥效果器的作用&#xff0c;使其获得满意的效果&#xff0c;还必须掌握正确的连接…

Spring Web-Flux – Cassandra后端的功能样式

在上一篇文章中&#xff0c;我介绍了Spring Web-Flux的基础知识&#xff0c;它表示Spring框架的Web层中的响应式支持。 我已经展示了使用Spring Data Cassandra并在Spring Web Layers中使用传统注释支持的端到端示例&#xff0c; 大致如下&#xff1a; ... import org.spring…

Mac入门--安装PHP扩展redis,swoole

1 php7以下可以通过pecl安装PHP扩展 安装redis扩展 pecl install redis 安装swoole扩展 pecl install swoole2 PHP7以上通过源码编译安装扩展 2.1 扩展安装包在官网上查找链接地址&#xff1a;http://pecl.php.net 2.2.1 在官网上直接下载上传的服务器上解压 2.2.2 通过wget下载…

商品pid是什么意思_0基础搞懂自动驾驶传统算法与深度学习的鸿沟-PID控制算法与MLP优化方法...

0基础搞懂自动驾驶传统算法与深度学习的鸿沟这个专题核心是要思考如何让rule-base的自动驾驶算法逐步提升为data-driven的算法&#xff0c;从而尽可能的提升软件的泛化性。数据驱动的逻辑代替控制&#xff0c;规划&#xff0c;定位&#xff0c;融合&#xff0c;感知以及替换整个…