Есть отличная удаленная работа для php+codeception+jenkins+allure+docker спецов. 100% remote! Присоединиться к проекту

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


(Alex) #1

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


(Александр Таранков) #2

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

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

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


(Sergey Korol) #3

Тут нужно отталкиваться от того, что в 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 не будут ничего знать о том, что еще имеется в моем классе, кроме переопределенных методов. Что в целом и является одной из основных идей, когда мы можем не задумываться о реализации: мой класс будет иметь одну, чей-то еще - другую. Но при этом оба будут соблюдать “договор”, заключенный между ними и интерфейсом, на реализацию определенных внутри виртуальных функций.


(Alex) #4

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


(Sergey Korol) #5

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


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
}

(Alex) #6

Спасибо большое я понял. В соседней теме я обсуждал матрешечный стиль (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()
}

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


(Sergey Korol) #7

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 паттерна.


(Александр Таранков) #8

И да и нет :slight_smile:

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

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


(Alex) #9

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


(Sergey Korol) #10

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