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

2024-06-10 01:52

本文主要是介绍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服务提供者框架的价值所在:它允许你以统一的方 式处理不同数据库的连接和操作,降低了切换数据库的成本。

这篇关于Effective Java 1 用静态工厂方法代替构造器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1046910

相关文章

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("