Интерфейсы — это больше чем интерфейсы — это поведение

Открыта

— Привет, Амиго! А вот и снова я. Хочу рассказать тебе еще об одном взгляде на интерфейсы. Понимаешь, класс – это, чаще всего модель какого-то конкретного объекта. Интерфейс же больше соответствует не объектам, а их способностям или ролям.

Например, такие вещи, как машина, велосипед, мотоцикл и колесо лучше всего представить в виде классов и объектов. А такие их способности как «могу ездить», «могу перевозить людей», «могу стоять» — лучше представить в виде интерфейсов. Смотри пример:

Код на Java Описание
interface Moveable
{
void move(String newAddress);
}
— соответствует способности передвигаться.
interface Driveable
{
void drive(Driver driver);
}
— соответствует способности управляться водителем.
interface Transport
{
void addStaff(Object staff);
Object removeStaff();
}
— соответствует способности перевозить грузы.
class Wheel implements Moveable
{
...
}
— класс «колесо». Обладает способностью передвигаться.
class Car implements Moveable, Drivable, Transport
{
...
}
— класс «машина». Обладает способностью передвигаться, управляться человеком и перевозить грузы.
class Skateboard implements Moveable, Driveable
{
...
}
— класс «скейтборд». Обладает способностью передвигаться и управляться человеком.
14
Задача
Java Core,  2 уровень,  8 лекция
Недоступна
Набираем код
Иногда думать не надо, строчить надо! Как ни парадоксально звучит, порой пальцы «запоминают» лучше, чем сознание. Вот почему во время обучения в секретном центре JavaRush вы иногда встречаете задания на набор кода. Набирая код, вы привыкаете к синтаксису и зарабатываете немного материи. А ещё — боретесь с ленью.

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

Весь смысл в том, что тебе не нужно писать код для взаимодействия со всеми классами. Тебе достаточно взаимодействовать с их ролями (интерфейсами).

Представь, что ты – робот-строитель и у тебя в подчинении есть десятки роботов, каждый из которых может иметь несколько профессий. Тебе нужно срочно достроить стену. Ты просто берешь всех роботов, у которых есть способность «строитель» и говоришь им строить стену. Тебе все равно, что это за роботы. Хоть робот-поливалка. Если он умеет строить – пусть идет строить.

Вот как это выглядело бы в коде:

Код на Java Описание
static interface WallBuilder
{
void buildWall();
}
— способность «строитель стен». Понимает команду «(по)строить стену» — имеет соответствующий метод.
static class РабочийРобот implements WallBuilder
{
void buildWall()
 {}
}
static class РоботСторож implements WallBuilder
{
void buildWall()
 {}
}
static class Поливалка
{}
— роботы у которых есть эта профессия/особенность.

— для удобства я сделал классам имена на русском. Такое допускается в java, но крайне нежелательно.

— поливалка не обладает способностью строить стены (не реализует интерфейс WallBuilder).

public static void main(String[] args)
{
 //добавляем всех роботов в список
 ArrayList robots = new ArrayList();
 robots.add(new РабочийРобот());
 robots.add(new РоботСторож());
 robots.add(new Поливалка());

 //строить стену, если есть такая способность
 for (Object robot: robots)
 {
  if (robot instanceof WallBuilder)
  {
   WallBuilder builder = (WallBuilder) robot;
   builder.buildWall();
   }
  }
 }
}
— как дать им команду – построить стену?

— Чертовски интересно. Даже и не думал, что интерфейсы – такая интересная тема.

— А то! В совокупности с полиморфизмом – это вообще бомба.

Комментарии (70)
  • популярные
  • новые
  • старые
Для того, что бы оставить комментарий вы должны авторизоваться
Grimax16 уровень, Санкт-Петербург
26 января, 07:48
Интерфейс это получается тип и тогда объект получается может быть одновременно типа "тип - Класс" (плюс все типы классов родителей) и типов "тип - Интерфейс" которые имплементирует его класс (и все классы родители) у меня крышу сносит полиморфизм полный)
Илья19 уровень
11 января, 23:40
Зачем здесь явное приведение типа:
WallBuilder builder = (WallBuilder) robot;
robot же является наследником интерфейса (реализует интерфейс) WallBuilder. То есть здесь не сужение идет, а автоматическое расширение. Да и поскольку интерфейс по сути абстрактный класс, то объект его типа невозможно создать (и, соответственно, к нему привести)? Иначе метод bildWall() должен выполняться тот, который по идее описан в классе-родителе (так как тип объекта теперь приведен к нему). А этой реализации не существует и не может существовать (все методы абстрактные) И полиморфизм теряется напрочь. У нас в каждом классе есть своя реализация метода bildWall(), и это логично. Один робот строит стену быстро и хорошо, другой медленно и средненько и так далее. Но для полиморфизма как раз приведения типов не должно быть, должно же быть так:
for (Object robot: robots)
 {
  if (robot instanceof WallBuilder)
  {
   robot.buildWall();
   }
  }
Или я просто капец где-то нить потерял?
AlexEremenko18 уровень
12 января, 09:05
Дело в том, что robot является объектом типа Object, поэтому здесь нужно явное приведение - тут у нас сужение.
Руслан17 уровень, Москва
29 января, 18:24
Ничего не теряется, тут как раз и задействована мощь полиморфизма. У нас есть лист роботов, которые являют собой разные реализации WallBuilder-а. Нужный метод buildWall() вызывается исходя из того, объект какого конкретного класса вызывается из листа.
UnNameD16 уровень, Москва
5 декабря 2018, 15:55
Пропустил наверно, что делает строка
WallBuilder builder = (WallBuilder) robot;
?
Иван16 уровень, Москва
17 декабря 2018, 11:37
Приведение типа
Andrii Gorshunov30 уровень
4 января, 22:52
Даункастинг. Вниз (сужение, даункастинг) типа надо приводить руками. Вверх расширяется автоматически. int x = 10; byte y = (byte) x; Аналогично и с иерархией наследования.
Andrii Gorshunov30 уровень
15 ноября 2018, 22:16
Интерфейс это как книга - говорит что должно быть сделано у тебя в Классе. При этом интерфейс имеет только абстрактные методы без тела {}. Внедряя их у себя в классе, ты обязываешься их применить и написать реализацию.
TheDIP17 уровень, Киев
4 января, 21:56
Как обязательное содержание глав книги, которые ты сам должен написать. :)
Ролан Запара16 уровень
1 октября 2018, 21:04
В последнем куске кода 3 скобки открыли и 4 закрыли. Или я туплю?
Асад18 уровень, Самара
21 октября 2018, 19:17
код просто не полностью, сверху еще основной класс
Иван Сапронов22 уровень
15 августа 2018, 14:21
А чем отличается статический интерфейс от нестатического? Или они все статические? Просто тут в примерах у интерфейсов public static везде прописано...
Максим27 уровень
2 августа 2018, 22:17
В 4-й строке ArrayList robots = new ArrayList(); какой тут дженерик тип будет? Вроде его надо указать, по-хорошему...
Vladimir21 уровень, Москва
3 августа 2018, 16:37
Судя по тому, что эти роботы не являются наследниками некоего базового класса и очень хочется использовать дженерик, то по всей видимости Object.
Максим27 уровень
4 августа 2018, 09:34
Разобрался, вроде... Можно так написать: ArrayList<T> robots = new ArrayList(); Всякие интересные дженерики: <E> <K> <N> <T> <V> <?>
5 августа 2018, 19:02
Вот последнее <?> – это дженерик (параметр) для ArrayList, а точнее wildcards, который может принимать форму <? extends название_родительского_класса> или <? super название_дочернего_класса>, а всё остальное, что вы написали – это просто общепринятые обозначения для коллекций
5 августа 2018, 19:14
"дженерик, то по всей видимости Object" – вы так предлагаете написать:
List<Object> robots = new ArrayList<>();
? Смысле дженериков как раз в том, чтобы указывать на конкретный класс, самый низкий в иерархии наследования с максимальным количеством методов, а не на родителя всех объектов. А у вас что-то из рубрики вредные советы. Конкретно в этом примере дженерик бессмысленно использовать без определения родительского абстрактного класса для рабочих роботов. Но и это не нужно, потому что ключевое слово "instanceof" проверяет наличие реализации интерфейса у каждого объекта.
5 августа 2018, 20:02
Дженерик по интерфейсу можно указать тогда, когда нам надо оградить нашу коллекцию от экземпляров класса не реализующих этот интерфейс. Но в этом примере нам это не надо, потому что в динамическом массиве роботов нам надо хранить всех роботов, а если бы у нас был дженерик <WallBuilder>, то добавить туда экземпляр класса Поливалка мы бы не смогли, потому что это ошибка компиляции. Вот пример кода с дженериком по реализуемым интерфейсам:
import java.util.ArrayList;
import java.util.List;

public class My {
    public static void main(String[] args) {
        List<Test> list = new ArrayList<>();
        list.add(new HasTest());
        list.add(new NoTest()); //тут ошибка компиляции "add(My.Test) in List cannot be applied to (My.NoTest)"
    }

    interface Test {
        String testing(int a);
    }

    public static class HasTest implements Test {
        @Override
        public String testing(int a) {
            return "testing" + a;
        }
    }

    public static class NoTest {
        public void testing() {
            System.out.println("testing");
        }
    }
}
Экземпляр класса NoTest можно будет добавить в массив, только если он будет реализовать (implements) интерфейс Test, а до этого этот код не скомпилируется!
Максим27 уровень
6 августа 2018, 15:53
Спасибо, Роман! Очень доходчиво объяснили.
Boris19 уровень
1 августа 2018, 17:45
в примере выше поливалка не имплиминтирует интерфейс WallBuilder, получается её в список добавили но, строить она не будет, поскольку она не проходит проверку на
if (robot instanceof WallBuilder)
поправьте если я не прав. остальные пойдут стройть.
Sergey15 уровень, Санкт-Петербург
24 июля 2018, 14:54
Долго не понимал суть интерфейсов. Ведь примеры очень простые и неочевидно зачем нужно создавать интерфейс, вместо того, чтобы сразу в классе прописать эти 1 или 2 метода. Задал вопрос другу-кодеру, вот что он рассказал. Возможно кому-то поможет разобраться. "Интерфейс если на пальцах. У тебя должно быть много разных классов. Грузовик мопед седан спорткар итд. они все разные. Но совершенно точно, что все они должны мочь ездить, поворачивать и тормозить. И чтобы тебе не забыть прописать каждое из этих свойств в каждом классе, ты создаешь интерфейс. В котором 3 функции - ехать, повернуть, тормозить. И подключаешь его к своим классам с машинами. Теперь, когда он подключен, ты не можешь не расписать эти три функции внутри этих машин, тк тебе ошибка вылетит. Это нужно, чтобы когда ты в начале создаешь архитектуру классов, добавил везде интерфейсов, и потом, когда будешь писать наполнение классам, то не забудешь заимплеменитть важные функции. Это самое простое из объяснений зачем."
Andry Max35 уровень, Минск
2 августа 2018, 22:35
вроде помогает понять немного. но как то глупо получается, что всё сводится к тому что ты просто можешь забыть написать какой то метод)
28 августа 2018, 14:33
забыть или наоборот добавить ненужное
verdy25 уровень
7 сентября 2018, 20:25
https://dou.ua/lenta/articles/composition-vs-inheritance-in-java/ Не за что (=
Артем23 уровень
6 декабря 2018, 18:26
Вот уж действительно не за что
JackHuman Entertainment20 уровень
3 июля 2018, 12:46
WallBuilder builder = (WallBuilder) robot. Зачем приведение типа, если это расширение?
JackHuman Entertainment20 уровень
19 июля 2018, 15:54
Понял, робот может быть любым объектом...
Rolik1 уровень, Харьков
19 июля 2018, 16:50
Извиняюсь, но я дал неправильный ответ.. Коллекция параметризована по умолчанию <Object> и сам итератор, тоже типа Object. И поэтому приведение его к типу данных интерфейса WallBuilder и передача ссылки на него переменной интерфейса builder, это сужение, а не расширение. Дальше, переменная builder вызывает метод интерфейса buildWall(), как я понимаю, при проходе каждого элемента листа-коллекции.
JackHuman Entertainment20 уровень
20 июля 2018, 12:32
Все понятно, это ибо это массив объектов, не обязательно роботов...