Чтобы построить на ответ StriplingWarrior , я думаю, что будет необходим следующий шаблон (это рецепт для иерархического 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();
Здесь значение того, что вызовы методов могут быть связаны цепочкой вверх и вниз по иерархии классов, сохраняя тот же конкретный тип возвращаемого значения.
ОТКАЗ
Выше приведена реализация любопытно повторяющегося шаблона в Java. Этот шаблон небезопасен и должен быть зарезервирован только для внутренней работы своего внутреннего API. Причина в том, что нет никакой гарантии, что параметр типа SELF
в приведенных выше примерах будет фактически преобразован в правильный тип времени выполнения. Например:
public final class EvilLeafClass extends MyBaseClass<AnotherLeafClass> {
@Override
AnotherLeafClass self() {
return getSomeOtherInstanceFromWhoKnowsWhere();
}
}
В этом примере показаны два отверстия в шаблоне:
EvilLeafClass
может «лежать» и заменять любой другой тип, расширяющий MyBaseClass
на SELF
.
- Независимо от этого, нет гарантии, что
self()
действительно вернет this
, что может быть или не быть проблемой, в зависимости от использования состояния в базовой логике.
По этим причинам эта модель имеет большой потенциал для злоупотребления или злоупотребления. Чтобы предотвратить это, разрешите публичное расширение none участвующих классов - обратите внимание на мое использование конструктора private-package в MyBaseClass
, который заменяет неявный открытый конструктор:
MyBaseClass() { }
Если возможно, держите self()
package-private также, чтобы это не добавляло шума и путаницы в публичный API. К сожалению, это возможно только в том случае, если SelfTyped
является абстрактным классом, поскольку методы интерфейса неявно общедоступны.
Как zhong.j.yu указывает на в комментариях, ограничение на SELF
может быть просто удалено, так как в конечном итоге не удается обеспечить "тип":
abstract class SelfTyped<SELF> {
abstract SELF self();
}
Ю советует полагаться только на контракт и избегать путаницы или ложных ощущений безопасности, возникающих из-за неинтуитивной рекурсивной границы. Лично я предпочитаю выходить за границы, поскольку SELF extends SelfTyped<SELF>
представляет ближайшее возможное выражение типа self в Java. Но мнение Юя определенно совпадает с прецедентом, установленным 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
и т. Д., Которые все должны были поделиться значимым кодом.
Однако вы не должны упускать из виду тот факт, что все это в конечном итоге сводится к синтаксическому сахару.Некоторые опытные программисты жестко относятся к шаблону CRT или, по крайней мере, скептически относятся к его преимуществам, сопоставимым с дополнительной сложностью .Их опасения законны.
Итог, внимательно посмотрите, действительно ли это необходимо, прежде чем внедрять его - и если вы это сделаете, не делайте его общедоступным.