生成器
这是一种专门负责创造对象的类,也是工厂方法设计模式的一种应用。与之不同的是,生成器生成对象时不需要任何参数,而工厂方法需要参数。简而言之,生成器无需额外的信息就知道如何创建新的对象。
一般而言,一个生成器只定义一个方法,用来产生新的对象。
public interface Generator<T> {
T next();
}
使用泛型使生成器能够生成所有类型的对象,只要你实现了这个接口并且传入需要的类型。
接下来我们来实现一个通用的Generator类,它实现了Generator接口,并重写了next方法。
public class BasicGenerator<T> implements Generator<T>{
private Class<T> type;
public BasicGenerator(Class<T> type) {\
this.type = type;
}
@Override\
public T next() {
// 被弃用的旧方法,原因是会抛出受检异常而不是具体的异常信息,同时绕过了编译检查
try {
return type.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
// 推荐使用下面的新方法
try {
Constructor<T> constructor = type.getDeclaredConstructor();
return constructor.newInstance();
} catch (NoSuchMethodException e) {
// type类缺少无参构造器
} catch (InvocationTargetException e) {
// 构造器抛出异常
} catch (InstantiationException e) {
// 无法实例化抽象类或者接口
} catch (IllegalAccessException e) {
// 无法访问构造器
}
return null;
}
// 创建BasicGenerator的方法,免于调用时麻烦的参数输入
public static <T> Generator<T> create(Class<T> type) {
return new BasicGenerator<T>(type);
}
}
如果我们希望某个类可以被生成,那么这个类需要具备两个要点:必须具备默认的无参构造器、类必须声明为public。
我们一条条来说明:
因为BasicGenerator类和要生成对象的类并不会在同一个包内,所以需要该类必须声明为public,并且不只具有包内的访问权限。
constructor.newInstance() 方法调用的是type类的无参构造器,所以必须要有默认的无参构造器,不然就会抛出NoSuchMethodException异常。
你或许会发现constructor对象还有这么一个方法:constructor.setAccessible(true),顾名思义,这个方法看上去像是授权的,实际上也的确如此。constructor.setAccessible(true)方法是 Java反射中的一个方法,用于绕过 Java的访问控制检查,允许你访问 private、protected 或包私有的成员(字段、方法、构造器)。这是一个十分强大的方法,常用于各大框架和序列化、单元测试中,比如spring的依赖注入,@AutoWired 注解的一般都是私有成员,就是靠这个权限才能够正常将需要的对象插入进成员内。当然,副作用也有很多,最直观的就是会破坏单例,可以强制调用私有构造器生成第二个对象。我们所创建的这个生成器自然没有必要开启这项权限,毕竟生成器需要用到这个权限的场景大概只有破坏单例了。
有了生成器,便可以使用生成器自由的创建各种对象了,下面是一些例子。
class GenshinImpact {
public GenshinImpact() {
System.out.println("GenshinImpact Start!");
}
}
public class BasicGeneratorDemo {
public static void main(String[] args) {
// 调用create方法创建生成器
Generator<GenshinImpact> gen = BasicGenerator.create(GenshinImpact.class);
// 调用生成器方法创建对象实例,调用无参构造器。
GenshinImpact genshinImpact = gen.next();
}
}
控制台输出,证明调用
还可以写一个随机对象生成器用来愉悦心情
import java.util.Random;
class MiHoYo {
public MiHoYo() {
System.out.println("MiHoYo什么的,最讨厌了");
}
}
class GenshinImpact extends MiHoYo{
public GenshinImpact() {
System.out.println("GenshinImpact Start!");
}
}
class HonkaiImpact extends MiHoYo{
public HonkaiImpact() {
System.out.println("HonkaiImpact Start!");
}
}
class StarRail extends MiHoYo{
public StarRail() {
System.out.println("StarRail Start!");
}
}
class ZZZ extends MiHoYo{
public ZZZ() {
System.out.println("ZZZZZZZZ");
}
}
public class BasicGeneratorDemo {
private static final Class[] types = {MiHoYo.class, GenshinImpact.class, HonkaiImpact.class, StarRail.class, ZZZ.class};
private static Random random = new Random();
public static void main(String\[] args) {
for (int i = 0; i < 3; i++) {
Class type = types[random.nextInt(types.length)];
Generator gen = BasicGenerator.create(type);
MiHoYo next = (MiHoYo) gen.next();
System.out.println(next);
}
}
}
一种输出:
当然,用作正事的时候,数组中装载的就是需要的类了
看到这里有人就又要问了,欸,那你生成器也就生成个对象,好像和直接把对象new出来区别也不大嘛,那为什么还需要费劲创建一个生成器呢?
生成器本质上就是把"创建对象"这个动作抽象化、参数化、可替换化。在简单示例里它确实没啥用,但在框架、库和复杂系统里,这种抽象价值巨大。它的真正作用在当我们无法直接new出对象的时候。试想一下,如果不知道要创建什么对象,单纯的new还能解决创建对象的问题吗?不能。new是将代码写死,而生成器使用泛型,在运行时动态决定创建什么类型的对象,让代码活了过来。
如果需要创建的对象有很多个配置变量(比如数据库连接池),传统new对象时每创建一次都要重新写一遍配置信息,不仅麻烦,修改的时候工作量也是巨大。而使用生成器,我们只需要创建一个类继承生成器接口,便可以在类的构造器里单独配置所有变量,一次封装,无数次使用,省心又省力。
再比如,当我们希望控制某个类创建的对象总数的时候,便可以在对应类生成器的next方法中添加规则,规定如果对象数量达到上限,则返回已有对象,否则继续new新对象。
生成器设计模式将创建对象这一操作封装为一个方法,并可以设定对象成员变量的初始值和控制对象数量,在面对复杂对象和复杂要求时效果显著。