Зачем нужны интерфейсы в ООП?
На русском Stackoverflow спросили зачем в Java нужны интерфейсы? Народ дал много интересных ответов, но все они так или иначе говорили о том ЧТО есть интерфейс и КАК устроена его реализация. О практической пользе никто не сказал. Т.е. ответ на вопрос для чего используются интерфейсы дан не был. Видимо потому, что для большинства это и так ЯСНО. Но, думаю, есть те, которые понимаю ЧТО и КАК, но не знают ДЛЯ ЧЕГО.
Я дал такой ответ:
Допустим, мы создали Framework, который определяет, совершил ли пользователь double-click по экрану.
Там мы определили функцию: что_делать_если_пользователь_кликнул_2_раза_по_экрану().
Но мы решаем не определять поведение этой функции сами, а предоставить эту реализацию программисту, использующему наш фреймворк. Предоставление этой реализации можно сделать через механизм интерфейсов.
Функцию: что_делать_если_пользователь_кликнул_2_раза_по_экрану() засовываем в интерфейс.
И, таким образом, сам момент двойного клика по экрану определяет наш фреймворк, а вот что делать (рисовать звездочки на экране, запустить проигрывание музыки и т.д.) после этого события решает программист, который реализует наш интерфейс.
Польза: программисту не надо думать, как словить double-click. За ним только реализация ответа на это событие.
Конечно, это только одна сторона пользы от интерфейсов. Зато очень наглядная на мой взгляд. Другая, очевидная поьза от интерфейсов — это придание гибкости коду в проекте (особенно в больших). См. e.g. принципы SOLID: academy.realm.io/posts/donn-felker-solid-part-5/
Для себя так понял практическую пользу интерфейсов. Как-то разбирал одну библиотеку, которая реализовывала Swipe&Dismiss карточек в RecyclerView. Эта библиотека реализовывала свой колбэк тоучхелпера:
public class ItemTouchHelperCallBack extends ItemTouchHelper.Callback . >
И соответственно в адаптере RecyclerView, нужно было реализовать функцию, которая вызывалась после Swipe&Dismiss в ItemTouchHelperCallBack. Соответственно передать реализацию этой функции в адаптер можно было только через механизм интерфейсов.
Еще раз наш колбэк тоучхелпера уже с интерфейсом и функцией, которая использует реализацию интерфейса:
public class ItemTouchHelperCallBack extends ItemTouchHelper.Callback . private onSwipListener mOnSwipListener; //экземпляр реализации интерфейса, //через который мы получим //реализованную Swipe&Dismiss . //получаем реализацию интерфейса //эта функция связывает нашу реализацию с //функциональностью библиотеки public void setOnSwipListener(onSwipListener onSwipListener) this.mOnSwipListener = onSwipListener; > . //функция, которая использует реализацию (имплементацию) интерфейса public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) int position = viewHolder.getAdapterPosition(); if (mOnSwipListener != null) mOnSwipListener.onSwip(viewHolder,position); > > > . //сам интерфейс public interface onSwipListener void onSwip(RecyclerView.ViewHolder viewHolder, int position); > . >
Простыми словами, функция которая реализует Swipe как бы говорит: “Чтобы я работала, мне нужен объект, который реализует интерфейс OnSwipListener. И не важно какой именно, я не должна знать это заранее. Просто объект, который реализует интерфейс”. Таким образом, мы избавляемся от зависимости между различными модулями программы.
Вот схема адаптера:
public class RecyclerViewCardStackAdapter extends RecyclerView.AdapterRecyclerViewCardStackAdapter.CardStackViewHolder> implements ItemTouchHelperCallBack.onSwipListener . //реализация интерфейса @Override public void onSwip(RecyclerView.ViewHolder viewHolder, int position) remove(position); > . >
Таким образом, мы видим чем может быть полезен интерфейс. Скажем так, отложеной реализацией, когда мы все подготавливаем для какого-то действия, а потом, пользуясь этой подготовкой, реализуем только само действие.
Кстати, вот как реализация интерфейса связывается с расширяющим классом (в классе, где мы готовили наш RecyclerView и привязывали к нему адаптер):
. if (adapter instanceof ItemTouchHelperCallBack.onSwipListener) ItemTouchHelperCallBack.onSwipListener swipListener = (ItemTouchHelperCallBack.onSwipListener) adapter; ItemTouchHelperCallBack itemTouchHelperCallBack = new ItemTouchHelperCallBack(); itemTouchHelperCallBack.setOnSwipListener(swipListener); ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallBack); itemTouchHelper.attachToRecyclerView(recyclerView); > .
С помощью instanseof мы проверяем, есть ли реализация нашего интерфейса в адаптере. Получаем эту реализацию — swipListener. И привязываем ее — setOnSwipListener(swipListener).
09 09 2017
- Java
- Android Quizzes 2021
- Реактивное программирование с применением RxJava
- Android изнутри. Часть I
- Большие запросы к базе данных на Android
- ProgressDialog умер, да здравствует ProgressBar!
- Введение в Android Architecture Components
- Firebase Tutorial. Part II. — Как автоматически развернуть сайт на хостинге Firebase из GitHub-репозитория?
- Firebase Tutorial — Firebase database. Part I.
- У меня было 10 Android собеседований за последние два года. (перевод)
- Android Tutorial — Facebook SDK Login. Part III. Get Posts.
- Android Tutorial — Facebook SDK Login. Part II. Share content.
- Android Tutorial — Facebook SDK Login. Part I.
- Оптимизация прокрутки вложенных в друг друга RecyclerView
- RecyclerView и ListView. В чем разница?
- Различные типы Item View в RecyclerView
- Тесты сделают вас счастливее (перевод).
- Пример анимации в Android с использованием TimeAnimator.
- Android. Два способа записать результат Timer в пользовательский поток (UI Thread).
- Видео. Architecture Components. Solving the Lifecycle Problem. Перевод субтитров.
- Зачем нужны интерфейсы в ООП?
- Видео. Architecture Components. Improve Your App’s Design. Перевод субтитров.
- Видео. Google I/O ’17 Architecture Components — Introduction. Перевод субтитров.
- Препарирование ContentProvider
- REST. Описание концепции. Особенности реализации API. Максимально кратко.
- Лоадеры (loaders) в Android. Для чего нужны. Конспект.
- Volley vs Retrofit. Описание библиотек REST API.
- Краткий конспект реализации Garbage Collector в Java
- Препарирование RecyclerView
- Лексикон прописных истин Android
- Android Quizzes 2021
- Review Currency App task.
- Foreground Service Android demo App — stopwatch
- Simple Custom View demo App — circular progress bar
- Simple RecyclerView demo App — Stopwatch
- RS School Android 2021
- Реактивное программирование с применением RxJava
- Android изнутри. Часть I
- Большие запросы к базе данных на Android
- ProgressDialog умер, да здравствует ProgressBar!
- Введение в Android Architecture Components
- Firebase Tutorial. Part II. — Как автоматически развернуть сайт на хостинге Firebase из GitHub-репозитория?
- Firebase Tutorial — Firebase database. Part I.
- У меня было 10 Android собеседований за последние два года. (перевод)
- Android Tutorial — Facebook SDK Login. Part III. Get Posts.
- Android Tutorial — Facebook SDK Login. Part II. Share content.
- Android Tutorial — Facebook SDK Login. Part I.
- Оптимизация прокрутки вложенных в друг друга RecyclerView
- RecyclerView и ListView. В чем разница?
- Различные типы Item View в RecyclerView
- Тесты сделают вас счастливее (перевод).
- Пример анимации в Android с использованием TimeAnimator.
- Android. Два способа записать результат Timer в пользовательский поток (UI Thread).
- Видео. Architecture Components. Solving the Lifecycle Problem. Перевод субтитров.
- Зачем нужны интерфейсы в ООП?
- Видео. Architecture Components. Improve Your App’s Design. Перевод субтитров.
- Видео. Google I/O ’17 Architecture Components — Introduction. Перевод субтитров.
- Препарирование ContentProvider
- REST. Описание концепции. Особенности реализации API. Максимально кратко.
- Лоадеры (loaders) в Android. Для чего нужны. Конспект.
- Volley vs Retrofit. Описание библиотек REST API.
- Несколько примеров на Kotlin
- Препарирование RecyclerView
- Лексикон прописных истин Android
- Xamarin.Forms. Layouts — виды
- Xamarin.Forms. Оптимизация ListView.
- Xamarin.Forms. Ускорение отображения окна (Activity)
- Платформозависимость на Xamarin
- Тесты сделают вас счастливее (перевод).
- Видео. Architecture Components. Solving the Lifecycle Problem. Перевод субтитров.
- Зачем нужны интерфейсы в ООП?
- Видео. Architecture Components. Improve Your App’s Design. Перевод субтитров.
- Видео. Google I/O ’17 Architecture Components — Introduction. Перевод субтитров.
- Краткий конспект реализации Garbage Collector в Java
- Лекция о языках программирования
- Зачем нужны интерфейсы в ООП?
Pro Java
Когда я в первой части говорил что интерфейсы, для начального понимания, можно сперва представлять как полностью абстрактные классы, я говорил это именно для начального понимания. Значение и применение интерфейсов выходит далеко за пределы этого начального понимания.
Лучше представлять интерфейсы как некие контракты, договора, которые определяют правила (интерфейс) взаимодействия между компонентами программы и при этом не указывают каким образом этот договор может быть реализован, главное чтобы он вписывался в рамки определения методов в интерфейсе. Реализаций же одного договора может быть сколько угодно много. Реализовывают договора описанные в интерфейсах классы, и они должны строго соблюдать договор описанный в интерфейсе, то есть реализовать все методы имплементируемых интерфейсов.
По идее интерфейсы, как и следует из их названия, предоставляют API тем классам которые их имплементируют.
Интерфейсы это еще больший уровень абстракции чем абстрактные классы 🙂
В действительности, сперва возникает много вопросов по поводу интерфейсов – зачем они вообще нужны, если там нет реализации методов а только их определения? Но как ни странно интерфейсы – это мощнейшая вещь для реализации инкапсуляции, полиморфизма и наследования, которая сильно расширяет возможности классов.
Ну ладно, от слов к делу. Попрактикуемся на хорошем примере видео, которое я уже предлагал для вашего просмотра.
Интерфейсы в Java
Давайте реализуем этот урок на практике! Правда, я конечно немного изменю код представленный в этом уроке, но в основном все останется так же как там. Полностью рабочий код можно посмотреть тут.
Во второй мутации данного проекта я добавил интерфейс IReplacer и его имплементацию в классе DefaultReplacer.
Код интерфеса IReplacer:
package pro.java.smile2;
public interface IReplacer String replace ( IReader reader , String from , String to ) ;
>Код класса DefaultReplacer:
public class DefaultReplacer implements IReplacer @Override
public String replace ( IReader reader , String from , String to ) return reader . read () . replace ( from , to ) ;
>
>Как видно метод объявленный в IRepalcer использует в качестве входного аргумента объект типа IReader, через который получает строку для замены, а так же подстроки для замены – с какой (from) на какую (to).
Надеюсь на этих примерах стало чуть более понятно как и для чего могут использоваться интерфейсы, а так же вся мощь и гибкость которую они предоставляют.

Хотя можно еще один пример привести – это реализация целочисленного стека. Идея взята из книги Герберта Шилдта, но в ней, этот пример, показался мне несколько странным, так как он не отражает идеи использования интерфейсов, поскольку там создается объект самого стека, а не интерфейсная ссылка на него. То есть в его примере можно легко было бы обойтись простыми классами.
Кроме того я добавил в этот пример метод печати стека, дабы было видно что происходит.
Функциональные интерфейсы в Java: Supplier, Consumer, Predicate и Function. Для чего они нужны и как их применять на практике? — Блог

Работаю Java/Kotlin разработчиком в компании Tune-it.
Люблю тёмное Guinness и chocolate trinidad moruga scorpion.
- api (3)
- async (8)
- feature (4)
- framework (5)
- gradle (4)
- java (11)
- json (3)
- kotlin (10)
- nosql (3)
- postgresql (2)
- reactive (8)
- software testing (2)
- spring (8)
- spring boot (7)
- toolkit (3)
- vert.x (3)
- webflux (4)
- асинхронность (8)
- реактивное приложение (8)
- тестирование по (2)
Интерфейсы interface
В статье рассматривается одно из свойств Java, как языка объектно-ориентированного программирования, вопрос использования интерфейса. Статья в большей степени ориентирована на философию Java, чем на практические рекомендации по программированию.
Первый вопрос, возникающий при знакомстве с Java, — Зачем нужны интерфейсы? Нельзя ли обойтись абстрактными классами? Может быть для маскировки отсутствия множественного наследования?
Привычные варианты ответа: класс вводит новый тип, класс обобщает и т.д.
Декларация типа
В Java новый тип можно определить спецификацией интерфейса. Любой java интерфейс (interface) может иметь много реализаций. Любой класс может реализовывать несколько интерфейсов.
В качестве примера создадим интерфейс INumber — тип, описывающий функции использования числовых значений. Ограничимся только операциями сложения и умножения.
/** ————————————————————- * INumber.java Декларация типа INumber * ————————————————————- */ interface INumber
Реализация интерфейса, implements
Пример реализации интерфейса INumber при описании класса целочисленных значений :
class IntNumber implements INumber < int i; public IntNumber() <>public IntNumber(int v) < this.i = v; >public IntNumber(String v) < this.i = toInt(v); >public Integer toInt (String value) < int l = value.indexOf('.'); if (l >0) value = value.substring(0, l); return (new Integer(value)).intValue(); > @Override public void setValue(String s) < i = toInt (s); >@Override public INumber add(INumber n) < i += toInt (n.toString()); return this; >@Override public INumber mul(INumber n) < i *= toInt (n.toString()); return this; >@Override public String toString() < return Integer.toString(i); >>В классе IntNumber реализованы (переопределены) методы интерфейса с использованием аннотации Override.
Пример использования интерфейса INumber :
public class TestINumber < public void calculation(INumber n1, INumber n2, INumber n3) < INumber i2; i2 = n2; /* Если закомментировать предыдущую строку, то компилятор выдаст ошибку: * variable in2 might not have been initialized */ System.out.println("i2 = " + xx.toString()); System.out.println("n1 = " + n1.toString()); System.out.println("n2 = " + n2.toString()); System.out.println("n3 = " + n3.toString()); System.out.println("(n1 + n2) * n3 = " + n1.add(n2).mul(n3).toString()); n1.setValue("21"); System.out.println("(n2 + n1) * n3 = " + n2.add(n1).mul(n3).toString()); n2.setValue("37.6"); System.out.println("n1 * (n2 + n3) = " + n1.mul(n2.add(n3)).toString()); n1.setValue("21"); n2.setValue("37.6"); System.out.println("n3 * (n1 + n2) = " + n3.mul(n1.add(n2)).toString()); >public static void main(String args[]) < TestINumber tin = new TestINumber(); INumber in1 = new IntNumber("21"), in2 = new IntNumber("37.6"), in3 = new IntNumber(1); tin.calculation(in1, in2, in3); >>В результате выполнения примера «TestINumber» в консоль будет выведен следующий текст :
i2 = 37 n1 = 21 n2 = 37 n3 = 1 (n1 + n2) * n3 = 58 (n2 + n1) * n3 = 58 n1 * (n2 + n3) = 798 n3 * (n1 + n2) = 58
Из приведенного примера видно :
- Интерфейс позволяет объявить тип. В приведенном примере объявляются переменные и параметры типа INumber, описываются действия над ними. Компиляция выполняется без ошибок.
- Реализация типа передается через объект. Объекты n1, n2 и n3 передаются в метод calculation через параметры. Таким образом компилятор информируется, что объекты проинициализированы где-то за пределами данного модуля. Этого достаточно и классы пока не нужны.
Добавим вариант реализации интерфейса при создании класса вещественного числа :
/** ------------------------------------------------------------- * DblNumber.java Реализация типа INumber через double. * ------------------------------------------------------------- */ class DblNumber implements INumber < double d; public DblNumber(double ip) < d = ip; >public void setValue(String s) < d = (new Double(s)).doubleValue(); >public INumber add(INumber n) < d += (new Double(n.toString())).doubleValue(); return this; >public INumber mul(INumber n) < d *= (new Double(n.toString())).doubleValue(); return this; >public String toString() < return (new Double(d)).toString(); >>
Протестируем классы, реализующие интерфейс INumber :
/** ------------------------------------------------------------- * TestNumber.java Тестирование ингтерфейса INumber. * ------------------------------------------------------------- */ public class TestNumber < public static void main(String[] args) < INumber i1 = new IntNumber(22 ); INumber i2 = new DblNumber(11.2); INumber i3 = new DblNumber(3.4 ); new TestINumber().calculation(i1, i2, i3); >>
Результат выполнения тестовой программы:
i2 = 11.2 n1 = 22 n2 = 11.2 n3 = 3.4 (n1 + n2) * n3 = 99 (n2 + n1) * n3 = 109.48 n1 * (n2 + n3) = 861 n3 * (n1 + n2) = 197.2
Следует обратить внимание, что реализация передается через объект. Класс нужен для порождения объекта, несущего реализацию. Но не обязательно, как увидим позднее. Интересно отметить, что результат операции над INumber зависит от последовательности использования переменных. Эффект возникает потому, что в спецификации типа мы опустили важные для чисел свойства: точность и диапазон допустимых значений. В результате они неявно берутся из базового типа, использованного при реализации.
Реализация интерфейса
В предыдущем примере мы видели, что реализация передается через объект. Следовательно, в объекте упакована вся необходимая информация по реализации интерфейса. Если поведение определяется интерфейсом, а реализация упакована в объекте, то зачем нужен класс? — Классы нужны для наследования реализации и повторного использования кода. Если повторное использование объекта не требуется, то и описание в виде класса не нужно.
В следующем примере класс используется только для запуска приложения. Логика программы реализована на трех интерфейсах без использования классов!
/** ------------------------------------------------------------- * TestAnimal.java Образец бесклассовой реализации интерфейса * ------------------------------------------------------------- */ import java.util.ArrayList; interface Animal < void giveSignals(); void goHome(); String getTitle(); String getNick(); >interface Command < void exeCommand(Animal an); >interface Ranch < void add(Animal an); void visitAll(Command cmd); >public class TestAnimal < public static void main(String[] args) < Ranch myRanch = new Ranch() < private ArrayList ranchAnimals = new ArrayList(); public void add(Animal a) < ranchAnimals.add(a); >public void visitAll(Command cmd) < for(int i = 0; i < ranchAnimals.size(); i++) cmd.exeCommand((Animal)ranchAnimals.get(i)); >>; // end of new Ranch() // add animals myRanch.add(new Animal() // dog < public void giveSignals() < System.out.println("Гав-гав"); >public void goHome() < System.out.println("Бежит в будку"); >public String getTitle() < return new String("собака"); >public String getNick() < return new String("Блэк"); >>); // end of add new Animal dog myRanch.add(new Animal() // sheep < public void giveSignals() < System.out.println("Бе-е"); >public void goHome() < System.out.println("Идет в загон"); >public String getTitle() < return new String("овца"); >public String getNick() < return new String(""); >>); // end of add new Animal sheep myRanch.add(new Animal() // another sheep < public void giveSignals() < System.out.println("Бе-е"); >public void goHome() < System.out.println("Идет в загон"); >public String getTitle() < return new String("овца"); >public String getNick() < return new String(""); >>); // end of add new Animal another sheep // gives signals System.out.println("\n ------- Все подали голос -------\n"); myRanch.visitAll(new Command() < public void exeCommand(Animal a) < System.out.print(a.getTitle()+" "+a.getNick() + " говорит: "); a.giveSignals(); >>); // go to Home System.out.println("\n------- Все домой! -------\n"); myRanch.visitAll(new Command() < public void exeCommand(Animal a) < System.out.print(a.getTitle()+" "+a.getNick() + " идет домой: "); a.goHome(); >>); > >Использование класса Sheep позволило бы сократить текст программы. Никаких других преимуществ введение этого класса не дает. Для остальных объектов определение соответствующих классов не дает ничего. Результат выполнения программы :
------- Все подали голос ------- собака Блэк говорит: Гав-гав овца говорит: Бе-е овца говорит: Бе-е ------- Все домой! ------- собака Блэк идет домой: Бежит в будку овца идет домой: Идет в загон овца идет домой: Идет в загон
Анонимный класс
Можно сказать, что в приведенном выше примере использованы анонимные классы, и мы будем правы.
Но что такое анонимный класс? В спецификации Java сказано: декларация анонимного класса автоматически извлекается компилятором из выражения создания экземпляра класса. Анонимный класс является подклассом существующего класса или реализации интерфейса, и анонимный класс не имеет имени.
Обычно, для того, чтобы создать объект, необходимо сначала декларировать класс. С анонимным классом все наоборот — сначала описывается экземпляр, а потом под него подгоняется класс. Можно сказать, что анонимный класс нужен для того, чтобы узаконить существование созданного объекта. То есть, в данном случае класс — это техническое средство для упаковки реализации; небольшой, относительно автономный кусочек программы (данные + код).
Но если этот кусочек необходимо повторить, то тогда имеет смысл сделать его доступным из разных частей программы. Таким образом, класс нужен только для повторного использования. Кроме того, в большой программе выделение кода в классы улучшает ее читаемость.
Наследование интерфейса, полиморфизм
Наследование типа и полиморфизм обеспечиваются наследованием интерфейса. Пример :
/** ------------------------------------------------------------- * TestShips.java Наследование интерфейсов и полиморфизм * ------------------------------------------------------------- */ import java.util.ArrayList; interface Ship < void runTo(String s); >interface WarShip extends Ship < void bombard(); >interface Transport extends Ship < void loadTroops(int n); void landTroops(); >public class TestShips < public static void main(String[] args) < ArrayListships = new ArrayList(); for(int i = 0; i < 3; i++) ships.add(new Transport() < private int troopers; public void runTo(String s) < System.out.println("Транспорт направляется в "+s+"."); >public void loadTroops(int n) < troopers = n; >public void landTroops() < System.out.println((new Integer(troopers)).toString() + " отрядов десантировано."); >>); for(int i = 0; i < 2; i++) ships.add(new WarShip() < public void runTo(String s) < System.out.println("Корабль направляется в " + s + "."); >public void bombard() < System.out.println("Корабль бомбардирует цель."); >>); for(int i = 0; i < 3; i++) ((Transport)ships.get(i)).loadTroops(i+5); for(int i = 0; i < ships.size(); i++) ((Ship)ships.get(i)).runTo("Вражий Порт"); for(int i = 0; i < 3; i++) ((Transport)ships.get(i)).landTroops(); for(int i = 3; i < ships.size(); i++) ((WarShip)ships.get(i)).bombard(); >>Результат выполнения программы:
Транспорт направляется в Вражий Порт. Транспорт направляется в Вражий Порт. Транспорт направляется в Вражий Порт. Корабль направляется в Вражий Порт. Корабль направляется в Вражий Порт. 5 отрядов десантировано. 6 отрядов десантировано. 7 отрядов десантировано. Корабль бомбардирует цель. Корабль бомбардирует цель.
Таким образом, концепция интерфейсов добавляет полиморфизму второе измерение :
- Иерархический полиморфизм в стиле C++, основанный на приведении к базовому типу классов и/или интерфейсов (см. TestShips);
- Полиморфизм экземпляров, основанный на разных реализациях одного и того же интерфейса (см. INumber).
Наследование имеет два аспекта:
- «быть похожим на» — наследование типа, поведения;
- «быть устроенным как» — наследование реализации.
Наследование реализации не означает наследование типа! В практике это не встречается, потому что и в С++ и в Java невозможно наследование реализации без наследования интерфейса. В C++ интерфейс и класс неотделимы друг от друга. В Java интерфейс от класса отделить можно, но класс от интерфейса — нельзя.
В С++ и в Java совокупность общедоступных (public) методов неявно образует интерфейс данного класса. В силу этого наследование класса автоматически означает как наследование реализации, так и наследование интерфейса (типа). Очевидно, что наследование структуры данных и программного кода не определяет тип потомка. Например, абстрактные методы являются частью интерфейса и не являются частью реализации. Если бы можно было исключить их из наследования, то мы получили бы наследование реализации без сохранения типа.
