为了基于StriplingWarrior的答案,我认为以下模式将是必要的(这是分层流利的Builder API的配方)。
解
首先,一个基本抽象类(或接口)列出了返回扩展该类实例的运行时类型的协定:
/**
* @param The runtime type of the implementor.
*/
abstract class SelfTyped> {
/**
* @return This instance.
*/
abstract SELF self();
}
所有中间扩展类都必须是QueryBuilder并维护递归类型参数Where:
public abstract class MyBaseClass>
extends SelfTyped {
MyBaseClass() { }
public SELF baseMethod() {
//logic
return self();
}
}
可以以相同的方式遵循其他派生类。 但是,这些类都不能直接用作变量类型,而无需诉诸原始类型或通配符(这违背了模式的目的)。 例如(如果QueryBuilder不是Where):
//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();
这就是我将这些类称为“中间类”的原因,因此也都应将它们都标记为QueryBuilder。为了闭合循环并利用模式,必须使用“叶子”类,这些类可以解析继承的类型参数 Where具有自己的类型和实现Having。还应标记Or,以避免违反合同:
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中奇怪重复模板模式的实现。 这种模式并非天生就安全,应仅保留其内部API的内部功能。 原因是不能保证上述示例中的类型参数QueryBuilder实际上将解析为正确的运行时类型。 例如:
public final class EvilLeafClass extends MyBaseClass {
@Override
AnotherLeafClass self() {
return getSomeOtherInstanceFromWhoKnowsWhere();
}
}
本示例在模式中暴露了两个孔:
QueryBuilder可以“撒谎”并用任何其他扩展Where的类型替换为Having。
除此之外,我们无法保证3002891441841631636352实际上会返回Where,这可能是问题,也可能不是问题,具体取决于基本逻辑中状态的使用。
由于这些原因,这种模式很可能被滥用或滥用。 为防止这种情况,不允许公开涉及的任何类-请注意我在QueryBuilder中使用package-private构造函数,该构造函数替换了隐式的public构造函数:
MyBaseClass() { }
如果可能的话,也将QueryBuilder的包私有设置为私有,这样就不会给公共API带来噪音和混乱。 不幸的是,这仅在Where是抽象类的情况下才有可能,因为接口方法是隐式公共的。
正如zhong.j.yu在评论中指出的那样,可以简单地删除QueryBuilder上的边界,因为它最终无法确保“自我类型”:
abstract class SelfTyped {
abstract SELF self();
}
Yu建议仅依赖合同,避免因直觉上的递归范围而引起的任何混乱或错误的安全感。 就个人而言,由于QueryBuilder表示Java中self类型的最可能表达,因此我更倾向于保留界限。 但是,于的观点肯定与QueryBuilder所设定的先例相吻合。
结论
这是一个值得的模式,它允许对构建器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、Or等,所有这些都需要共享大量代码。
但是,您不应忘记所有这些最终仅构成语法糖这一事实。 一些经验丰富的程序员对CRT模式持强硬立场,或者至少对将其优势与复杂性相权衡表示怀疑。 他们的担忧是合理的。
最重要的是,在实现它之前仔细研究一下它是否真的必要-如果这样做,则不要使其可公开扩展。