Fluent API and Generics with example
Look at the class ChromiumOptions at https://github.com/SeleniumHQ/selenium/blob/e8745c61e3ab3d1873d1a8cb178fc166aed63e71/dotnet/src/webdriver/Chromium/ChromiumOptions.cs
The use of generics in the declaration public class ChromiumOptions<T extends ChromiumOptions<?>>
allows for a more flexible and type-safe implementation of the ChromiumOptions
class. Let's delve into the details of why T
is used and what would happen if it were not used.
Detailed Explanation
1. Generics in Java
Generics provide a way to parameterize types, making code more flexible and type-safe. The T
in ChromiumOptions<T extends ChromiumOptions<?>>
is a generic type parameter.
2. Fluent API and Method Chaining
The primary reason for using T extends ChromiumOptions<?>
is to support a fluent API, which relies on method chaining. The fluent API pattern allows methods to return the current instance (this
), so multiple method calls can be chained together in a readable manner.
Example with Generics
Consider the following method in the ChromiumOptions
class:
public class ChromiumOptions<T extends ChromiumOptions<?>> extends AbstractDriverOptions<ChromiumOptions<?>> {
// Other fields and methods
public T setBinary(String path) {
this.binary = path;
return (T) this;
}
public T addArgument(String argument) {
this.args.add(argument);
return (T) this;
}
// Other methods
}
Here, T
allows the methods to return the current instance of ChromiumOptions
or any subclass thereof. This ensures that the return type matches the actual type of the object, supporting fluent chaining.
Why T extends ChromiumOptions<?>
?
The T extends ChromiumOptions<?>
constraint ensures that T
is a subtype of ChromiumOptions<?>
. This allows methods to return T
instead of ChromiumOptions<?>
, enabling fluent API chaining that is type-safe.
What Happens Without T
If we remove the generic T
, the methods would return ChromiumOptions<?>
or just ChromiumOptions
. This would restrict the ability to use fluent method chaining in subclasses or in cases where a more specific type is required.
Example Without Generics
public class ChromiumOptions extends AbstractDriverOptions<ChromiumOptions> {
// Other fields and methods
public ChromiumOptions setBinary(String path) {
this.binary = path;
return this;
}
public ChromiumOptions addArgument(String argument) {
this.args.add(argument);
return this;
}
// Other methods
}
In this case, the methods return ChromiumOptions
, which means the fluent API will only work for the ChromiumOptions
class and not for any subclasses that might want to extend ChromiumOptions
.
Subclass Example
Consider a subclass ChromeOptions
that extends ChromiumOptions
:
public class ChromeOptions extends ChromiumOptions<ChromeOptions> {
// Additional Chrome-specific methods
}
With generics:
ChromeOptions options = new ChromeOptions()
.setBinary("/path/to/chrome")
.addArgument("--headless");
Without generics, the subclass would lose the ability to return its own type, breaking the fluent API:
public class ChromeOptions extends ChromiumOptions {
// Additional Chrome-specific methods
}
// Usage without generics
ChromeOptions options = new ChromeOptions()
.setBinary("/path/to/chrome") // Returns ChromiumOptions, not ChromeOptions
.addArgument("--headless"); // Further method calls won't be recognized on ChromeOptions