t.me/atinfo_chat Telegram группа по автоматизации тестирования

Избитая тема интерфейсов

Начал пробовать использовать интерфейсы (Java). Понятно, что 2 разных класса могут иметь разную реализацию, но одинаковый интерфейс: например public void click(). Но как бы я не пытался применить их, у меня сводится все к общему методу который можно просто засунуть в отдельный класс и унаследовать его 2 другими. В интернете полно примеров, но они грубо говоря сделаны не для размышления (сложности нет). Нашел правда форум где парень вроде бы объяснил зачем нужен интерфейс на примере: ссылка. Но также не до конца понял зачем он нужен. Можете также задать задачку или объяснить на реальном примере интерфейсы (например взять сайт)? Спасибо.

Интерфейсы используют например для того, чтобы метод, в котором будет использоваться эекземпляр класса, реализующего интерфейс, мог абстрагироваться от реализации этого класса, и просто вызывать нужные ему методы.

Короче, они нужны для “абстрагирования кусков кода друг от друга”. Чтобы один кусок кода мог юзать другой кусок кода, не заботясь о том, как он реализован внутри. Это позволяет добиться слабой связанности кода, что в свою очередь позволяет проще изменять реализацию отдельных кусков внутри, не меняя интерфейсов их взаимодействия

На мой взгляд необходимость интерфейса становится понятной при изучении паттерна проектирования Abstract Factory

Тут нужно отталкиваться от того, что в java нет понятия множественного наследования. Если вы копнете глубже и постараетесь понять разницу между абстрактными классами и интерфейсами, вы сами сможете ответить на свой вопрос.

По вашему примеру: в случае, если кому-то понадобится переопределить поведение click(), ему обязательно нужно будет унаследовать ваш абстрактный класс. Но что если их класс уже наследует какой-то другой, не менее важный и нужный? Что если им вовсе не нужно расширять ваш класс, а лишь подсунуть совершенно другую имплементацию, при этом сохранив сигнатуры методов, которые используются на более высоком уровне, а также - оригинальный тип, который аналогично может быть использован в качестве входных аргументов высокоуровневых API? Как быть тогда? Вот тут и приходят на помощь интерфейсы, которых можно имплементить сколь угодно много.

К примеру:


public class MyClass extends MyBaseClass implements YourFirstInterface, YourSecondInterface {
    public void overriddenFirstInterfaceMethod() {}

    public void overriddenSecondInterfaceMethod() {}
}

Далее, некие высокоуровневые API могут не заботиться о том, кто и как переопределил методы ваших интерфейсов.


public void firstHighLevelAPI(YourFirstInterface myClassImplementaion) {
    myClassImplementation.overriddenFirstInterfaceMethod();
}

public void secondHighLevelAPI(YourSecondInterface myClassImplementaion) {
    myClassImplementation.overriddenSecondInterfaceMethod();
}

public void someWhereOutside() {
    YourFirstInterface yfi = new MyClass();
    YourSecondInterface ysi = new MyClass();

    fistHighLevelAPI(yfi);
    secondHighLevelAPI(ysi);
}

Причем, приведение к типу интерфейса является операцией сужения, т.е. высокоуровневые API не будут ничего знать о том, что еще имеется в моем классе, кроме переопределенных методов. Что в целом и является одной из основных идей, когда мы можем не задумываться о реализации: мой класс будет иметь одну, чей-то еще - другую. Но при этом оба будут соблюдать “договор”, заключенный между ними и интерфейсом, на реализацию определенных внутри виртуальных функций.

То есть интерфейс работает как ссылка на класс с реализованными методами?

Ссылка на объект класса, суженного до типа интерфейса.


public inteface C {
    public void someMethod1();
}

public class A implements C {
    public void someMethod1() {}
    public void someMethod3() {}
}

public class B extends A {
    public void someMethod1() {}
    public void someMethod2() {}
}

public static void main(String[] args) {
    A ob1 = new A(); // access to someMethod1/3
    A ob2 = new B(); // access to someMethod1/3
    B ob3 = new B(); // access to someMethod1/2/3
    C ob4 = new A(); // access to someMethod1
    C ob5 = new B(); // access to someMethod1
}

Спасибо большое я понял. В соседней теме я обсуждал матрешечный стиль (methods.a().b().c()). Интерфейсы еще можно использовать для того чтобы сделать видимость методов между собой например:


public interface Actions{
     public void print()
     public void show()
}

public class Page implements Actions{

public Actions open(){
return this;
}

@Override
 public void print(){
}
@Override
public void show(){
}
}

public static void main(String[] args){
Page page = new Page();
page.open().show() //после open(). нам не будет виден метод еще раз open(). Только print() и show()
}

Верно? Так вообще правильно делать?


public interface Actions {
    public Actions print();
    public Actions show();
}

public class Page implements Actions {
    public Actions print() { return this; }
    public Actions show() { return this; }
}

public static void main(String[] args) {
    Actions actions = new Page();
    actions.show().print();
}

Хотя, вообще говоря не совсем понимаю смысла вашей затеи. PageObject паттерн как раз исповедует стиль, при котором вам будет видна функциональность только той страницы, в пределах которой вы сейчас находитесь, перемещаясь по цепочке вызовов. В случае наследования конечно вам будет также доступен и функционал родителей.

П.С. Ну или посмотрите еще в сторону Builder паттерна.

И да и нет :slight_smile:

Верно, метод open() в цепочке еще раз вызвать не получится, но смысла делать интерфейс для Page-класса в общем случае нет. Поскольку страницы в общем случае достаточно уникальны, а интерфейс - это все-таки некоторая универсализация, то есть подразумевает возможность одинаково работать с разными объектами в рамках реализуемого ими интерфейса.

Ну и использовать интерфейс только для того, чтоб в цепочке можно было вызвать open() только в начале, а дальше нельзя - это как использовать отвертку чтобы забить гвоздь. Оно может работать в ряде случаев, но придумано не для этого

А можете привести кусок кода из реальной практики, можно просто ту часть где вы ссылаетесь на интерфейс?

WebDriver, к слову, - это интерфейс. А тот же RemoteWebDriver - его реализует. :wink:

1 Симпатия