Effective Java 1 用静态工厂方法代替构造器

知识点上本书需要会Java语法和lang、util、io库,涉及concurrent和function包。

内容上主要和设计模式相关,代码风格力求清晰+简洁,代码尽量复用,组件尽量少依赖,错误尽早发现。

第1个经验法则:用静态工厂方法代替构造器(consider static factory methods instead of constructors)

下面是Boolean的静态工厂方法:

一、静态工厂方法与构造器不同的第一大优势在于:它们有名称。

如果构造器的参数本身没有确切地描述正被返回的对象,那么具有适当名称的静态工厂会更容易使用,产生的客户端代码也更易于阅读。 什么意思呢?我们看一个例子:

假设我们有一个表示图书的类,它需要标题和作者作为创建对象的参数。

当我们想创建一本新书时,我们会这样调用构造器:

这个调用很直接,但方法名Book跟类名一样是个名词,没有传达出创建动作的任何特殊含义或上下文信息。

现在我们改用静态工厂方法来创建 Book 实例,并给这个方法一个有意义的名字,比如 createFromTitleAndAuthor ,代码看起来会是这样的:

创建同样一本《Effective Java》的书,现在代码变成了:

对比两种方法的效果,静态工厂方法 createFromTitleAndAuthor 直观地表明了这个方法是用来根据书的标题和作者创建 Book 对象的,相比构造器,它提供了更多的语境信息,使得代码的意图更加清晰,而无需查看方法的具体实现或文档注释。

所以:当一个类需要多个带有相同签名的构造器时,推荐用静态工厂方法代替构造器。

并且在语法上,当你尝试定义多个构造函数,而这些构造函数仅在参数顺序上有所不同时,Java 编译器会报错,因为它无法根据参数顺序唯一确定应该调用哪个构造函数。这是不合法的Java代码。

二、静态工厂方法与构造器不同的第二大优势在于:不必在每次调用它们的时候都创建一个新对象

静态工厂方法能够像单例模式一样为重复的调用返回相同对象。

在下面的例子中,每次调用 new Car() 都会创建一个新的。如果程序中频繁创建同一型号的车对象,这可能会导致不必要的资源消耗。

现在,我们修改 Car类代码。如果程序中频繁创建同一Car类,引入一个静态工厂方法 createCar ,它可以决定是否复用已有的同类对象或根据条件创建新对象:

在这个例子中,createCar方法首先检查是否有已经存在的同型号汽车对象存储在carPool中。如果有,则直接返回该对象,避免了重复创建;如果没有,则创建新的Car 实例并保存在池中,供后续可能的复用。这种方法在处理大量相同或相似配置对象的场景下,可以显著减少对象创建的开销,提高性能。

通过上述对比,可以看到静态工厂方法提供了更多的灵活性,允许我们在创建对象时实施逻辑判 断,比如复用对象、优化资源分配等,这是直接使用构造器所不具备的优势。尤其是在需要控制 对象实例化策略,如单例模式、多例模式或池化对象的场景下,静态工厂方法展现了其独特的价 值。

三、静态工厂方法与构造器不同的第三大优势在于:它们可以返回原返回类型的任何子类型的对象

这点其实就是说的多态。这种灵活性的一种应用是API可以返回对象,同时又不会使对象的类变成公有的。以这种方式隐藏实现类会使API变得非常简洁。

假设我们正在设计一个图书馆管理系统,其中有一个功能是管理不同类型的图书。图书可以是电 子书( EBook )、纸质书( Paperback )或其他未来可能增加的类型。为了保持API的简洁性和 未来的可扩展性,我们不希望客户端代码直接依赖于具体的图书实现类。

如果不使用静态工厂方法,客户端可能会这样直接通过构造器创建书籍对象:

这里的问题是, EBook 类必须是公共的,这暴露了系统的内部实现细节给客户端,降低了系统的封装性。

相反,如果用静态工厂方法,我们可以做到隐藏实现类,只暴露基类型(如Book类)给客户端:

现在,客户端代码通过工厂方法请求书籍,而无需了解具体的实现类:

通过上述例子,可以看到静态工厂方法如何帮助我们在API设计中实现了以下几点:

1. 隐藏实现细节:客户端不需要直接访问或依赖于具体的子类(如 EBook 或 Paperback), 这些类可以是包级私有或完全私有,从而增强了封装性。

2.简化API:API接口变得更加简洁,客户端仅需与 Book 类交互,无论底层实现如何变化。

3. 提高灵活性和扩展性:新增书籍类型(如有声书Audiobook )时,只需在工厂方法内部添加 新的分支逻辑,而无需修改客户端代码。

四、静态工厂的第四大优势在于:所返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值。

只要是已声明的返回类型的子类型,都是允许的。返回对象的类也可能随着发行版本的不同而不同。

想象一下,我们正在开发一个日志记录框架,该框架允许用户记录日志到不同媒介(控制台、文 件、数据库等)。框架定义了一个基础接口 Logger ,以及几个实现该接口的类(如 ConsoleLogger , FileLogger , DatabaseLogger )。随着时间的推移,我们可能需要对这些日志记录方式做升级或优化,甚至引入全新的日志存储技术,比如云存储服务。

日志的接口和实现类如下:

早期版本中,我们的工厂方法可能很简单,直接根据用户的选择返回相应的日志记录器:

随着框架升级到新版本,我们可能引入了更高效的 CloudLogger 类来替代原有的 DatabaseLogger,以支持日志存储到云服务。但是,为了保持向后兼容,我们不希望老的客户端代码因为直接依赖于 DatabaseLogger 而无法工作。

此时,我们可以在工厂方法内部调整逻辑,根据版本信息或配置来决定返回哪个实现类,即使接 口调用保持不变:

在这个场景中,尽管客户端代码始终通过 LoggerFactory.getLogger("database") 请求日志记录 器,但随着框架版本的演进,工厂方法能够根据版本兼容性检查或其他逻辑,动态返回更优的实 现类(如 CloudLogger),从而实现了版本间的平滑过渡,保证了向后兼容性,同时无需客户端代码做出改变。这就是静态工厂方法在支持版本兼容与演进方面的强大之处。

五、静态工厂的第五大优势在于,方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在。

这种灵活的静态工厂方法构成了服务提供者框架(Service Provider Framework)的基础,例如JDBC(Java数据库连接) API。服务提供者框架是指这样一个系统:多个服务提供者实现一 个服务,系统为服务提供者的客户端提供多个实现,并把它们从多个实现中解耦出来。

JDBC是Java语言中用来与数据库交互的一组接口和类。想象一下,它就像是一个“翻译官”, 让Java程序能够和各种数据库(比如MySQL、Oracle、SQL Server等)“对话”。

不同的数据库有自己的“语言”(协议),所以为了让Java程序能和它们沟通,就需要针对每个数据库专门编写的驱动程序。但是,我们不希望每次换数据库时,都要重写所有与数据库交互的代码。这就引入了服务提供者框架的概念。

JDBC工作流程如下:

1.接口定义:JDBC定义了一套标准接口(比如Connection、Statement、ResultSet等), 这些接口就是Java程序和数据库“交流”的规则。所有的数据库驱动都必须实现这些接口, 确保它们能以一种统一的方式被Java程序调用。

2.服务提供者(驱动程序):MySQL、Oracle等数据库厂商提供符合JDBC标准的驱动程序。 这些驱动就是具体的服务提供者,它们实现了JDBC接口,并且包含了与特定数据库通信的 详细逻辑。

3.动态加载与注册:当你在代码中通过Class.forName("com.mysql.jdbc.Driver")这样的语句 时,实际上是在告诉Java:“嘿,去找到这个驱动类并加载它。”这个驱动类在加载过程中,会自动把自己注册到JDBC的DriverManager中。DriverManager就像是一个调度中心, 它知道所有可用的驱动,并能在需要时为你提供一个数据库连接。

4.无感知切换:由于你的代码是基于JDBC接口编写的,而不是直接依赖于某个特定数据库的 API,因此,如果你决定从MySQL切换到PostgreSQL,你只需要更换加载的驱动类名,而 无需修改所有与数据库交互的代码。这就是解耦带来的灵活性。

总结来说,JDBC的服务提供者框架通过标准化接口和动态加载驱动的方式,让你能够在不知道 具体数据库细节的情况下编写代码,从而达到数据库无关性的目标,提高了代码的可维护性和可 移植性。

下面分别是连接MySQL和Oracle的代码:

在这两个示例中,尽管数据库不同,但代码的结构非常相似。主要区别在于数据库URL的格式、 驱动类名以及可能的连接属性。这正是JDBC服务提供者框架的价值所在:它允许你以统一的方 式处理不同数据库的连接和操作,降低了切换数据库的成本。

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

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

相关文章

Chroium 源码目录结构分析(1):源码目录体积一栏

获取源码 首先,我们拉一份最新的源代码(笔者是2024.6.6日拉取的): fetch --nohistory chromium 源码预处理 如果运行build,会生成许多生成的代码,因此我们不运行build。 然后,把干扰后续分析…

sqlilabs靶场安装

05-sqllabs靶场安装 1 安装 1 把靶场sqli-labs-master.zip上传到 /opt/lampp/htdocs 目录下 2 解压缩 unzip sqli-labs-master.zip3 数据库配置 找到配置文件,修改数据库配置信息 用户名密码,修改为你lampp下mysql的用户名密码,root/123456host:la…

贪吃蛇双人模式设计(2)

敲上瘾-CSDN博客控制台程序设置_c语言控制程序窗口大小-CSDN博客贪吃蛇小游戏_贪吃蛇小游戏csdn-CSDN博客​​​​​​​ 一、功能实现: 玩家1使用↓ → ← ↑按键来操作蛇的方向,使用右Shift键加速,右Ctrl键减速玩家2使用W A S D按键来操…

matlab演示地月碰撞

代码 function EarthMoonCollisionSimulation()% 初始化参数earth_radius 6371; % 地球半径,单位:公里moon_radius 1737; % 月球半径,单位:公里distance 384400; % 地月距离,单位:公里collision_tim…

Astar路径规划算法复现-python实现

# -*- coding: utf-8 -*- """ Created on Fri May 24 09:04:23 2024"""import os import sys import math import heapq import matplotlib.pyplot as plt import time 传统A*算法 class Astar:AStar set the cost heuristics as the priorityA…

Kafka集成flume

1.flume作为生产者集成Kafka kafka作为flume的sink,扮演消费者角色 1.1 flume配置文件 vim $kafka/jobs/flume-kafka.conf # agent a1.sources r1 a1.sinks k1 a1.channels c1 c2# Describe/configure the source a1.sources.r1.type TAILDIR #记录最后监控文件…

基于python-CNN深度学习的水瓶是否装满水识别-含数据集+pyqt界面

代码下载地址: https://download.csdn.net/download/qq_34904125/89374853 本代码是基于python pytorch环境安装的。 下载本代码后,有个requirement.txt文本,里面介绍了如何安装环境,环境需要自行配置。 或可直接参考下面博文…

绘唐2.5一键追爆款2.5免费版

免费分享给您 小说推文工具是一种用于在社交媒体上宣传和推广小说的工具。它可以帮助作者将小说的内容和相关信息以推文的形式快速发布在各种社交媒体平台上,吸引读者的注意力并增加小说的曝光度。 以下是一些小说推文工具可能具备的功能: 1. 编辑和排…

【linux】进程控制——进程创建,进程退出,进程等待

个人主页:东洛的克莱斯韦克-CSDN博客 祝福语:愿你拥抱自由的风 相关文章 【Linux】进程地址空间-CSDN博客 【linux】详解linux基本指令-CSDN博客 目录 进程控制概述 创建子进程 fork函数 父子进程执行流 原理刨析 常见用法 出错原因 进程退出 概…

MyBatis-Plus学习总结

一.快速入门 (一)简介 MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window) 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 (二)快速入门 1.准备数据库脚本 2.准备bo…

六、Docker Swarm、Docker Stack和Portainer的使用

六、Docker swarm和Docker stack的使用 系列文章目录1.Docker swarm1.简介2.docker swarm常用命令3.docker node常用命令4.docker service常用命令5.实战案例6.参考文章 2.Docker stack1.简介3.Docker stack常用命令4.实战案例5.常见问题及调错方式1.查看报错信息并尝试解决&am…

SpringBootWeb 篇-深入了解 Redis 五种类型命令与如何在 Java 中操作 Redis

🔥博客主页: 【小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录 1.0 Redis 概述 1.1 Redis 下载与安装 2.0 Redis 数据类型 3.0 Redis 常见五种类型的命令 3.1 字符串操作命令 3.2 哈希操作命令 3.3 列表操作命令 3.4 集合操作命令 …

7-43 排列问题

排列问题 分数 10 全屏浏览 切换布局 作者 雷丽兰 单位 宜春学院 全排列问题 输出自然数1至n中n个数字的全排列(1≤n≤9),要求所产生的任一数字序列中不允许出现重复的数字。 输入格式: 一个自然数 输出格式: 由1到n中n个数字组成的…

Tessy学习系列(三):单元测试——官方例程isValueInRange

一、工程创建 (1)新建工程 注意:工程名称以及路劲不能包含空格和中文 (2)新建测试集与单元测试模块 新建测试集 新建单元测试模块 设置测试模块为单元测试模块并选择GNU GCC编译器如果需要其他的编译器,…

关于选择,关于处事

一个人选择应该选择的是勇敢,选择不应该选择的是无奈。放弃,不该放弃的是懦夫,不放弃应该放弃的是睿智。所以,碰到事的时候要先静,先不管什么事,先静下来,先淡定,先从容。在生活里要…

【线性代数】向量空间,子空间,向量空间的基和维数

向量空间 设V为n维向量的集合,如果V非空,且集合V对于向量的加法以及数乘两种运算封闭,那么就称集合V为向量空间 x,y是n维列向量。 x 向量组等价说明可以互相线性表示 向量组等价则生成的向量空间是一样的 子空间 例题18是三位向…

Docker Swarm持久化

Docker Swarm持久化 1 简介 Docker Swarm持久化有bind、volume和NFS三种方式,bind和volume两种方式适合挂载单个宿主机,不适合集群;NFS适合集群服务,但需要安装NFS系统。 注意:Docker Swarm需要先安装集群。 由Doc…

python-数字黑洞

[题目描述] 给定一个三位数,要求各位不能相同。例如,352是符合要求的,112是不符合要求的。将这个三位数的三个数字重新排列,得到的最大的数,减去得到的最小的数,形成一个新的三位数。对这个新的三位数可以重…

【数据结构】【版本1.0】【线性时代】——顺序表

快乐的流畅:个人主页 个人专栏:《算法神殿》《数据结构世界》《进击的C》 远方有一堆篝火,在为久候之人燃烧! 文章目录 引言一、顺序表的概念1.1 最基础的数据结构:数组1.2 数组与顺序表的区别 二、静态顺序表三、动态…

error while loading shared libraries 找不到动态库问题如何解决

在使用 c 或 c 开发应用时,在启动程序时,有时会遇到这个错误,找不到动态库。这个时候,我们使用 ldd 来查看,发现可执行文件依赖的动态库显示为 not found。 1 实验代码 使用如下 3 个文件做实验。 hello.h 中声明了函…