本文的主要目的是比对静态工厂方法和构造函数两种创建实例的方法的利弊。
在Java中常见的实例化对象的方法有两种。一是通过类的构造函数,第二种就是通过静态工厂方法。假设有一个场景,涉及到采购书籍,从而定义了这样一个类:
public class BookOrder {
private String bookName;
private double price; // 单价
private int number; // 购书数量
private double totalPrice; // 总价
// get,set方法省略
}
这个类中只有一个默认的构造函数,那么实例化这个类的时候一般这样操作:
BookOrder bookOrder = new BookOrder();
或者我们可以在这个类中写入一个静态工厂方法,如下:
public class BookOrder {
private String bookName;
private double price; // 单价
private int number; // 购书数量
private double totalPrice; // 总价
// get,set方法省略
// 静态工厂方法
public static BookOrder createObject() {
BookOrder bookOrder = new BookOrder();
return bookOrder;
}
}
那么开发者还可以这样实例化这个类:
BookOrder bookOrder = BookOrder.createObject();
构造函数与静态工厂方法各有利弊。
根据Java的语法,一个类的构造函数是与类同名的。以上面BookOrder这个对象为例,它的构造函数名称必须为BookOrder。如果想创建多个构造函数,那么就只能通过形参列表的不同进行区分。例如我现在想通过构造函数实现两个需求
那么这个类的定义就变成了这样:
public class BookOrder {
private String bookName;
private double price; // 单价
private int number; // 购书数量
private double totalPrice; // 总价
// get,set方法省略
// 构造方法
public BookOrder() {
}
public BookOrder(String bookName, int number, double price) {
this.bookName = bookName;
this.number = number;
this.price = price;
this.totalPrice = number * price;
}
public BookOrder(String bookName, double totalPrice, int number) {
this.bookName = bookName;
this.number = number;
this.price = totalPrice / number;
this.totalPrice = totalPrice;
}
}
因为Java不允许出现两个名称、形参列表均一样的方法,所以后两个构造函数的参数顺序不能一样。如果想通过这三个构造函数实例化对线,会出现下面这种局面:
// 仅仅创建对象
BookOrder bookOrder1 = new BookOrder();
// 计算总价
BookOrder bookOrder2 = new BookOrder("bookName", 10, 23.50);
// 计算单价
BookOrder bookOrder3 = new BookOrder("bookName", 235, 10);
仅仅看这三行代码是真的没法去猜测后两个究竟想干什么。哪怕开发者隔一段时间回头看自己代码也会头疼。但是如果不使用构造函数,而是使用静态工厂方法,那么这个类可以这样定义:
public class BookOrder {
private String bookName;
private double price; // 单价
private int number; // 购书数量
private double totalPrice; // 总价
// get,set方法省略
// 静态工厂方法
private BookOrder() {
}
public static BookOrder getTotalPrice(String bookName, int number, double price) {
BookOrder bookOrder = new BookOrder();
bookOrder.setBookName(bookName);
bookOrder.setNumber(number);
bookOrder.setPrice(price);
bookOrder.setTotalPrice(bookName * price);
return bookOrder;
}
public static BookOrder getUnitPrice(String bookName, int number, double totalPrice) {
BookOrder bookOrder = new BookOrder();
bookOrder.setBookName(bookName);
bookOrder.setNumber(number);
bookOrder.setTotalPrice(totalPrice);
bookOrder.setPrice(totalPrice / number);
return bookOrder;
}
}
同样是初始化三个实例,代码会变成这样:
// 仅仅创建对象
BookOrder bookOrder1 = new BookOrder(); // 因为构造函数被私有化,因此创建失败
// 计算总价
BookOrder bookOrder2 = BookOrder.getTotalPrice("bookName", 10, 23.50);
// 计算单价
BookOrder bookOrder3 = BookOrder.getUnitPrice("bookName", 2350, 10);
显然后者的意图更为明显,不容易引起歧义。这就是所谓的“语义更为明确”。其灵活性在这里也能得到很好的体现:因为不同的静态工厂方法使用的是不同的方法名,因此完全不必在意形参列表的注意事项。
由上面的例子可以看出,当我们使用构造函数创建对象的时候,三个对象使用了三个new,即创建了三个对象。而使用静态构造方法创建对象的时候并没有使用new,也就是没有创建对象就实现了类的实例化(实际上是在静态工厂方法内创建了对象)。这种方法类似于Flyweight模式。如果程序经常请求创建相同的对象,而且创建对象的代价很高,那么使用静态对象方法可以有效地提升性能。因为静态工厂方法每次被调用的时候,不一定要创建一个新的对象。
另一方面,静态工厂方法对于重复的调用返回的是相同的对象,这些对象是实例受控的。这样可以避免出现两个相同的实例化对象,例如如果执行下面两行代码:
BookOrder bookOrder1 = BookOrder.getTotalPrice("bookName", 10, 23.5);
BookOrder bookOrder2 = BookOrder.getTotalPrice("bookName", 10, 23.5);
此时创建的两个对象中的内容是一样的,但是这是两个不同的对象。即bookOrder1 == bookOrder2得到的结果为false;bookOrder1.equals(bookOrder2)得到的结果为true。
方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在。这种方法构成了服务提供者框架的基础。
服务提供者框架:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把它们从多个实现中解耦出来。
在API文档中,静态工厂方法没有像构造器那样被明确标识出来,因此对于提供了静态工厂方法的类来说,搞清楚它是如何实例化的需要费些功夫。在类或者接口注释中对静态工厂方法进行说明、遵守标准的命名习惯可以在一定程度上弥补这个劣势。
如果类不含有共有的或者受保护的构造器,就不能被子类化。换句话说,如果父类没有公共的构造器,它就不允许被继承。如果想在另一个类中使用这个类的方法,就需要使用复合的方式。例如想在SubBookOrder这个类中使用BookOrder中的方法,代码需要这样写:
public class SubBookOrder {
BookOrder bookOrder = null;
public void SubBookOrderTest() {
bookOrder = BookOrder.getTotalPrice("bookName", 10, 23.50);
bookOrder.getTotalPrice();
}
}