skip to content

When would you use an ExtensiblePolymorphicDomainObjectContainer, and how does registerFactory / registerBinding enable a polymorphic named DSL?

level: seniorimportance: should knowfreq 28%

answer

  1. many subtypes in one named collection
  2. like TaskContainer / publications
  3. registerFactory = custom instantiation
  4. registerBinding(public, impl) = managed
  5. withType(Sub).configureEach filters

basics

~20 s

Use 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 s

A 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 lines
kotlin
val 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

for a junior

Recognize that some containers (like tasks) hold many types; deeper detail is beyond junior scope.

for a middle

Know register(name, Type) creates typed elements and withType filters by subtype.

for a senior

Explain registerFactory vs registerBinding, when polymorphism is justified, and how the typed DSL and withType.configureEach compose.

for a principal

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.

context