Reputation: 409
Currently in my Java program, I have a base Parent class with several methods which return a new instance of itself. For example:
public BasePage fill(String input, By locator) {
driver.findElement(locator).clear();
driver.findElement(locator).sendKeys(input);
return new BasePage(driver);
}
As well, I have several classes which extend this base class with their own methods. My question is this: is there a way in my main program to call these Parent methods, but assign the returned value to a child class without using casts? For example, if I had a loginPage
Child class which needed to use this method in a main program, how would I turn this:
loginPage = (LoginPage) loginPage.fill("username", usernameLocator);
into this:
loginPage = loginPage.fill("username", usernameLocator);
where I initialize the class:
public class LoginPage extends BasePage {
}
While not mentioned in this example, is it possible to allow it to return any of the child classes, such that it is not restricted to just one class.
Upvotes: 3
Views: 2183
Reputation: 2819
There several ways to proceed here, but if the new instance if pretty much the same as the old one with perhaps some modifications I think you should consider to make your hierarchy clone-able.
public class BasePage implements Cloneable {
public BasePage fill(String input, By locator) {
driver.findElement(locator).clear();
driver.findElement(locator).sendKeys(input);
return (BasePage) clone();
}
@Override
protected Object clone() {
try {
return super.clone();
} catch ( CloneNotSupportedException ex) {
throw new RuntimeException("this can't ever happen!!!",ex);
}
}
...
}
Then the child classes need to override the parent's fill with the appropriate cast
public class LoginPage extends BasePage {
@Override
public LoginPage fill(String input, By locator) {
return (LoginPage) super.fill(input,locator);
}
}
Notice that clone will always return an instance of the original invoking instance... in this case LoginPage.
This approach may not work or would need refinement depending on what are the other member fields in the hierarchy and how these are supposed to be shared between clones.
A different approach that works as long as you keep constant the signature of the constructor of all child classes, in this case (Driver), is to keep the logic of the method fill in the parent and select the appropriate child class by passing the class object...
public class BasePage {
public <T extends BasePage> T fill(String input, By locator, Class<T> clazz) {
driver.findElement(locator).clear();
driver.findElement(locator).sendKeys(input);
// not sure if the cast is even needed.
try {
return (T) clazz.getConstructor(Driver.class).newInstance(driver);
} catch (Exception ex) {
throw new RuntimeException("whoops! something went wrong",ex);
}
}
}
Then to get a LoginPage...
LoginPage lp = page.fill(input,loc,LoginPage.class);
You can overload fill in the children classes to avoid passing the class but then we would be doing as much as work as with the clone solution above.
Upvotes: 1
Reputation: 181907
No, not as such. If BasePage
is the formal type of the reference on which you invoke the given fill()
method, then the formal type of the return value is BasePage
. You cannot treat that value as a subtype of BasePage
without a cast.
In your particular example, even a cast should fail with a ClassCastException
because the base class's method, which the subclass is not shown to override, returns an object whose class is BasePage
, and such an object is not an instance of LoginPage
.
However, a subclass could override that method, employing a covariant return type:
public class LoginPage extends BasePage {
@Override
public LoginPage fill(String input, Locator by) {
return new LoginPage(/* ... */);
}
}
In that case, if you invoke that method on a reference to a LoginPage
(or one of its subclasses) then the return value is an instance of LoginPage
and can be treated as such:
LoginPage page1 = new LoginPage();
LoginPage page2 = page1.fill("username", usernameLocator);
Upvotes: 0
Reputation: 178363
Yes, there is a way. In the subclass (e.g. LoginPage
), override the fill
method. With Java's covariant return types (scroll to the bottom), you can specify LoginPage
as the return type.
public LoginPage fill(String input, By locator) {
driver.findElement(locator).clear();
driver.findElement(locator).sendKeys(input);
return new LoginPage(driver);
}
That assumes that you can create a LoginPage
with a driver
, assumed to be of a Driver
class, which means you need a subclass constructor that takes a Driver
.
public LoginPage(Driver driver)
{
super(driver);
// Anything else LoginPage-specific goes here
}
Upvotes: 3