文章目录

  • 前言
  • 使用静态工厂方法代替构造器
    • 1. 优点
      • 1.1 静态工厂方法有名称
      • 1.2 不必每次调用的时候都创建一个对象
      • 1.3 可以返回类型的任何子类型的对象
      • 1.4 所返回的对象的类型可以随着每次的调用而发生变化,取决于参数值
      • 1.5 方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在
    • 2. 缺点
      • 2.1 类如果没有公共的或者受保护的构造器,就不能被实例化。
      • 2.2 程序员很难发现它们

前言

对于类而言,为了让客户端获取它本身的一个实例,最传统的方法就是使用构造器,还有一种方法就是可以使用静态工厂方法。这是《Effective Java》这本书的第一节,里面主要介绍一点就是尽量使用静态工厂方法而不是构造器。


使用静态工厂方法代替构造器

1. 优点

1.1 静态工厂方法有名称

这一点可以和单例模式联系起来,单例模式中一般使用了getInstance方法来返回一个对象,这个方法的名字就可以提示我们这是一个单例的对象。又比如下面的例子,当我们遇到一个场景,我们需要通过不同的方法来获取Car对象,就可以在这个类里面提供下面两种方法:

public class Car {static HashMap<String, Car> map = new HashMap<>();private Car(){}public static Car getCarByInstanceOrNull(String name){return map.getOrDefault(name, null);}public static Car getCarByNameIfNullWillCreate(String name){if(!map.containsKey(name)){map.put(name, new Car());}return map.get(name);}
}
public class TestCar {public static void main(String[] args) {final Car aaa = Car.getCarByInstanceOrNull("aaa");  //获取名字为aaa的final Car bbb = Car.getCarByInstanceOrNull("bbb");  //获取名字为bbb的System.out.println(aaa + " : " + bbb);final Car bbb1 = Car.getCarByNameIfNullWillCreate("bbb");   //获取名字为bbb的,如果不存在就创建System.out.println(bbb1);}
}

结果截图如下:

其实通过这个代码就可以很清楚看到,不同的方法getCarByNameIfNullWillCreategetCarByInstanceOrNull都是返回实例对象,但是有着不同的功效,而我们使用构造器是没有这种效果的,并且可以看到在这些方法中,可以很明确看出来我们可以控制对象的创建,比如上面就是控制名字不能相同。

下面还有一个例子,Boolean中的valueOf方法

public static Boolean valueOf(boolean b) {return b ? TRUE : FALSE;}

这个方法中使用valueOf,我们通过参数名就可以明确知道自己想要做什么,要构建一个false还是true的角色等等。此外,我们都可以在一些类中可以使用静态方法来返回一些特定条件的对象。

1.2 不必每次调用的时候都创建一个对象

这种也很好理解,我们可以提前创建好一些对象,然后调用的时候就直接从方法中返回就可以了。比如spring的对象管理,还有单例模式等等都体现了这个思想。

1.3 可以返回类型的任何子类型的对象

看下面这个例子,还是以上面的为基础,现在新增一个Animal类,然后用Car类继承Animal,在里面我们返回了Car的一个对象:

public class Animal {public static Animal getCar(){return Car.getCarByNameIfNullWillCreate("aaa");}
}

这个例子很明显就可以体现出来返回子类型的特点,这种用法一般都是在接口中,比较适用于基于接口的框架,这种框架中,接口为静态工厂方法提供了自然返回对象,而构造器明显是做不到的。

1.4 所返回的对象的类型可以随着每次的调用而发生变化,取决于参数值

这一点其实和第一点所演示的内容差不多了,你可以传入一个参数,然后静态方法根据这个参数返回你需要的对象,而我们知道构造器是做不到这一点的。还是1.1的例子,对比构造器你就可以发现,如果1.1中不把构造器设置成私有的,那么同一个名字的Animal可能会多次创建;如果设置了私有的,通过静态的方法,那么我们就可以控制同一个名字只能创建一次。比较适合使用的场景就是你的对象有某种约束条件的时候可以这么写。

类似的还有EnumSet没有公共的构造器,只有静态工厂方法。在OpenJDK实现中它们返回两种子类之一的一个实例,具体则取决于底层枚举类型的大小:如果它的元素有64个或者更少,就像大多数枚举类型一样,静态工厂方法就会返回一个RegalarEumSet的实例,用单个long进行支持;如果枚举类型有65个或者更多元素,工厂就会返回JumboEnumSet实例,用一个long数组进行支持。最终结果就是,在以后的版本中,这些返回的类型都可以随意增加或者删除,因为基于接口的返回,所以没有多大影响。用户可以不用关心得到了什么对象,只需要得到对象即可。

public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {Enum<?>[] universe = getUniverse(elementType);if (universe == null) {throw new ClassCastException(elementType + " not an enum");} else {return (EnumSet)(universe.length <= 64 ? new RegularEnumSet(elementType, universe) : new JumboEnumSet(elementType, universe));}}

1.5 方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在

这种静态工厂方法构成了服务提供者框架, 有兴趣可以了解下这个框架。这个框架提供了四个组件

  1. 服务接口(Service Interface):这是提供者实现的
  2. 提供者注册API(Provider Registration API):这是提供者用来注册实现的
  3. 服务访问API(Service Access API):这时客户端用来获取服务的实例,你可以理解成上面例子中的 getCarByInstanceOrNull 方法,这个方法中根据名字返回实例对象
  4. 服务提供者接口(Service Provider Interface):表示产生服务接口实例的工厂对象

其中一个很典型的例子就是JDBC,我们来看看JDBC获取数据库连接的几个关键步骤:

//实现连接的几种方式
public class jdbcTest1 {@Test//将数据库连接需要的四个基本信息放在配置文件中,对配置文件加载就可以了//好处:实现数据与代码分离public void test1(){//1.读取配置文件中4个基本信息ResourceBundle bundle = ResourceBundle.getBundle("jdbc");String driverClass = bundle.getString("driverClass");String url = bundle.getString("url");String user = bundle.getString("user");String password = bundle.getString("password");System.out.println(driverClass);System.out.println(url);//2. 加载驱动try {Class.forName(driverClass);//3. 获取连接Connection connection = DriverManager.getConnection(url, user, password);System.out.println(connection);} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException throwables) {throwables.printStackTrace();}}
}

其中最关键的就是 2 和 3 ,当然这两步还可以再详细一点:

//1.实例化Driver
Class clazz = Class.forName(driverName);
//获取实例
Driver driver = (Driver) clazz.newInstance();//2.注册驱动
DriverManager.registerDriver(driver);
//3.获取Connection对象
conn = DriverManager.getConnection(url, user, password);

对于JDBC来说,Connection就是其服务接口的一部分,也是最终我们获取的对象,但是要注意的一点是这个Connection是一个接口来的:public interface Connection extends Wrapper, AutoCloseable{},我们操作数据库要使用这个连接对象,也就是这个服务接口,这个服务接口的一些逻辑是由产商自己写好的。而 Driver 就是服务提供者接口,是用来提供 Connection 的,这一点可以去翻源码,在源码中 DriverManager.registerDriver(driver) 这一句把driver对象放到了一个 registeredDrivers 里面,然后在调用 getConnection 的时候是遍历这个 registeredDrivers ,把里面的driver一个一个取出来然后调用 connect 方法,最终获取到Connection的。所以从上面的流程看下来:DriverManager.registerDriver(driver) 就是提供者注册API,用来注册driver的,这里的注册你可以理解为存起来,而 DriverManager.getConnection 就是服务访问API,可以通过这个方法去访问API。下面就是整个源码过程:

//提供者注册API,注册driver用的
public static void registerDriver(Driver driver) throws SQLException {registerDriver(driver, (DriverAction)null);
}public static void registerDriver(Driver driver, DriverAction da) throws SQLException {if (driver != null) {//把driver存到registeredDrivers里面,就算是注册了registeredDrivers.addIfAbsent(new DriverInfo(driver, da));println("registerDriver: " + driver);} else {throw new NullPointerException();}
}//服务访问API,意思就是获取服务接口的API
private static Connection getConnection(String url, Properties info, Class<?> caller) throws SQLException {ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;if (callerCL == null || callerCL == ClassLoader.getPlatformClassLoader()) {callerCL = Thread.currentThread().getContextClassLoader();}if (url == null) {throw new SQLException("The url cannot be null", "08001");} else {println("DriverManager.getConnection(\"" + url + "\")");ensureDriversInitialized();SQLException reason = null;//遍历所有的提供者注册APIIterator var5 = registeredDrivers.iterator();while(true) {while(var5.hasNext()) {DriverInfo aDriver = (DriverInfo)var5.next();if (isDriverAllowed(aDriver.driver, callerCL)) {try {println("    trying " + aDriver.driver.getClass().getName());//使用服务提供者调用connect方法获取服务接口Connection con = aDriver.driver.connect(url, info);if (con != null) {println("getConnection returning " + aDriver.driver.getClass().getName());return con;}} catch (SQLException var8) {if (reason == null) {reason = var8;}}} else {println("    skipping: " + aDriver.getClass().getName());}}if (reason != null) {println("getConnection failed: " + reason);throw reason;}println("getConnection: no suitable driver found for " + url);throw new SQLException("No suitable driver found for " + url, "08001");}}}

最终我们终于理清楚这四个接口之间的关系了:
1. 服务接口(Service Interface):也就是Connection,这是提供者实现的,是我们最终的目的
2. 服务提供者接口(Service Provider Interface):这个接口就是用来提供服务者的,那么在上面就体现在Driver,Driver.connect方法可以获取到服务接口Connection
3. 服务访问API(Service Access API):这个就是用来获取服务接口的方法,往往里面是通过调用服务提供者接口来获取服务接口,也就是 DriverManager.getConnection
4. 提供者注册API(Provider Registration API):这就是用来注册提供者的,也就是DriverManager.registerDriver方法,你可以理解为将Driver存起来(JDBC是这样做的)

那么下面了解了这四个关系之后,我们就可以自己写一个服务提供者框架了:
假设现在我需要通过手机发送一条短信,那么这个过程就是首先我需要注册一个服务提供者手机,然后通过一个注册提供者API把手机注册,然后调用服务访问API来访问手机,最终进行发短信,整个过程如下:

//1. 服务者接口
public interface Phone {//发送短信public void sendMsg();
}//服务接口实现类
public class PhoneImpl implements Phone {@Overridepublic void sendMsg() {System.out.println("手机发送信息");}
}//2. 服务提供者接口
//手机服务提供者接口
public interface PhoneProvider {public Phone getService();
}public class PhoneProviderImpl implements PhoneProvider{static{ServiceManager.registProvider("手机", new PhoneProviderImpl());}@Overridepublic Phone getService() {return new PhoneImpl();}
}//提供者管理类
//服务提供者注册类
public class ServiceManager {private ServiceManager() {}private static final Map<String, PhoneProvider> registerProvider = new HashMap<>();/*** 3. 提供者注册API*/public static void registProvider(String name, PhoneProvider phoneProvider){registerProvider.put(name, phoneProvider);}/*** 4. 服务访问API, 获取服务类*/public static Phone getPhone(String name){if(!registerProvider.containsKey(name)){throw new RuntimeException("The required service provider could not be found");}return registerProvider.get(name).getService();}
}

测试:

public class TestService {public static void main(String[] args) throws ClassNotFoundException {//注册服务提供者Class.forName("com.jianglianghao.One.PhoneProviderImpl");//然后直接获取final Phone phone = ServiceManager.getPhone("手机");//调用接口phone.sendMsg();}
}

2. 缺点

2.1 类如果没有公共的或者受保护的构造器,就不能被实例化。

如果是私有的,那么类就不能被直接 new 出来了,这时候建议使用复合而不是继承。

2.2 程序员很难发现它们

一些类中的静态方法有时候是很难发现的,要查明如何实例化一个类不太容易。下面是静态工厂方法的一些惯用名称,下面列出一小部分:

  • from: 类型转换方法,只有单个参数,返回该类型的一个相对应得实例,比如:Date d = Date.from(instant)
  • of :聚合方法,带有多个参数,返回该类型的一个实例,把它们合并起来,比如:Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
  • valueOf: 比 from 和 of 更加烦琐的一种替代方法,比如:BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE)
  • instance或者getInstance:返回的实例是通过方法的(如有)参数来描绘的,但是不能说与参数具有同样的值,比如StackWalker luke = StackWalker.getInstance(options)
  • create或者newInstance:像 instance 或者 getInstance 一样,但是 create 或者 newInstance 能够确保每一次调用都返回一个新的实例,例如:Object newArray = Array.newInstance(classObject, arrayLen);
  • getType:像 getInstance 一样,但是在工厂方法处于不同的类中的时候使用。Type 表示工厂方法所返回的对象类型,例如:FileStore fs = Files.getFileStore(path);
  • newType:像 newInstance 一样,但是在工厂方法处于不同类中的时候使用。Type 表示工厂方法所返回的对象类型,比如 BufferedReader br = Files.newBufferedReader(path);
  • type:getType 和 newType 的简版,例如:List<Complaint> litany = Collections.list(legacyLitany);

总之,静态工厂方法和构造器各有好处,因此切忌第一反应就是提供公有构造器而不是先考虑静态工厂

如有错误,欢迎指出!!!!

《Effective Java》学习笔记 - (1) 使用静态工厂方法代替构造器相关推荐

  1. 小鸡爪读Effective Java记录1:用静态工厂方法代替构造器

    //小鸡爪 == 菜鸡 遇到多个构造器参数时要考虑使用构建器 Preface 静态工厂和构造器有个共同的局限性:它们都不能很好地扩展到大量的可选参数.比如用一个类表示包装食品外面显示的营养成分标签.这 ...

  2. 第一条:考虑用静态工厂方法代替构造器

    转载链接:https://www.jianshu.com/p/ceb5ec8f1174 1.序:什么是静态工厂方法 在 Java 中,获得一个类实例最简单的方法就是使用 new 关键字,通过构造函数来 ...

  3. Effective Java学习笔记之第5条 避免创建不必要的对象

    第5条 避免创建不必要的对象 一般来说,最好能重用对象而不是在每次需要的时候就创建一个相同功能的对象. 反面例子: String s = new String("stringette&quo ...

  4. 【Effective Java】第二章:静态工厂、构建器、强化Singleton属性、私有构造器、

    文章目录 一. 用静态工厂方法代替构造器 优势: 劣势: 实例代码: 二. 遇到多个构造器参数时要考虑使用构建器 ① 重叠构建器 ② JavaBeans模式 ③ Builder模式 三. 用私有构造器 ...

  5. Effective Java 学习笔记 1

    Item 1: Consider static factory methods instead of constructors  (多考虑使用静态工厂方法而不是构造方法) 使用静态工厂方法有以下几点好 ...

  6. 设计模式学习笔记-2 创建者模式-工厂方法模式

    设计模式学习笔记-2 创建者模式-工厂方法模式 工厂模式介绍 工厂模式又称工厂方法模式,是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型. 这种设计模式使Java开 ...

  7. java 静态工厂方法代替构造器的好处

    Java 的静态工厂方法 序:什么是静态工厂方法 Effective Java 2.1 静态工厂方法与构造器不同的第一优势在于,它们有名字 2.2 第二个优势,不用每次被调用时都创建新对象 2.3 第 ...

  8. Java九十条经验法则之第一条:用静态工厂方法代替构造器

    一.用静态工厂方法代替构造器的优势 1.静态构造方法与构造器不同的第一大优势在于,他们有名称,而构造器名字相同,容易混淆,例如: 使用静态构造方式的方式,我们就可以通过知道产生的是什么样类型的对象,而 ...

  9. 01、静态工厂方法替代构造器

    考虑用静态工厂方法替代构造器 考虑使用静态工厂方法来替代构造器的原因: 静态工厂方法有名称:普通的构造器中,参数并不能很好地描述返回对象的特点,代码的阅读性不好. 考虑下面的程序: Random ra ...

最新文章

  1. oracle读书记录
  2. PlayMaker的Transition和Global Transition
  3. 【模型迭代】拒绝推断(RI)
  4. linux文件显示程序,Linux下文件显示命令简介
  5. 电脑屏幕卡住了按什么都没反应_刚买2个月,联想电脑屏幕出现坏点,售后回复:坏点不够3个不能保修...
  6. Selenium中WebDriver的close()和quit()
  7. C++ Template 使用简介
  8. delphi 获取数组长度_Java中的数组(基础篇六)
  9. 使用计算机模拟抛硬币,计算机模拟抛硬币实验学生任务单.pdf
  10. Spring全面详解(学习总结)
  11. 单片机c语言实验报告心得,关于单片机实训心得体会
  12. 联想计算机设置恢复出厂,联想电脑恢复出厂设置还原系统攻略
  13. STC单片机不同数据类型串口打印输出示例程序
  14. 2018.4.3晚_京东实习_后端开发面试记录
  15. 单片机能做什么创意小发明?新手怎么制作单片机智能小车?
  16. 【OpenCV图像处理入门学习教程六】基于Python的网络爬虫与OpenCV扩展库中的人脸识别算法比较
  17. find和grep区别
  18. 边缘计算是5G应用的核心平台 , 产业空间广阔
  19. bigquant量化平台笔记
  20. 微信小程序期末大作业-蔬菜商城

热门文章

  1. torch_geometric
  2. 写一个 JS 调用栈可视化工具 hound-trace
  3. 思科致力加强本土投入 推动行业发展
  4. linux i2c detect函数,手把手教你写Linux I2C设备驱动
  5. 上海亚商投顾:沪指重返3200点 牛市旗手回归!
  6. 让机器“删繁就简”:深度神经网络加速与压缩|VALSE2018之六
  7. Hibernate+Spring+Struts2+ExtJS开发CRUD功能
  8. android广告拦截 知乎,Android仿制知乎滑动广告条
  9. 《Spatially Attentive Output Layer for Image Classification》论文翻译
  10. 图像分类中的对抗鲁棒性