Когда следует использовать интерфейсы?
Я знаю, что интерфейс не имеет тела, просто определение метода. Но когда я должен использовать интерфейсы? Если я предоставляю кому-то набор интерфейсов без тела, зачем ему нужно писать тело функции? Будет ли им лучше написать свой собственный абстрактный класс с абстрактными методами в нем.
Отредактировано:
Я думаю, что использование интерфейсов больше, когда вы являетесь частью команды. Предположим, что команда A пишет код для чего-то, и они хотели увидеть, является ли вызов метода. с именем getRecords(), сделано или нет. Это поможет команде B написать тело интерфейса, предоставленного им, и команда B должна сохранить имя метода схожим, чтобы выполнялся код команды A.
Просто мысль. Я могу быть не прав. Я думаю, что интерфейсы бесполезны для одного разработчика.
Отредактировано:
Спасибо всем за ответы. С учетом того, что вы все ответили, я думаю, что интерфейсы более полезны, когда вы создаете что-то вроде API?
19 ответов
В таких языках, как интерфейсы Java и C#, средства полиморфизируются. То есть класс может удовлетворять более чем одному контракту - он может вести себя как несколько разных типов, класс одного типа может заменить другой. В других языках это также может быть обеспечено множественным наследованием, но у этого подхода есть различные недостатки. Однако наличие класса, который ведет себя как несколько типов, не является наиболее распространенной мотивацией для использования интерфейсов.
Программируя интерфейсы вместо классов, вы также можете отделить вашу программу от конкретных реализаций. Это значительно упрощает замену одной реализации класса другой. Это особенно полезно при написании модульных тестов, в которых вы можете поменять местами некоторую реализацию тяжелого класса с легковесным фиктивным объектом. Если ваша программа ожидает только тип интерфейса, а тяжелый и фиктивный объект реализуют указанный интерфейс, то их очень легко заменить.
Также рассмотрим простой пример Java, где я говорю, что есть программа, которая отображает страницы данных на экране. Сначала я хочу получить данные из базы данных или файлов XML. Если я напишу свою программу так, чтобы она использовала интерфейсы, я могу определить интерфейс следующим образом:
public interface PageDatasource {
public List<Page> getPages();
}
И используйте это так:
PageDatasource datasource = // insert concrete PageDatasource implementation here
List<Pages> pages = datasource.getPages();
display(pages);
Затем я могу написать отдельную реализацию базы данных и XML, которая придерживается этого интерфейса:
public class DatabasePageDatasource implements PageDatasource {
public List<Page> getPages() {
// Database specific code
}
}
public class XmlPageDatasource implements PageDatasource {
public List<Page> getPages() {
// XML specific code
}
}
Поскольку я использовал интерфейс, я теперь могу использовать либо реализацию - базу данных, либо XML - взаимозаменяемо, без необходимости изменять части моей программы, которые запрашивают данные страницы. Реализации XML и базы данных, вероятно, делают совершенно разные вещи, но все, о чем заботится моя программа, - это то, что объект, предоставляющий данные страницы, реализует PageDatasource
интерфейс.
Интерфейс Real Life по аналогии:
Допустим, вы хотите выпустить книгу из библиотеки. Что ты будешь делать:
1) Перейти в библиотеку
2) Найдите книгу, которая вас интересует
3) Выберите книгу
4) Пойдите в Бюро Библиотеки, чтобы попросить, чтобы они Выпустили Книгу.
Теперь здесь Библиотекарь является Интерфейсом, что означает, что вас не интересует, кто, черт возьми, Библиотекарь, вы интересуетесь только тем человеком, который сидит на столе библиотекаря (это тот человек, который согласился на контракт, чтобы действовать как Библиотекарь, что означает, что этот человек согласился реализовать все поведения библиотекаря)
Итак, позвольте сказать: Поведение библиотекаря:
1) Выпуск книги
2) Переиздание книги
3) Вернуть книгу.
человек, который обозначен как библиотекарь (что означает согласие адаптировать вышеупомянутые три поведения), должен реализовать поведение по-своему.
Допустим, PersonA хочет сыграть роль библиотекаря, а затем ему нужно адаптировать вышеуказанные 3 поведения. Нам, как Клиенту, все равно, как он ведет себя как библиотекарь, нас интересует только тот человек, который является библиотекарем, и он соблюдает задачи библиотекаря.
Поэтому то, что вы будете делать, - это "Ссылка по интерфейсу" [перейти к Бюро библиотекарей (которое приведет вас к лицам, выступающим в роли библиотекаря)) и, скажем, в будущем один человек покинул должность библиотекаря, тогда как клиент это не повлияет на вас, если вы подошли к столу библиотекаря, вместо конкретного человека, выступающего в роли библиотекаря. Так что код для интерфейса вместо конкретности выгоден.
class PersonA implements Librarian {
public void IssueBook(){...}
public void ReIssueBook(){...}
public void ReturnBook(){...}
//Other person related tasks...
}
Даже в качестве одного разработчика интерфейсы могут быть очень полезным инструментом. Позвольте мне попытаться проиллюстрировать это на примере.
Предположим, вы разрабатываете систему для управления каталогом библиотеки, и библиотека будет предоставлять книги и DVD. Вы решаете создать классы Book и Dvd для моделирования предметов, которые вы предоставляете, но теперь вы хотите реализовать полиморфизм, чтобы вы могли иметь дело с предметами, а не с книгами или DVD. Вопрос в том, должен ли Item быть абстрактным классом или интерфейсом?
В этом случае вы, вероятно, захотите использовать абстрактный класс, поскольку есть функциональные возможности, общие для Book и Dvd, которые вы можете предоставить родительскому классу, например, проверить или вернуть элемент.
Теперь предположим, что вы хотите реализовать механизм персистентности для своего библиотечного каталога. Вы решили, что хотите хранить некоторые данные в базе данных, некоторые данные в файлах XML, а некоторые - в файлах с разделителями-запятыми. Таким образом, теперь вопрос заключается в том, как вы можете сделать это полиморфным способом, который позволяет вам иметь дело с общим API персистентности?
В этом случае вам, вероятно, следует определить интерфейс, который может быть реализован каждым из ваших классов, обеспечивающих постоянство базы данных, XML и запятых, поскольку каждый из этих механизмов сохранения предоставляет аналогичные функции, то есть хранение и извлечение данных, но каждый из них будет реализован совершенно по-разному. Это позволит вам легко изменить используемый вами механизм персистентности, не внося много изменений в код, который использует механизм персистентности.
Интерфейсы помогают прояснить различие между различными функциональными единицами. Один юнит зависит от другого, чтобы что- то делать, а не быть чем-то. Пока этот другой может делать то, что оговорено в интерфейсе (думать контракт), тогда это может быть что-то за кадром.
Например, у меня есть процессор ввода, который читает записи из одного места и записывает их в другое место. Неважно, что / где или куда / где. Все, что его волнует, - это то, что он получает запись от какого-то типа читателя (используя интерфейс IReader) на одной стороне, и передает их некоторому типу писателя (используя интерфейс IWriter).
В моей первой реализации разработчик IReader получает данные из базы данных SQL, а разработчик IWriter отправляет их через клиента веб-службы. Тем не менее, мы в конечном итоге создадим другие реализации для доступа к другим репозиториям (FTP-сайтам, каталогам файлов на локальном сетевом диске и т. Д.).
Все это время процессор посередине не заботится и не меняется. Он просто говорит через эти стандартные интерфейсы.
Теоретически вы могли бы использовать базовый класс (предпочтительно абстрактный) вместо интерфейса, но это начинает теснее связывать ваши модули, что усложняет обслуживание вашей системы. Потеря связи действительно делает вашу жизнь проще, даже если вы не в команде программистов. Считайте себя другим программистом, каждый раз, когда вы работаете в другой части вашей системы. Каждый раз, когда вы посещаете данный раздел, вы должны заново изучать, что он делает, чтобы поддерживать его. Если каждая часть вашей системы тесно связана с любой другой частью, то вы должны иметь постоянное, глубокое знание всей системы, а не только одной части, над которой вы работаете.
Также существует концепция единого класса, реализующего несколько интерфейсов, что почти похоже на множественное наследование. В этой ситуации один класс может выполнять несколько заданий (что можно утверждать, не очень разумно, но, по крайней мере, это возможно). Если вы решили использовать базовый класс вместо интерфейса выше, это было бы невозможно.
Причина, по которой существуют интерфейсы, связана с двумя основными принципами ООП, а именно "идентичность" и "функциональность".
Классы имеют функциональность и индивидуальность. С наследованием объекты могут иметь много функциональных возможностей и множество идентификаторов.
Интерфейсы идентичны без функциональности. Функциональность будет предоставлена экземпляром класса.
Третья форма, "миксин" - это функциональность без идентичности. Языки программирования, такие как ruby, предоставляют эту третью форму наследования.
То, как вы используете интерфейс, зависит от контекста языка программирования и ваших обстоятельств, но просто помните, что интерфейсы используются для определения идентификаторов, которые применяются к объектам.
На первый взгляд абстрактные классы и интерфейсы кажутся простыми. Зачем предоставлять только интерфейс, если вы также можете предоставить некоторую базовую реализацию? После расследования вы обнаружите, что это еще не все.
Тем не менее, есть ряд причин для использования интерфейсов. Вы можете найти достойный пост в блоге о различиях здесь.
Тем не менее, учтите тот факт, что вы можете создать интерфейс ("контракт", который говорит, что ваш класс определенно поддерживает определенные вызовы вызовов / вызовов методов), но вы можете предоставить только один абстрактный класс. Также учтите тот факт, что вы можете создать абстрактный класс, который также реализует один или несколько интерфейсов, и наследовать от него. Это не крайний случай. На самом деле это делается довольно часто в API, предназначенных для высокой расширяемости.
Посмотрите на сообщение в блоге, на которое я вам указывал, и вы должны получить полное представление о том, когда использовать каждый из них и почему вы должны их использовать. Я также очень рекомендую хорошую книгу, такую как "CLR via C#" от Microsoft Press. Вы многому научитесь!
Интерфейс лучше абстрактного класса, потому что вы можете реализовать несколько интерфейсов, и вы можете наследовать только от одного абстрактного класса.
Так что вы можете сделать:
class MyRow extends AbstractRow implements ISearchable, ISortable
{
}
Также ищите в Stackru другие подобные вопросы, такие как Необходимость интерфейсов в C#
Чтобы добавить к предыдущим ответам, интерфейсы помогают вам во время модульного тестирования, позволяя вам вводить фиктивный объект на основе интерфейса в ваш код, позволяя имитировать конкретные сценарии, а также изолировать ваш модульный тест для конкретного компонента, не полагаясь на внешние компоненты.,
Например, предположим, что у вас есть класс бизнес-логики, который использует класс логики данных для извлечения данных из источника данных и последующей их обработки. Создав интерфейс для класса логики данных, который он затем наследует, вы можете создать макет / фальшивый экземпляр этого класса на основе интерфейса и вставить его в класс бизнес-логики, который вы тестируете. Модулируемый экземпляр может быть определен так, чтобы он ожидал определенных вызовов методов / свойств, генерировал исключения в определенных точках, возвращал действительные выходные данные и т. Д. И т. Д. Это означает, что ваши модульные тесты могут выполняться быстрее / потенциально более надежно, поскольку они не зависят от базовых данных. источник доступен / фактически не должен подключаться к нему. И вы изолируете юнит-тест до конкретной единицы кода.
Допустим, у меня есть класс FoodEstablishment, теперь я хочу сделать простую операцию CRUD, так как мне это сделать? Я определяю интерфейс для сервиса, но почему?
public interface IFoodEstablishmentService
{
Task<int> AddAsync(FoodEstablishment oFoodEstablishment);
FoodEstablishment SelectByFoodEstablishmentId(long id);
Task<int> UpdateAsync(FoodEstablishment oFoodEstablishment);
Task<int> DeleteAsync(FoodEstablishment oFoodEstablishment);
}
Затем я реализую этот контракт или интерфейс для этой конкретной службы
public class FoodEstablishmentService : IFoodEstablishmentService
{
public async Task<int> AddAsync(FoodEstablishment oFoodEstablishment)
{
// Insert Operation
return result;
}
public FoodEstablishment SelectByFoodEstablishmentId(long id)
{
// Select Logic
return oFoodEstablishment;
}
public async Task<int> UpdateAsync(FoodEstablishment oFoodEstablishment)
{
// Update Logic
return result;
}
public async Task<int> DeleteAsync(FoodEstablishment oFoodEstablishment)
{
// Delete Logic
return result;
}
}
Так что в моей основной программе или где я хочу использовать Сервис, я буду делать
IFoodEstablishmentService oFoodEstablishmentService = new FoodEstablishmentService();
FoodEstablishment oFoodEstablishment = // Input might be from views;
oFoodEstablishmentService.AddAsync(oFoodEstablishment);
Так что до сих пор это кажется дополнительным шагом, когда мы могли бы сделать
FoodEstablishmentService oFoodEstablishmentService = new FoodEstablishmentService();
FoodEstablishment oFoodEstablishment = // Input might be from views;
oFoodEstablishmentService.AddAsync(oFoodEstablishment);
Но подождите, что если мне может понадобиться передать мою логику вставки через очередь, а не напрямую на сервер, дождаться завершения операции вставки и затем вернуть результат, вместо этого передать очередь, а затем работник очереди выполнит эту операцию, может быть не лучшей идеей вставка очереди, но да, безусловно, хорошо для примера интерфейса:-). Итак, теперь я создаю еще один класс FoodEstablishment, реализующий тот же контракт IFoodEstablishment.
public class FoodEstablishmentQueueService : IFoodEstablishmentService
{
public async Task<int> AddAsync(FoodEstablishment oFoodEstablishment)
{
// Insert Queue Operation
return result;
}
public FoodEstablishment SelectByFoodEstablishmentId(long id)
{
// Select Queue Logic
return oFoodEstablishment;
}
public async Task<int> UpdateAsync(FoodEstablishment oFoodEstablishment)
{
// Update Queue Logic
return result;
}
public async Task<int> DeleteAsync(FoodEstablishment oFoodEstablishment)
{
// Delete Queue Logic
return result;
}
}
Так что теперь, если я хочу использовать версию очереди, я бы просто сделал
IFoodEstablishmentService oFoodEstablishmentService = new FoodEstablishmentQueueService();
FoodEstablishment oFoodEstablishment = // Input might be from views;
oFoodEstablishmentService.AddAsync(oFoodEstablishment);
Мы могли бы сделать это с более старым способом, используя класс, но это ограничивает создание экземпляра класса определенным классом, который является своего рода жестким для расширения, теперь FoodEstablishmentQueueService может делать и другие вещи, создавать другой метод до тех пор, пока контракт не будет действительным, поэтому интерфейс является контактом для согласованности. Воображая, что один парень делает обычную версию, а другой - версию очереди, или кто-то делает кэш-версию, могут возникнуть проблемы с подписью, если только она заранее не указана относительно рабочего контракта, и люди не заканчивают все проверкой.
Аналогично, давайте рассмотрим другой простой пример использования предопределенных типов, таких как IEnumerable. Предположим, я передаю список коллекции FoodEstablishment и возвращаю отсортированный пользователем список
public FoodEstablishment[] SortFoodEstablishment(FoodEstablishment[] list)
{
foreach(var oFoodEstablishment in list)
{
// some logic
}
return sortedList;
}
Таким образом, мы будем использовать это таким образом
FoodEstablishment[] list = new FoodEstablishment[]{ //some values }
var listSorted = oFoodEstablishmentService.SortFoodEstablishment(list);
Но что, если мы отправим список вместо массива
List<FoodEstablishment> list = //some values;
var listSorted = oFoodEstablishmentService.SortFoodEstablishment(list);
Мы получим ошибку, потому что ее строгая реализация использует класс, поэтому вместо этого мы используем IEnumerable<>, который реализован в List и который в основном удаляет зависимость от List к Interface.
public IEnumerable<FoodEstablishment> SortFoodEstablishment(IEnumerable<FoodEstablishment> list)
{
foreach(var oFoodEstablishment in list)
{
// some logic
}
return sortedList;
}
Так что обычно реализация в значительной степени зависит от ситуации
Интерфейсы и абстрактные классы служат разным целям. Для начала абстрактный класс хорош только для наследования от него - его нельзя создать напрямую. Вы можете поместить код реализации в абстрактный класс, но этот код можно вызывать только из класса, который его расширяет, или из класса, который его расширяет.
пример:
public abstract class A {
public void myFunc() {
//do some work
}
}
public class B : A {
private void someOtherFunc() {
base.myFunc();
}
}
public class Z {
public void exampleFunc() {
//call public base class function exposed through extending class:
B myB = new B();
myB.myFunc();
//explicitly cast B to A:
((A) myB).myFunc();
//'A' cannot be created directly, do implicit cast down to base class:
A myA = new B();
myA.myFunc();
}
}
Цель интерфейса - предоставить контракт - это означает, что класс, который его реализует, должен обеспечить реализацию свойств или функций, объявленных в интерфейсе, и он должен использовать точно такие же сигнатуры функций. Это дает уверенность, когда я пишу некоторый код, который вызывает класс, который реализует интерфейс, и не имеет значения, из чего получены классы, или даже на каком языке они написаны, или откуда я их получил. Еще одна интересная особенность - у меня может быть несколько разных реализаций одной и той же сигнатуры функции для разных интерфейсов. Проверьте этот пример, который использует те же классы из предыдущего примера:
public class Z {
public void exampleFunc() {
C myC = new C();
//these two calls both call the same function:
myC.myFunc();
((IExampleInterface1)myC).myFunc();
D myD = new D();
//hmmm... try and work out what happens here, which myFunc() gets called?
//(clue: check the base class)
myD.myFunc();
//call different myFunc() in same class with identical signatures:
((IExampleInterface1)myD).myFunc();
((IExampleInterface2)myD).myFunc();
}
}
interface IExampleInterface1
{
void myFunc();
}
interface IExampleInterface2
{
void myFunc();
}
public class C : IExampleInterface1
{
public void myFunc()
{
//do stuff
}
}
public class D : A, IExampleInterface1, IExampleInterface2
{
void IExampleInterface1.myFunc()
{
//this is called when D is cast as IExampleInterface1
}
void IExampleInterface2.myFunc()
{
//this is called when D is cast as IExampleInterface2
}
}
Это проблема дизайна (я говорю о мире Java).
Интерфейс позволяет вам определять поведение и структуру компонента (класса) вашего программного обеспечения без ограничений во время выполнения.
Вместо этого абстрактный класс по умолчанию дает вам метод для метода: usefull, если вы уверены, что этот код может не измениться во время работы приложения.
Пример:
У вас есть веб-приложение коммерческой системы, которое в некоторых ситуациях отправляет электронное письмо (регистрация нового пользователя).
Вы можете использовать абстрактный класс SenderCommunication с методом boolean sendWithSuccefull (...), если вы уверены, что поведение не изменится oftern.
Вы можете использовать интерфейс InterfaceSenderCommunication с методом boolean sendWithSuccefull (...), если вы не уверены, что поведение не будет часто меняться.
Конечно, суждение "меняйте часто - не часто" зависит от 2 элементов:
- Сколько времени я должен потратить на синхронизацию старого кода с новыми спецификациями?
- Сколько платит клиент?;)
Интерфейс сохраняет определение и реализацию Separate.So на тот случай, если вы хотите внедрить конкретную реализацию, то это лучший способ сделать это.
Примером из реальной жизни является JDBC. Если вы видите API Statemnet,PreparedStatemnet Все является интерфейсом, но реализация зависит от поставщика, например, Oracle ojdbc jar имеет некоторую другую реализацию, mysql имеет другую.
В случае, если вы хотите изменить базу данных, вам просто нужно изменить драйвер nad в имени класса соединения. Это преимущество использования интерфейса
Абстрактный класс v/s интерфейс всегда является предметом обсуждения среди разработчиков. Я бы добавил свои 5 центов. Используйте абстрактный класс, когда вы хотите расширить базу комманов и где вы хотите предоставить реализацию по умолчанию для абстрактного метода.
Используйте интерфейс, когда вы хотите точно реализовать все абстрактные методы в классе, реализующем интерфейс, и тело метода по умолчанию не может быть предоставлено.
Одна из причин использования интерфейсов - это когда класс реализует несколько интерфейсов. Абстрактный класс не может этого сделать.
Одним из примеров является класс, который обрабатывает движение мыши, а нажатия клавиш будут реализовывать оба (вымышленных) интерфейса IMouseMove и IKeyPress.
Также использование интерфейсов облегчает юнит-тестирование. Чтобы протестировать классы, которые зависят от интерфейсов, и пока вы используете какое-либо внедрение зависимостей, вы можете создать классы-заглушки, которые реализуют зависимые интерфейсы, или вы можете использовать механизм имитации.
Java lang не поддерживает множественное наследование, поскольку для достижения цели используются интерфейсы.
Для того чтобы класс был абстрактным, только 1 метод должен быть абстрактным; Тогда как в случае интерфейса все методы являются абстрактными.
Объекты, принадлежащие к одной категории, могут использовать интерфейс. Когда между объектами существует отношение IS-A.
Например, текст, изображение,MP3,FLV,MOV принадлежит категория FileType
Если наше приложение позволяет пользователям загружать 4 типа файлов, все они принадлежат к одной категории, называемой ТИП ФАЙЛА.
- Текст
- Образ
- аудио
- видео
Внутри кода у нас будет объект для каждого из перечисленных выше типов.
Предположим, что нам нужно отправить файл тогда
С внешним интерфейсомС интерфейсомТаким образом, мы можем предположить, что все типы файлов (текст, аудио и т. Д.) Относятся к типу FileFormat. Поэтому мы формируем отношение IS-A.
Это помогает при возврате одного типа данных вместо того, чтобы возвращать определенный тип из функции, которую мы можем отправить общим типом, который является FileFormat.
Хорошим стилем считается сохранение ссылки на HashSet или TreeSet в переменной типа Set.
Set<String> names = new HashSet<String>();
Таким образом, вы должны изменить только одну строку, если вы решили использовать TreeSet
вместо.
Кроме того, методы, которые работают с наборами, должны указывать параметры типа Set:
public static void print(Set<String> s)
Тогда метод может использоваться для всех установленных реализаций.
Теоретически, мы должны сделать ту же рекомендацию для связанных списков, а именно сохранить ссылки LinkedList в переменных типа List. Однако в библиотеке Java интерфейс List является общим для ArrayList
и LinkedList
учебный класс. В частности, он имеет методы get и set для произвольного доступа, хотя эти методы очень неэффективны для связанных списков.
Вы не можете написать эффективный код, если не знаете, эффективен ли произвольный доступ или нет. Это явно серьезная ошибка проектирования в стандартной библиотеке, и я не могу рекомендовать использовать интерфейс List по этой причине.
(Чтобы понять, насколько смущает эта ошибка, взгляните на исходный код метода binarySearch класса Collections. Этот метод принимает параметр List, но двоичный поиск не имеет смысла для связанного списка. Затем код неуклюже пытается узнайте, является ли список связанным списком, а затем переключитесь на линейный поиск!)
Set
интерфейс и Map
интерфейс, хорошо продуман, и вы должны их использовать.
Использование интерфейса:
- Определить договор
- Связать несвязанные классы с возможностями (например, классы, реализующие интерфейс Serializable, могут иметь или не иметь никакого отношения между ними, кроме реализации этого интерфейса
- Чтобы обеспечить взаимозаменяемую реализацию, например,
Strategy_pattern
Если вы ищете Java в качестве языка программирования, interface
имеет несколько дополнительных возможностей с добавлением default
а также static
методы с момента выпуска Java-8.
Обратитесь к этому вопросу для более подробной информации:
Зачем использовать интерфейсы, множественное наследование и интерфейсы, преимущества интерфейсов?