- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
我知道有类似的问题。不过,我还没有看到我的问题的答案。
我将使用一些简化的代码来展示我想要的内容。假设我有一个复杂的对象,它的一些值是通用的:
public static class SomeObject<T, S> {
public int number;
public T singleGeneric;
public List<S> listGeneric;
public SomeObject(int number, T singleGeneric, List<S> listGeneric) {
this.number = number;
this.singleGeneric = singleGeneric;
this.listGeneric = listGeneric;
}
}
我想用流畅的 Builder 语法构建它。不过,我想让它优雅。我希望它能像那样工作:
SomeObject<String, Integer> works = new Builder() // not generic yet!
.withNumber(4)
// and only here we get "lifted";
// since now it's set on the Integer type for the list
.withList(new ArrayList<Integer>())
// and the decision to go with String type for the single value
// is made here:
.withTyped("something")
// we've gathered all the type info along the way
.create();
没有不安全的转换警告,也不需要预先指定泛型类型(在顶部,构造 Builder 的地方)。
相反,我们让类型信息显式流入,在链的更下方——连同 withList
和 withTyped
电话。
现在,实现它的最优雅的方法是什么?
我知道最常见的技巧,例如使用 recursive generics ,但我玩了一会儿,无法弄清楚它如何适用于这个用例。
下面是一个普通的冗长解决方案,它可以满足所有要求,但代价是过于冗长——它引入了四个构建器(在继承方面不相关),代表了 T
的四种可能组合。和 S
类型是否被定义。
它确实有效,但是这不是一个值得骄傲的版本,如果我们期望的通用参数不仅仅是两个,那么它就无法维护。
public static class Builder {
private int number;
public Builder withNumber(int number) {
this.number = number;
return this;
}
public <T> TypedBuilder<T> withTyped(T t) {
return new TypedBuilder<T>()
.withNumber(this.number)
.withTyped(t);
}
public <S> TypedListBuilder<S> withList(List<S> list) {
return new TypedListBuilder<S>()
.withNumber(number)
.withList(list);
}
}
public static class TypedListBuilder<S> {
private int number;
private List<S> list;
public TypedListBuilder<S> withList(List<S> list) {
this.list = list;
return this;
}
public <T> TypedBothBuilder<T, S> withTyped(T t) {
return new TypedBothBuilder<T, S>()
.withList(list)
.withNumber(number)
.withTyped(t);
}
public TypedListBuilder<S> withNumber(int number) {
this.number = number;
return this;
}
}
public static class TypedBothBuilder<T, S> {
private int number;
private List<S> list;
private T typed;
public TypedBothBuilder<T, S> withList(List<S> list) {
this.list = list;
return this;
}
public TypedBothBuilder<T, S> withTyped(T t) {
this.typed = t;
return this;
}
public TypedBothBuilder<T, S> withNumber(int number) {
this.number = number;
return this;
}
public SomeObject<T, S> create() {
return new SomeObject<>(number, typed, list);
}
}
public static class TypedBuilder<T> {
private int number;
private T typed;
private Builder builder = new Builder();
public TypedBuilder<T> withNumber(int value) {
this.number = value;
return this;
}
public TypedBuilder<T> withTyped(T t) {
typed = t;
return this;
}
public <S> TypedBothBuilder<T, S> withList(List<S> list) {
return new TypedBothBuilder<T, S>()
.withNumber(number)
.withTyped(typed)
.withList(list);
}
}
我可以应用更聪明的技术吗?
最佳答案
好的,所以更传统的步骤生成器方法应该是这样的。
不幸的是,因为我们混合了泛型和非泛型方法,我们不得不重新声明很多方法。我认为没有解决这个问题的好方法。
基本思想就是:在接口(interface)上定义每个步骤,然后在私有(private)类上实现它们。我们可以通过继承原始类型来使用通用接口(interface)来做到这一点。这很丑陋,但它确实有效。
public interface NumberStep {
NumberStep withNumber(int number);
}
public interface NeitherDoneStep extends NumberStep {
@Override NeitherDoneStep withNumber(int number);
<T> TypeDoneStep<T> withTyped(T type);
<S> ListDoneStep<S> withList(List<S> list);
}
public interface TypeDoneStep<T> extends NumberStep {
@Override TypeDoneStep<T> withNumber(int number);
TypeDoneStep<T> withTyped(T type);
<S> BothDoneStep<T, S> withList(List<S> list);
}
public interface ListDoneStep<S> extends NumberStep {
@Override ListDoneStep<S> withNumber(int number);
<T> BothDoneStep<T, S> withTyped(T type);
ListDoneStep<S> withList(List<S> list);
}
public interface BothDoneStep<T, S> extends NumberStep {
@Override BothDoneStep<T, S> withNumber(int number);
BothDoneStep<T, S> withTyped(T type);
BothDoneStep<T, S> withList(List<S> list);
SomeObject<T, S> create();
}
@SuppressWarnings({"rawtypes","unchecked"})
private static final class BuilderImpl implements NeitherDoneStep, TypeDoneStep, ListDoneStep, BothDoneStep {
private final int number;
private final Object typed;
private final List list;
private BuilderImpl(int number, Object typed, List list) {
this.number = number;
this.typed = typed;
this.list = list;
}
@Override
public BuilderImpl withNumber(int number) {
return new BuilderImpl(number, this.typed, this.list);
}
@Override
public BuilderImpl withTyped(Object typed) {
// we could return 'this' at the risk of heap pollution
return new BuilderImpl(this.number, typed, this.list);
}
@Override
public BuilderImpl withList(List list) {
// we could return 'this' at the risk of heap pollution
return new BuilderImpl(this.number, this.typed, list);
}
@Override
public SomeObject create() {
return new SomeObject(number, typed, list);
}
}
// static factory
public static NeitherDoneStep builder() {
return new BuilderImpl(0, null, null);
}
由于我们不希望人们访问丑陋的实现,我们将其设为私有(private)并让每个人都通过 static
方法。
否则它的工作原理与您自己的想法几乎相同:
SomeObject<String, Integer> works =
SomeObject.builder()
.withNumber(4)
.withList(new ArrayList<Integer>())
.withTyped("something")
.create();
// we could return 'this' at the risk of heap pollution
这是怎么回事?好的,所以这里有一个一般性的问题,它是这样的:
NeitherDoneStep step = SomeObject.builder();
BothDoneStep<String, Integer> both =
step.withTyped("abc")
.withList(Arrays.asList(123));
// setting 'typed' to an Integer when
// we already set it to a String
step.withTyped(123);
SomeObject<String, Integer> oops = both.create();
如果我们不创建副本,我们现在会将 123
伪装成 String
。
(如果您仅将构建器用作流畅的调用集,则不会发生这种情况。)
虽然我们不需要为 withNumber
制作副本,但我只是做了额外的一步,让构建器不可变。我们正在创建比我们必须创建的更多的对象,但实际上并没有另一个好的解决方案。如果每个人都打算以正确的方式使用构建器,那么我们可以使它可变并返回它
。
由于我们对新颖的通用解决方案感兴趣,这里是单个类中的构建器实现。
这里的区别在于,如果我们第二次调用它们的任何一个 setter ,我们不会保留 typed
和 list
的类型。这本身并不是真正的缺点,我猜只是不同而已。这意味着我们可以这样做:
SomeObject<Long, String> =
SomeObject.builder()
.withType( new Integer(1) )
.withList( Arrays.asList("abc","def") )
.withType( new Long(1L) ) // <-- changing T here
.create();
public static class OneBuilder<T, S> {
private final int number;
private final T typed;
private final List<S> list;
private OneBuilder(int number, T typed, List<S> list) {
this.number = number;
this.typed = typed;
this.list = list;
}
public OneBuilder<T, S> withNumber(int number) {
return new OneBuilder<T, S>(number, this.typed, this.list);
}
public <TR> OneBuilder<TR, S> withTyped(TR typed) {
// we could return 'this' at the risk of heap pollution
return new OneBuilder<TR, S>(this.number, typed, this.list);
}
public <SR> OneBuilder<T, SR> withList(List<SR> list) {
// we could return 'this' at the risk of heap pollution
return new OneBuilder<T, SR>(this.number, this.typed, list);
}
public SomeObject<T, S> create() {
return new SomeObject<T, S>(number, typed, list);
}
}
// As a side note,
// we could return e.g. <?, ?> here if we wanted to restrict
// the return type of create() in the case that somebody
// calls it immediately.
// The type arguments we specify here are just whatever
// we want create() to return before withTyped(...) and
// withList(...) are each called at least once.
public static OneBuilder<Object, Object> builder() {
return new OneBuilder<Object, Object>(0, null, null);
}
创建副本和堆污染也是如此。
现在我们变得真正新颖了。这里的想法是我们可以通过引起捕获转换错误来“禁用”每个方法。
解释起来有点复杂,但基本思路是:
?
来“禁用”该方法。这个例子和前面例子的区别在于,如果我们尝试第二次调用一个setter,我们会得到一个编译器错误:
SomeObject<Long, String> =
SomeObject.builder()
.withType( new Integer(1) )
.withList( Arrays.asList("abc","def") )
.withType( new Long(1L) ) // <-- compiler error here
.create();
因此,我们只能调用每个 setter 一次。
这里的两个主要缺点是您:
null
文字第二次调用setter。我认为这是一个非常有趣的概念验证,即使它有点不切实际。
public static class OneBuilder<T, S, TCAP, SCAP> {
private final int number;
private final T typed;
private final List<S> list;
private OneBuilder(int number, T typed, List<S> list) {
this.number = number;
this.typed = typed;
this.list = list;
}
public OneBuilder<T, S, TCAP, SCAP> withNumber(int number) {
return new OneBuilder<T, S, TCAP, SCAP>(number, this.typed, this.list);
}
public <TR extends TCAP> OneBuilder<TR, S, ?, SCAP> withTyped(TR typed) {
// we could return 'this' at the risk of heap pollution
return new OneBuilder<TR, S, TCAP, SCAP>(this.number, typed, this.list);
}
public <SR extends SCAP> OneBuilder<T, SR, TCAP, ?> withList(List<SR> list) {
// we could return 'this' at the risk of heap pollution
return new OneBuilder<T, SR, TCAP, SCAP>(this.number, this.typed, list);
}
public SomeObject<T, S> create() {
return new SomeObject<T, S>(number, typed, list);
}
}
// Same thing as the previous example,
// we could return <?, ?, Object, Object> if we wanted
// to restrict the return type of create() in the case
// that someone called it immediately.
// (The type arguments to TCAP and SCAP should stay
// Object because they are the initial bound of TR and SR.)
public static OneBuilder<Object, Object, Object, Object> builder() {
return new OneBuilder<Object, Object, Object, Object>(0, null, null);
}
同样,创建副本和堆污染也是如此。
无论如何,我希望这能给您一些想法,让您全神贯注。 :)
如果你一般对这类事情感兴趣,我建议学习 code generation with annotation processing , 因为你可以生成这样的东西比手写它们容易得多。正如我们在评论中谈到的那样,手写这样的东西很快就会变得不现实。
关于java - Java 中的通用 fluent Builder,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36778148/
我想做的是让 JTextPane 在 JPanel 中占用尽可能多的空间。对于我使用的 UpdateInfoPanel: public class UpdateInfoPanel extends JP
我在 JPanel 中有一个 JTextArea,我想将其与 JScrollPane 一起使用。我正在使用 GridBagLayout。当我运行它时,框架似乎为 JScrollPane 腾出了空间,但
我想在 xcode 中实现以下功能。 我有一个 View Controller 。在这个 UIViewController 中,我有一个 UITabBar。它们下面是一个 UIView。将 UITab
有谁知道Firebird 2.5有没有类似于SQL中“STUFF”函数的功能? 我有一个包含父用户记录的表,另一个表包含与父相关的子用户记录。我希望能够提取用户拥有的“ROLES”的逗号分隔字符串,而
我想使用 JSON 作为 mirth channel 的输入和输出,例如详细信息保存在数据库中或创建 HL7 消息。 简而言之,输入为 JSON 解析它并输出为任何格式。 最佳答案 var objec
通常我会使用 R 并执行 merge.by,但这个文件似乎太大了,部门中的任何一台计算机都无法处理它! (任何从事遗传学工作的人的附加信息)本质上,插补似乎删除了 snp ID 的 rs 数字,我只剩
我有一个以前可能被问过的问题,但我很难找到正确的描述。我希望有人能帮助我。 在下面的代码中,我设置了varprice,我想添加javascript变量accu_id以通过rails在我的数据库中查找记
我有一个简单的 SVG 文件,在 Firefox 中可以正常查看 - 它的一些包装文本使用 foreignObject 包含一些 HTML - 文本包装在 div 中:
所以我正在为学校编写一个 Ruby 程序,如果某个值是 1 或 3,则将 bool 值更改为 true,如果是 0 或 2,则更改为 false。由于我有 Java 背景,所以我认为这段代码应该有效:
我做了什么: 我在这些账户之间创建了 VPC 对等连接 互联网网关也连接到每个 VPC 还配置了路由表(以允许来自双方的流量) 情况1: 当这两个 VPC 在同一个账户中时,我成功测试了从另一个 La
我有一个名为 contacts 的表: user_id contact_id 10294 10295 10294 10293 10293 10294 102
我正在使用 Magento 中的新模板。为避免重复代码,我想为每个产品预览使用相同的子模板。 特别是我做了这样一个展示: $products = Mage::getModel('catalog/pro
“for”是否总是检查协议(protocol)中定义的每个函数中第一个参数的类型? 编辑(改写): 当协议(protocol)方法只有一个参数时,根据该单个参数的类型(直接或任意)找到实现。当协议(p
我想从我的 PHP 代码中调用 JavaScript 函数。我通过使用以下方法实现了这一点: echo ' drawChart($id); '; 这工作正常,但我想从我的 PHP 代码中获取数据,我使
这个问题已经有答案了: Event binding on dynamically created elements? (23 个回答) 已关闭 5 年前。 我有一个动态表单,我想在其中附加一些其他 h
我正在尝试找到一种解决方案,以在 componentDidMount 中的映射项上使用 setState。 我正在使用 GraphQL连同 Gatsby返回许多 data 项目,但要求在特定的 pat
我在 ScrollView 中有一个 View 。只要用户按住该 View ,我想每 80 毫秒调用一次方法。这是我已经实现的: final Runnable vibrate = new Runnab
我用 jni 开发了一个 android 应用程序。我在 GetStringUTFChars 的 dvmDecodeIndirectRef 中得到了一个 dvmabort。我只中止了一次。 为什么会这
当我到达我的 Activity 时,我调用 FragmentPagerAdapter 来处理我的不同选项卡。在我的一个选项卡中,我想显示一个 RecyclerView,但他从未出现过,有了断点,我看到
当我按下 Activity 中的按钮时,会弹出一个 DialogFragment。在对话框 fragment 中,有一个看起来像普通 ListView 的 RecyclerView。 我想要的行为是当
我是一名优秀的程序员,十分优秀!