Использование конкретного класса из переменной абстрактного типа
Извините, если этот вопрос уже задавался, я еще не нашел ничего похожего на мой вопрос...
Я работаю / играю / учусь, чтобы создать какую-то среду тестирования... Внутри нее я создаю прикладной уровень (пакет классов, которые являются виртуальным представлением различных страниц / окон / форм) приложение. Упрощенная настройка заключается в следующем:
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();
}
}
Идея заключается в том, что я хочу, чтобы мой тест содержал только одну текущую веб-страницу. Я не хочу создавать новую переменную каждый раз, когда я меняю страницу.
Я также не хочу, чтобы меня заранее знали, на какую страницу я направляюсь. Я хочу, чтобы прикладной уровень давал мне поток приложения. Точно так же, как при переходе по ссылке, вы переходите на следующую страницу, я хочу, чтобы при нажатии на ссылку, которая приводит меня на другую страницу, этот метод сообщал мне, на какую страницу я направляюсь.
(Абстрактный класс WebPage также предоставляет множество общих методов для всех конкретных веб-страниц)
Таким образом, мое предполагаемое использование было:
WebPage currentPage = new HomePage();
currentPage = currentPage.ClickLinktoProfilePage(); //currentPage = new ProfilePage();
System.out.println(currentPage.getUsername());
currentPage.menuLogout();
К сожалению, это не работает, поскольку переменная currentPage типизируется как WebPage, она не может видеть ни один из методов конкретных классов. Я нахожу это логичным и нечетным одновременно, потому что я могу спросить "currentPage.getClass(). GetName();" и он вернет "packageName.ConcreteClassName".
Чтобы Typecasting работал, мне нужно было бы переопределить тип переменной... (не уверен, возможно ли это или даже полезно).
Так что я знаю, что могу найти имя класса внутри переменной, но я не уверен, куда идти дальше.
У кого-нибудь есть решение?
2 ответа
Чтобы уточнить, что Radiodef и я говорим в комментариях здесь:
Что вы хотите, чтобы определить WebPage
(ваш абстрактный API) таким образом, что ваши конкретные подклассы не должны иметь открытые методы, которые не являются частью этого API.
Например, сравните java.util.List
интерфейс в стандартной библиотеке. Есть несколько реализаций этого интерфейса (ArrayList
а также LinkedList
являются наиболее известными, но есть много других), но большинство кода, который использует List
не нужно заботиться о том, использует ли он на самом деле ArrayList
или LinkedList
или что-то еще, так как все необходимые операции открываются через List
интерфейс.
Вы можете сделать то же самое с вашим WebPage
учебный класс. Например, вы можете определить серию "хуков" для различных операций, которые вы можете выполнять с веб-страницей:
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());
}
}
И тогда ваши страницы будут реализовывать этот контракт следующим образом:
// 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";
}
}
Тогда код, который хочет работать с WebPage
не нужно знать, что это Page1
(или любая другая страница):
public static void printPageName(WebPage webPage) {
System.out.println(webPage.getNameToLinkTo());
}
В качестве альтернативы, как говорит Resueman, вы можете просто использовать Page1
, Page2
и т. д., печатает напрямую, используя WebPage
только для реализации наследования, а не API. Это тоже хорошо - правильное решение зависит от того, насколько гибким (и сложным) вы хотите, чтобы ваш код был.
Вы можете вызывать методы для конкретного типа, приведя его следующим образом:
WebPage currentPage = new Page1();
currentPage = ((Page1)currentPage).clickLink();
((Page2)currentPage).printHello();
Однако, это, скорее всего, неправильное решение проблемы. Это было бы лучше сделать, если бы абстрактный класс содержал эти методы, чтобы их можно было вызывать без приведения, или чтобы ваши переменные были конкретным типом. Прямо сейчас вы пишете код, который утверждает, что принимает любой WebPage
, но на самом деле ожидает определенный тип, удаляя безопасность типов, которую обычно дает статическая типизация.
В вашем случае, кажется, нет никаких причин делать переменную WebPage
, Во-первых, это должно быть, и, как известно, Page1
, Тогда это должно быть Page2
и опять же, вы знаете, что это один. Поэтому правильное решение - точно отразить это на ваших типах:
Page1 currentPage = new Page1();
Page2 linkedPage = currentPage.clickLink();
linkedPage.printHello();
Это совершенно безопасно для типов и делает код более самодокументированным.
Если это не работает, вы должны увидеть, может ли другое поведение быть выражено как специализация API, который вы определяете WebPage
, Из комментария Daniel Pryden к другому ответу,
Намного лучше определить API в самых общих терминах и позволить реализациям этого API специализироваться. По сути, WebPage (абстрактный класс) должен предоставлять достаточно гибкий API, который может потребоваться любой операции, которую может потребовать код клиента (который не знает, с каким типом WebPage он работает).