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

Открыта

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

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

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

Код на 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();
   }
  }
 }
}
— как дать им команду – построить стену?

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

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

Комментарии (79)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий вы должны авторизоваться
S20 уровень, Минск
4 марта, 13:44
Какая-то пошла ерунда в лекциях. Интерфейс - это реализация, поэтому каждый метод кладем в отдельный интерфейс?! В результате класс будет имплементировать 10 интерфейсов? Не вижу никакого преимущества\смысла в интерфейсе, кроме как обязать программиста реализовать необходимый метод(ы). Почему просто не создавать необходимые методы где это нужно? - Ну, допустим, программисты тупой и забывчивый. Ну так если программист тупой и забывчивый - он создаст данный метод, но не напишет внутри нужную\правильную реализацию. Тогда можно еще всунуть в язык программирования какой-нибудь глупый функционал - например, чтобы перед компиляцией выскакивало сообщение - "Вася, а ты точно ничего не забыл и все правильно сделал?!" В результате нагородили абстрактных классов, которые имеют единственное наследование и абстрактные и неабстрактные методы, потом придумали интерфейсы с множественным наследованием, но только с асбтрактными методами. Потом придумали, что в можно и неабстрактные методы в интерфейсах. Может сразу сделали бы множественное наследование и не дурили бы мозги?
Илья17 уровень, Санкт-Петербург
4 марта, 16:23
Интерфейс это как раз не реализация) Я сам уже на практике убедился, что интерфейсы очень удобная штука. В чем преимущество не возьмусь описать своими словами, но могу порекомендовать прочесть у Шилдта на эту тему, очень хорошо поясняется. Г. Шилдт - Java Руководство для начинающих. Глава 8.
Serhii24 уровень
4 марта, 18:30
Интерфейс это не реализация. Это контракт взаимодействия между классами. То есть, если класс реализует какой-то интерфейс он обязуется реализовать и все методы этого инерфейса. Таким образом програмист работая на уровне интерфейса, может вообще не догадываться какая конкретно реализация интерфейса в данный момент используется. Интерфейсы как раз и раскрывают всю силу полиморфизма. Допустим мы написали класс зоопарка, который получает на вход обьект собаки и заставляет ее ходить.
class Dog {
    public String walking() {
        return "Я хожу как собака";
    }
}
class Zoo {
    public void startDog(Dog dog) {
        dog.walking();
    }
}
Теперь пришел заказчик и хочет добавить еще котов. Окей, мы помним что нам надо реализовать метод walking для кота
class Cat {
    public String walking() {
        return "Я хожу как котейка";
    }
}
class Zoo {
    public void startDog(Dog dog) {
        dog.walking();
   }
   public void startCat(Cat cat) {
        cat.walking();
   }
 }
Нам еще пришлось добавлять новый метод в наш старый класс Zoo. И вот тут как раз кроется проблема - нам ПОСТОЯННО прийдется добавлять новые методы в существующий класс, когда нужно будет добавить животного. И так же придется вносить изменения в класс, использующий Zoo, чтобы он вызывал соответствующий метод. Интерфейс решает это проблему, если у нас будет Dog implements Walkable и Cat implements Walkable, то классу Zoo вообще не надо ничего знать про то собака это или кот или птица или вообще человек. То есть обьекты могут вообще находиться в разных иерархиях классов Dog extends Animal и Worker extends Human, но если они оба реализуют Walkable, то класс Zoo может застивить их ходить всего лишь с помощью одного метода, который не придется менять: class Zoo { public void startWalking(Walkable anyWalkableThing) { anyWalkableThing.walk(); } }
Roman Afonin24 уровень, Санкт-Петербург
11 марта, 21:09
аж вслух протянул "а... вона как". Спасибо за объяснение!
Viktor 17 уровень
22 марта, 00:39
виден свет в конце скобочек))) спасибо!
АртемGeek13 уровень, Москва
вторник, 15:16
Спасибо за объяснение!
Eugene Burdeinyi26 уровень, Одесса
25 февраля, 14:49
BUILD THE WALL & CRIME WILL FALL!
Grimax16 уровень, Санкт-Петербург
26 января, 07:48
Интерфейс это получается тип и тогда объект получается может быть одновременно типа "тип - Класс" (плюс все типы классов родителей) и типов "тип - Интерфейс" которые имплементирует его класс (и все классы родители) у меня крышу сносит полиморфизм полный)
Илья26 уровень
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, поэтому здесь нужно явное приведение - тут у нас сужение.
Руслан20 уровень, Москва
29 января, 18:24
Ничего не теряется, тут как раз и задействована мощь полиморфизма. У нас есть лист роботов, которые являют собой разные реализации WallBuilder-а. Нужный метод buildWall() вызывается исходя из того, объект какого конкретного класса вызывается из листа.
Dronya_3313 уровень, Москва
2 марта, 22:15
А где написано что лист именно роботов? Если туда помещаются роботы, это еще не значит что там роботы. У ArrayList нет дженериков(<тип хранимых объектов в коллекции>), которые бы указали, объекты какого типа хранить в коллекции robots.
ArrayList robots = new ArrayList(); // тоже самое что и
ArrayList<Object> robots = new ArrayList<Object>()
А чтобы там хранились роботы, надо было бы в дженериках указать их родительский класс(хотя опять же нужно будет обратное приведение в более конкретный класс, ведь потомок расширяет класс, как правило), или интерфейс, который они все реализуют, в нашем случае, как мне кажется.(тут я не совсем еще разобрался)
Dronya_3313 уровень, Москва
2 марта, 22:15
Так что, если быть точнее, то там, да, роботы, но ссылки на них там присваиваются объекту типа Object. И возвращаются от туда, тоже, являясь типом Object. Это так устроено по умолчанию. То какие методы можно использовать, решает как раз тип ссылки(Object), а не тип объекта(РабочийРобот). Тип объекта(РабочийРобот) решает только какая реализация методов, общих с его предком(объект Object), будет работать. Java так устроен, что любой класс явно или неявно является наследником класса Object, если на прямую не наследует другой класс. Хотя, по сути, тот другой класс наследуется от того-же Object. А так как в классе Object прописаны самые необходимые методы для работы объектов, и он (Object) не наследует ни каких интерфейсов, то у объекта РабочийРобот, который вернулся типом Object, будут работать только методы, которые хранил в себе его предок - класс Object, ведь он все еще его типа. Потому приведение нужно, чтоб вернуть объекту РабочийРобот его поведение(функционал). А как мы знаем класс РоботРабочий, когда-то наследовал интерфейс, то проверку на соответствие IS-A(является), а является ли объект РоботРабочий потомком интерфейса WallBuilder:
robot instanceof WallBuilder
он пройдет. И будет свою работу работать.
UnNameD16 уровень, Москва
5 декабря 2018, 15:55
Пропустил наверно, что делает строка
WallBuilder builder = (WallBuilder) robot;
?
Иван19 уровень, Москва
17 декабря 2018, 11:37
Приведение типа
Andrii Gorshunov38 уровень
4 января, 22:52
Даункастинг. Вниз (сужение, даункастинг) типа надо приводить руками. Вверх расширяется автоматически. int x = 10; byte y = (byte) x; Аналогично и с иерархией наследования.
Andrii Gorshunov38 уровень
15 ноября 2018, 22:16
Интерфейс это как книга - говорит что должно быть сделано у тебя в Классе. При этом интерфейс имеет только абстрактные методы без тела {}. Внедряя их у себя в классе, ты обязываешься их применить и написать реализацию.
TheDIP23 уровень, Киев
4 января, 21:56
Как обязательное содержание глав книги, которые ты сам должен написать. :)
Ролан Запара16 уровень
1 октября 2018, 21:04
В последнем куске кода 3 скобки открыли и 4 закрыли. Или я туплю?
Асад18 уровень, Самара
21 октября 2018, 19:17
код просто не полностью, сверху еще основной класс
Иван Сапронов27 уровень
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
Спасибо, Роман! Очень доходчиво объяснили.
Boris27 уровень
1 августа 2018, 17:45
в примере выше поливалка не имплиминтирует интерфейс WallBuilder, получается её в список добавили но, строить она не будет, поскольку она не проходит проверку на
if (robot instanceof WallBuilder)
поправьте если я не прав. остальные пойдут стройть.