要在 StriplingWarrior's answer 上构建,我认为以下模式是必要的(这是分层流式构建器 API 的秘诀)。
解决方案
首先,一个基本抽象类(或接口),它规定了返回扩展类的实例的运行时类型的约定:
/**
* @param <SELF> The runtime type of the implementor.
*/
abstract class SelfTyped<SELF extends SelfTyped<SELF>> {
/**
* @return This instance.
*/
abstract SELF self();
}
所有中间扩展类必须是abstract,并保持递归类型参数SELF:
public abstract class MyBaseClass<SELF extends MyBaseClass<SELF>>
extends SelfTyped<SELF> {
MyBaseClass() { }
public SELF baseMethod() {
//logic
return self();
}
}
其他派生类可以以相同的方式进行。但是,如果不使用原始类型或通配符(这违背了模式的目的),这些类都不能直接用作变量类型。例如(如果 MyClass 不是 abstract):
//wrong: raw type warning
MyBaseClass mbc = new MyBaseClass().baseMethod();
//wrong: type argument is not within the bounds of SELF
MyBaseClass<MyBaseClass> mbc2 = new MyBaseClass<MyBaseClass>().baseMethod();
//wrong: no way to correctly declare the type, as its parameter is recursive!
MyBaseClass<MyBaseClass<MyBaseClass>> mbc3 =
new MyBaseClass<MyBaseClass<MyBaseClass>>().baseMethod();
这就是我将这些类称为“中级”的原因,这就是为什么它们都应该标记为abstract。为了关闭循环并利用该模式,“叶子”类是必要的,它将继承的类型参数SELF 解析为自己的类型并实现self()。他们也应该被标记为final以避免违反合同:
public final class MyLeafClass extends MyBaseClass<MyLeafClass> {
@Override
MyLeafClass self() {
return this;
}
public MyLeafClass leafMethod() {
//logic
return self(); //could also just return this
}
}
这样的类使模式可用:
MyLeafClass mlc = new MyLeafClass().baseMethod().leafMethod();
AnotherLeafClass alc = new AnotherLeafClass().baseMethod().anotherLeafMethod();
这里的价值是方法调用可以在类层次结构中上下链接,同时保持相同的特定返回类型。
免责声明
上面是curiously recurring template pattern在Java中的一个实现。这种模式本质上并不安全,应该只保留用于内部 API 的内部工作。原因是无法保证上述示例中的类型参数SELF 实际上会被解析为正确的运行时类型。例如:
public final class EvilLeafClass extends MyBaseClass<AnotherLeafClass> {
@Override
AnotherLeafClass self() {
return getSomeOtherInstanceFromWhoKnowsWhere();
}
}
这个例子暴露了图案中的两个洞:
-
EvilLeafClass 可以“撒谎”并用任何其他扩展 MyBaseClass 的类型替换 SELF。
- 除此之外,不能保证
self() 实际上会返回 this,这可能是也可能不是问题,具体取决于基本逻辑中状态的使用。
由于这些原因,这种模式极有可能被误用或滥用。为防止这种情况发生,请允许 none 所涉及的类被公开扩展 - 请注意我在 MyBaseClass 中使用了包私有构造函数,它替换了隐式公共构造函数:
MyBaseClass() { }
如果可能,也请保留self() package-private,这样它就不会给公共 API 添加噪音和混乱。不幸的是,这只有在 SelfTyped 是抽象类时才有可能,因为接口方法是隐式公共的。
作为 cmets 中的 zhong.j.yu points out,SELF 上的绑定可能会被简单地删除,因为它最终无法确保“自我类型”:
abstract class SelfTyped<SELF> {
abstract SELF self();
}
Yu 建议只依赖合约,并避免因不直观的递归绑定而产生的任何混淆或错误的安全感。就个人而言,我更喜欢离开这个界限,因为SELF extends SelfTyped<SELF> 代表 Java 中 self 类型的 最接近 可能的表达式。但余的观点绝对符合Comparable的先例。
结论
这是一个有价值的模式,它允许对构建器 API 进行流畅和富有表现力的调用。我在认真的工作中使用过它几次,最值得注意的是编写一个自定义查询构建器框架,它允许这样的调用站点:
List<Foo> foos = QueryBuilder.make(context, Foo.class)
.where()
.equals(DBPaths.from_Foo().to_FooParent().endAt_FooParentId(), parentId)
.or()
.lessThanOrEqual(DBPaths.from_Foo().endAt_StartDate(), now)
.isNull(DBPaths.from_Foo().endAt_PublishedDate())
.or()
.greaterThan(DBPaths.from_Foo().endAt_EndDate(), now)
.endOr()
.or()
.isNull(DBPaths.from_Foo().endAt_EndDate())
.endOr()
.endOr()
.or()
.lessThanOrEqual(DBPaths.from_Foo().endAt_EndDate(), now)
.isNull(DBPaths.from_Foo().endAt_ExpiredDate())
.endOr()
.endWhere()
.havingEvery()
.equals(DBPaths.from_Foo().to_FooChild().endAt_FooChildId(), childId)
.endHaving()
.orderBy(DBPaths.from_Foo().endAt_ExpiredDate(), true)
.limit(50)
.offset(5)
.getResults();
关键点在于QueryBuilder 不仅仅是一个平面实现,而是从构建器类的复杂层次结构扩展而来的“叶子”。相同的模式用于Where、Having、Or 等助手,所有这些都需要共享重要代码。
但是,您不应忽视这样一个事实,即所有这些最终都只是语法糖。一些有经验的程序员take a hard stance against the CRT pattern,或者至少are skeptical of the its benefits weighed against the added complexity。他们的担忧是合理的。
归根结底,在实施之前仔细看看它是否真的有必要——如果有,不要让它公开可扩展。