要建立在
StriplingWarrior’s answer上,我认为需要以下模式(这是一个分层流利的构建器API的方法)。
解
首先,一个基本的抽象类(或接口),其中列出了用于返回扩展类的实例的运行时类型的合同:
/**
* @param The runtime type of the implementor.
*/
abstract class SelfTyped> {
/**
* @return This instance.
*/
abstract SELF self();
}
所有中间扩展类必须是抽象的并且保持递归类型参数SELF:
public abstract class MyBaseClass>
extends SelfTyped {
MyBaseClass() { }
public SELF baseMethod() {
//logic
return self();
}
}
其他派生类可以按照相同的方式进行。但是,这些类都不能直接用作变量类型,而不使用原型或通配符(这违反了模式的目的)。例如(如果MyClass不是抽象的):
//wrong: raw type warning
MyBaseClass mbc = new MyBaseClass().baseMethod();
//wrong: type argument is not within the bounds of SELF
MyBaseClass mbc2 = new MyBaseClass().baseMethod();
//wrong: no way to correctly declare the type, as its parameter is recursive!
MyBaseClass> mbc3 =
new MyBaseClass>().baseMethod();
这就是我把这些课程称为“中间”的原因,这就是为什么他们都应该被标记为抽象的。为了关闭循环并使用模式,需要“叶”类,它用自己的类型解析继承类型参数SELF并实现self()。他们也应该标记为最终,以避免违约:
public final class MyLeafClass extends MyBaseClass {
@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();
这里的值是该方法调用可以在类层次结构中上下链接,同时保持相同的特定返回类型。
免责声明
以上是Java中的curiously recurring template pattern的实现。这种模式本身并不安全,只能用于内部API的内部工作。原因是不能保证上述示例中的类型参数SELF实际上将被解析为正确的运行时类型。例如:
public final class EvilLeafClass extends MyBaseClass {
@Override
AnotherLeafClass self() {
return getSomeOtherInstanceFromWhoKnowsWhere();
}
}
此示例在模式中显示两个孔:
> EvilLeafClass可以“撒谎”,并将任何其他类型的扩展MyBaseClass替换为SELF。
>独立于此,不能保证self()将实际返回这个,这可能是也可能不是一个问题,这取决于在基本逻辑中使用状态。
由于这些原因,这种模式很可能被滥用或滥用。为了防止这种情况,不允许任何涉及的类被公开扩展 – 请注意我使用MyBaseClass中的package-private构造函数,它替换隐式的公共构造函数:
MyBaseClass() { }
如果可能的话,保持self()包私有,所以它不会增加噪音和混乱的公共API。不幸的是,只有SelfTyped是一个抽象类,这是因为接口方法是隐式公开的。
作为zhong.j.yu points out在评论中,对SELF的约束可能会被删除,因为它最终不能保证“self type”:
abstract class SelfTyped {
abstract SELF self();
}
Yu建议仅依赖合同,避免来自非直观递归约束的任何混淆或虚假的安全感。就个人而言,我喜欢离开绑定,因为SELF扩展了SelfTyped< SELF>代表Java中最近可能的self类型表达式。但是,俞先生的观点肯定与Comparable规定的先例相一致。
结论
这是一个值得的模式,允许对您的构建器API进行流畅和表达性的调用。我在认真的工作中使用了几次,最引人注目的是编写了一个自定义查询构建器框架,它允许调用网站如下所示:
List 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等等,所有这些都需要共享重要的代码。
底线,请仔细看看在实施之前是否真的有必要 – 如果你这样做,不要让它公开扩展。