如何遵循对抽象超类的子类使用构建器模式的最佳 Java 实践?
How to follow best Java practices using builder pattern for a subclass of an abstract superclass?
// CommandBuilder.java
public class CommandBuilder {
public String name; // required
public String description = "Default Value"; //optional
public CommandBuilder(String name) {
this.name = name;
}
public CommandBuilder setDescription(String description) {
this.description = description;
return this;
}
public CommandBuilder build() {
return this;
}
}
// Command.java
public abstract class Command extends ListenerAdapter {
private String name;
private String description;
protected abstract void execCommand(MessageReceivedEvent event);
public Command(CommandBuilder builder) {
this.name = builder.name;
this.description = builder.description;
}
@Override
public void onMessageReceived(MessageReceivedEvent event) {
execCommand(event);
}
}
// ExampleCommand.java
public class ExampleCommand extends Command {
public ExampleCommand(CommandBuilder builder) {
super(builder);
}
@Override
protected void execCommand(MessageReceivedEvent event) {
// ...
}
}
// Bot.java
public class Bot() {
public static void main(String[] args) {
// ...
jdaBuilder.addEventListener(
new ExampleCommand(
new CommandBuilder("Example Command").setDescription("You know it.").build();
)
);
// ...
}
}
所以,我需要一些关于代码风格的建议。以上大致是我在 JDA 中为 Discord 机器人设置的代码。 jdaBuilder.addEventListener(Object)
做什么或 MessageReceivedEvent
做什么并不重要。
我使用构建器模式来避免构造具有继承类型 Command
的对象时过多的构造函数重载,因为在我的实际代码中 Command
class 可以接受的不仅仅是两个参数。
CommandBuilder
的问题是 build()
没有也不能 return 类型为 Command
的对象(因为它是抽象的)而是类型 CommandBuilder
本身,Command 的每个 subclass 作为参数(然后传递给 Command
)。
return 中的问题在于:
- 不需要调用
build()
,因为每个其他方法 return 都是 CommandBuilder
以及
- 具有 4-6 个参数的
Command
的子 class 的实例化可能会使主体变得 非常 混乱。
那么,解决这个问题的最佳方法是什么?我想过使用一个接口,但是在我的抽象命令 class 中有一些带有 "default" 代码的方法,subclasses 可以选择覆盖如果他们需要它(但它不是必需的!)。
这些默认方法利用了 Command
class 的其他方法,因此我不能将它们作为真正的默认方法重构到接口中。
我的代码工作得很好,我只是觉得我必须实例化我的对象的方式,我在某处走错了路。关于如何重构或重写我的代码以遵循最佳 Java 实践的任何建议?
也许你应该像这样重构你的 build()
:
public <T extends Command> T build(Function<CommandBuilder, T> constructor) {
return constructor.apply(this);
}
并且这种方式允许构建器接受任何构造函数引用,例如
FooCommand cmd = builder.build(FooCommand::new);
也许您还可以添加适当的代码来允许反射,以便
FooCommand cmd = builder.build(FooCommand.class);
也可以使用,但前者的优点是如果您没有匹配的构造函数,它会出现编译时错误。
// CommandBuilder.java
public class CommandBuilder {
public String name; // required
public String description = "Default Value"; //optional
public CommandBuilder(String name) {
this.name = name;
}
public CommandBuilder setDescription(String description) {
this.description = description;
return this;
}
public CommandBuilder build() {
return this;
}
}
// Command.java
public abstract class Command extends ListenerAdapter {
private String name;
private String description;
protected abstract void execCommand(MessageReceivedEvent event);
public Command(CommandBuilder builder) {
this.name = builder.name;
this.description = builder.description;
}
@Override
public void onMessageReceived(MessageReceivedEvent event) {
execCommand(event);
}
}
// ExampleCommand.java
public class ExampleCommand extends Command {
public ExampleCommand(CommandBuilder builder) {
super(builder);
}
@Override
protected void execCommand(MessageReceivedEvent event) {
// ...
}
}
// Bot.java
public class Bot() {
public static void main(String[] args) {
// ...
jdaBuilder.addEventListener(
new ExampleCommand(
new CommandBuilder("Example Command").setDescription("You know it.").build();
)
);
// ...
}
}
所以,我需要一些关于代码风格的建议。以上大致是我在 JDA 中为 Discord 机器人设置的代码。 jdaBuilder.addEventListener(Object)
做什么或 MessageReceivedEvent
做什么并不重要。
我使用构建器模式来避免构造具有继承类型 Command
的对象时过多的构造函数重载,因为在我的实际代码中 Command
class 可以接受的不仅仅是两个参数。
CommandBuilder
的问题是 build()
没有也不能 return 类型为 Command
的对象(因为它是抽象的)而是类型 CommandBuilder
本身,Command 的每个 subclass 作为参数(然后传递给 Command
)。
return 中的问题在于:
- 不需要调用
build()
,因为每个其他方法 return 都是CommandBuilder
以及 - 具有 4-6 个参数的
Command
的子 class 的实例化可能会使主体变得 非常 混乱。
那么,解决这个问题的最佳方法是什么?我想过使用一个接口,但是在我的抽象命令 class 中有一些带有 "default" 代码的方法,subclasses 可以选择覆盖如果他们需要它(但它不是必需的!)。
这些默认方法利用了 Command
class 的其他方法,因此我不能将它们作为真正的默认方法重构到接口中。
我的代码工作得很好,我只是觉得我必须实例化我的对象的方式,我在某处走错了路。关于如何重构或重写我的代码以遵循最佳 Java 实践的任何建议?
也许你应该像这样重构你的 build()
:
public <T extends Command> T build(Function<CommandBuilder, T> constructor) {
return constructor.apply(this);
}
并且这种方式允许构建器接受任何构造函数引用,例如
FooCommand cmd = builder.build(FooCommand::new);
也许您还可以添加适当的代码来允许反射,以便
FooCommand cmd = builder.build(FooCommand.class);
也可以使用,但前者的优点是如果您没有匹配的构造函数,它会出现编译时错误。