When would you use an ExtensiblePolymorphicDomainObjectContainer, and how does registerFactory / registerBinding enable a polymorphic named DSL?
answer
- many subtypes in one named collection
- like TaskContainer / publications
- registerFactory = custom instantiation
- registerBinding(public, impl) = managed
- withType(Sub).configureEach filters
basics
~20 sUse it when one named container must hold several distinct element types (like tasks). You register a factory or type binding per subtype, then users create typed elements by name with register(name, Type), getting a polymorphic DSL.
solid answer
~40 sA plain `NamedDomainObjectContainer<T>` holds one concrete element type. When a single named collection must contain **multiple, distinct subtypes** — the way `TaskContainer` holds many task classes, or a `publications` container holds `MavenPublication` and `IvyPublication` — you need a polymorphic container. `ExtensiblePolymorphicDomainObjectContainer<T>` is the extensible variant: plugins register the subtypes it can create. You wire a subtype with `registerFactory(Type) { name -> ... }` (full control over instantiation) or, more commonly, `registerBinding(PublicType, ImplType)` to let Gradle's `ObjectFactory` instantiate a managed implementation. Once bound, users author a polymorphic DSL: `container.register("a", Foo::class) { }`, `container.register("b", Bar::class) { }`, and `withType(Foo::class).configureEach { }` filters by subtype. You create the base container with `objects.polymorphicDomainObjectContainer(T::class.java)`. It's the pattern that makes one DSL block hold heterogeneous, extensible element kinds while keeping name-based access, laziness, and typed bulk configuration.
code
kotlin · 13 linesval endpoints = objects.polymorphicDomainObjectContainer(Endpoint::class.java)
as ExtensiblePolymorphicDomainObjectContainer<Endpoint>
// Let ObjectFactory build managed impls
endpoints.registerBinding(HttpEndpoint::class.java, HttpEndpoint::class.java)
endpoints.registerBinding(GrpcEndpoint::class.java, GrpcEndpoint::class.java)
// Or full control via a factory:
// endpoints.registerFactory(WsEndpoint::class.java) { name -> WsEndpoint(name) }
// Typed lazy creation + filtered bulk config
endpoints.register("api", HttpEndpoint::class.java) { method.set("POST") }
endpoints.withType(HttpEndpoint::class.java).configureEach { url.convention("https://x") }go deeper
Recognize that some containers (like tasks) hold many types; deeper detail is beyond junior scope.
Know register(name, Type) creates typed elements and withType filters by subtype.
Explain registerFactory vs registerBinding, when polymorphism is justified, and how the typed DSL and withType.configureEach compose.
Decide whether a plugin's extensibility surface warrants a polymorphic container, governing the public-type contract third parties bind against.
## The problem it solves A `NamedDomainObjectContainer<T>` is monomorphic in practice: it creates one concrete `T` per name. But some DSLs need a single named collection to hold *different* element types. `tasks` holds hundreds of distinct `Task` subclasses; `publications` holds `MavenPublication` and `IvyPublication`. You can't model that with a plain container — you need a **polymorphic** one. ## The types - `PolymorphicDomainObjectContainer<T>` — a container whose elements may be of various subtypes of `T`; supports `register(name, Type)` and `create(name, Type)`. - `ExtensiblePolymorphicDomainObjectContainer<T>` — the variant that lets *plugins* register which subtypes can be created, via factories or bindings. This is what you author for an extensible plugin DSL. ## Registering subtypes Two mechanisms: ### registerFactory(Type, factory) You supply a `NamedDomainObjectFactory<U>` (a `name -> U`). Full control: you decide how each instance of subtype `U` is constructed. Use when instantiation needs custom logic or constructor args. ### registerBinding(publicType, implType) You declare "when someone asks for `publicType`, instantiate `implType`." Gradle's `ObjectFactory` constructs the implementation as a *managed* object, injecting `name`, `Property`, `ListProperty`, nested containers, etc. This is the preferred, lower-boilerplate path for managed types. ## The resulting DSL ```kotlin // Plugin setup abstract class Endpoint(val name: String) { abstract val url: Property<String> } abstract class HttpEndpoint(name: String) : Endpoint(name) { abstract val method: Property<String> } abstract class GrpcEndpoint(name: String) : Endpoint(name) { abstract val service: Property<String> } val endpoints = objects.polymorphicDomainObjectContainer(Endpoint::class.java) as ExtensiblePolymorphicDomainObjectContainer<Endpoint> endpoints.registerBinding(HttpEndpoint::class.java, HttpEndpoint::class.java) endpoints.registerBinding(GrpcEndpoint::class.java, GrpcEndpoint::class.java) project.extensions.add("endpoints", endpoints) ``` Users then write a polymorphic, typed, lazy DSL: ```kotlin // endpoints { // register<HttpEndpoint>("api") { method.set("POST") } // register<GrpcEndpoint>("stream") { service.set("Feed") } // } // endpoints.withType<HttpEndpoint>().configureEach { url.convention("https://...") } ``` ## What you keep All the container goodness still applies: name-based access, `register` laziness, `named`, and crucially `withType(Subtype).configureEach { }` for *type-filtered* bulk configuration — the polymorphic payoff. You can configure all HTTP endpoints in one rule without touching gRPC ones. ## When NOT to use it If every element is the same type, a plain `NamedDomainObjectContainer` is simpler. Reach for polymorphic only when heterogeneity and plugin-extensibility of the element types are real requirements.
- What's the difference between registerFactory and registerBinding?registerFactory supplies a custom name->instance factory giving full control of construction; registerBinding maps a public type to an implementation type and lets Gradle's ObjectFactory create it as a managed object with injected name/Property/etc.
- How do you configure only one subtype of a polymorphic container in bulk?Use withType(Subtype::class.java).configureEach { } — a lazy, type-filtered bulk rule that ignores other subtypes.
- Name a built-in Gradle polymorphic container.TaskContainer (holds many Task subtypes) and the publications container (MavenPublication, IvyPublication) are polymorphic containers.
saying these in an interview costs you the question
- Using a polymorphic container when all elements are one type — unnecessary complexity.
- Claiming registerBinding requires writing a factory — it delegates instantiation to ObjectFactory.
- Forgetting that withType(...).configureEach is the type-filtered, lazy way to bulk-configure a subtype.