简体   繁体   中英

Using concrete class from a abstract type variable

I'm sorry if this question has been asked already, I haven't found anything like my question yet...

I'm working/playing/learning to build up some kind of testing environment... Inside it, I'm building an Application Layer (a package of classes that are the virtual representation of the different pages/windows/forms) of an application. The simplified setup is the following:

public abstract class WebPage {

    protected WebDriver driver;

    protected WebElement getElement(By by){
        WebElement element = (new WebDriverWait(driver, 10))
              .until(ExpectedConditions.presenceOfElementLocated(by));

    return element;
}

    public void menuLogout(){
        System.out.println("Logged out");
    }
}

public class HomePage extends WebPage {

    public ProfilePage ClickLinktoProfilePage(){
        return new ProfilePage();
    }

    public DashBoardPage clickViewDashboard(){
        return new DashBoardPage();
    }

    public String getTitle(){
        return getElement(By.id("title")).getText();
    }
}

public class ProfilePage extends WebPage {

    public String getUsername(){
        return getElement(By.id("name")).getText();
    }

    public String getEmail(){
    return getElement(By.id("email")).getText();
}       
    public HomePage clickReturnToHomePage(){
        return new HomePage();
    }

}

public class DashBoardPage extends WebPage {

    public String getcurrentPeriod(){
        return getElement(By.id("name")).getText();

}
}

The idea behind this is that I wish my Test to hold only one current WebPage. I do not wish to create a new variable each time I change page.

I also do not want to be forced to know in advance which page I'm heading into. I want the application Layer to give me the flow of the Application. In the same way that when clicking a link, you are brought to the following page, I wish that when I click a link that brings me to another page, that method tells me what page I'm heading into.

(WebPage abstract class also exposes lots of shared methods between all concrete WebPages)

So my intended use was:

WebPage currentPage = new HomePage();

currentPage = currentPage.ClickLinktoProfilePage(); //currentPage = new ProfilePage();
System.out.println(currentPage.getUsername());
currentPage.menuLogout();

Sadly, this does not work, since the currentPage variable is typed as WebPage, it cannot see any of the concrete classes's methods. I find it logical and odd at the same time because I can ask "currentPage.getClass().getName();" and it'll return "packageName.ConcreteClassName".

For Typecasting to work, I would need to redefine the variable's type... (not sure if it's possible or even good to do).

So I know I can find the name of the class inside the variable, but I'm not sure where to go from there.

Anyone got a solution?

To clarify what Radiodef and I are saying in the comments here:

What you want is to define WebPage (your abstract API) in such a way that your concrete subclasses don't need to have public methods that aren't a part of that API.

For example, compare the java.util.List interface in the standard library. There are multiple implementations of this interface ( ArrayList and LinkedList are the most well-known ones, but there are many others), but the majority of code that uses List doesn't need to care whether it's actually using an ArrayList or a LinkedList or something else, since all the operations that you need are exposed via the List interface.

You can do the same thing with your WebPage class. For example, you could define a series of "hooks" for different operations that you can do with a web page:

public abstract class WebPage {
  // methods that each subclass needs to implement
  protected abstract String renderBodyHtml();
  public abstract String getNameToLinkTo();

  // other methods that are common to every page
  public final void serve(
      HttpServletRequest request, HttpServletResponse response) {
     // write the response, using the specific page's body HTML
     response.getWriter().println(renderToBodyHtml());
  }
}

And then your pages would implement that contract like so:

// Note: the class doesn't need to be public, since anybody that uses
// it can just declare their variable as type WebPage
class Page1 extends WebPage {
  @Override protected String renderBodyHtml() {
    return "<body>Hello world!</body>";
  }

  @Override public String getNameToLinkTo() {
     return "Page1";
  }
}

Then code that wants to work with a WebPage doesn't need to know that it's a Page1 (or any other page):

public static void printPageName(WebPage webPage) {
  System.out.println(webPage.getNameToLinkTo());
}

Alternatively, like resueman says, you can just use the Page1 , Page2 , etc., types directly, using WebPage only for implementation inheritance, not API. This is fine as well -- the correct solution depends on how flexible (and complex) you want your code to be.

You can call methods on the concrete type by casting it, like this:

WebPage currentPage = new Page1();
currentPage = ((Page1)currentPage).clickLink();
((Page2)currentPage).printHello();

However, this is most likely the wrong solution to the problem. This would be better handled by either having the abstract class contain those methods, so that they can be called without casting, or having your variables be the concrete type. Right now you're writing code that claims to accept any WebPage , but actually expects a specific kind, removing the type safety that static typing would normally give you.

In your case, there doesn't seem to be any reason to make the variable be a WebPage . At first, it must be, and is known to be, a Page1 . Then, it must be a Page2 and again, you know it is one. So the correct solution is to accurately reflect that with your types:

Page1 currentPage = new Page1();
Page2 linkedPage = currentPage.clickLink();
linkedPage.printHello();

This is perfectly type safe, and makes the code more self-documenting.

If this doesn't work then you should see if the different behavior can be expressed as specialization of the api you're defining with WebPage . From Daniel Pryden 's comment on another answer ,

It's much better to define an API in the most general terms possible, and allow implementations of that API to specialize. Basically, WebPage (the abstract class) should expose a sufficiently flexible API that any operation that client code (which doesn't know what kind of WebPage it's working with) might want.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM