Что значит "программировать на интерфейс"?

Я видел это упомянутое несколько раз, и мне не ясно, что это значит. Когда и зачем ты это делаешь?

Я знаю, что делают интерфейсы, но тот факт, что я не совсем уверен в этом, заставляет меня думать, что мне не хватает их правильного использования.

Это просто так, если вы должны были сделать:

IInterface classRef = new ObjectWhatever()

Вы можете использовать любой класс, который реализует IInterface? Когда вам нужно это сделать? Единственное, о чем я могу думать, это если у вас есть метод, и вы не уверены, какой объект будет передан, за исключением того, что он реализует IInterface, Я не могу думать, как часто вам нужно будет это делать.

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

35 ответов

Решение

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

Когда я впервые начал знакомиться с интерфейсами, я тоже был озадачен их актуальностью. Я не поняла, зачем они тебе нужны. Если мы используем такой язык, как Java или C#, у нас уже есть наследование, и я рассматривал интерфейсы как более слабую форму наследования и думал: "Зачем беспокоиться?" В некотором смысле я был прав, вы можете думать об интерфейсах как о некоторой слабой форме наследования, но помимо этого я, наконец, понял их использование в качестве языковой конструкции, рассматривая их как средство классификации общих черт или поведений, которые были продемонстрированы потенциально много не связанных классов объектов.

Например - скажем, у вас есть игра на SIM-карте и есть следующие классы:

class HouseFly inherits Insect {
    void FlyAroundYourHead(){}
    void LandOnThings(){}
}

class Telemarketer inherits Person {
    void CallDuringDinner(){}
    void ContinueTalkingWhenYouSayNo(){}
}

Очевидно, что эти два объекта не имеют ничего общего с точки зрения прямого наследования. Но вы могли бы сказать, что они оба раздражают.

Допустим, в нашей игре должна быть какая-то случайная вещь, которая раздражает игрока, когда они обедают. Это может быть HouseFly или Telemarketer или и то и другое - но как вы допускаете оба с одной функцией? И как вы просите каждый другой тип объекта "делать их надоедливые вещи" таким же образом?

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

interface IPest {
    void BeAnnoying();
}

class HouseFly inherits Insect implements IPest {
    void FlyAroundYourHead(){}
    void LandOnThings(){}

    void BeAnnoying() {
        FlyAroundYourHead();
        LandOnThings();
    }
}

class Telemarketer inherits Person implements IPest {
    void CallDuringDinner(){}
    void ContinueTalkingWhenYouSayNo(){}

    void BeAnnoying() {
        CallDuringDinner();
        ContinueTalkingWhenYouSayNo();
    }
}

Теперь у нас есть два класса, каждый из которых может раздражать по-своему. И они не должны происходить из одного и того же базового класса и иметь общие присущие им характеристики - им просто необходимо выполнить договор IPest - этот контракт прост. Вы просто должны BeAnnoying, В связи с этим мы можем смоделировать следующее:

class DiningRoom {

    DiningRoom(Person[] diningPeople, IPest[] pests) { ... }

    void ServeDinner() {
        when diningPeople are eating,

        foreach pest in pests
        pest.BeAnnoying();
    }
}

Здесь у нас есть столовая, которая принимает несколько закусочных и количество вредителей - обратите внимание на использование интерфейса. Это означает, что в нашем маленьком мире, член pests массив может быть на самом деле Telemarketer объект или HouseFly объект.

ServeDinner Метод вызывается, когда обед подается, и наши люди в столовой должны есть. В нашей маленькой игре именно тогда наши вредители выполняют свою работу - каждый вредитель должен быть раздражен IPest интерфейс. Таким образом, мы можем легко иметь как Telemarketers а также HouseFlys надоедать по-своему - нас волнует только то, что у нас что-то есть DiningRoom объект, который является вредным организмом, нам на самом деле все равно, что это такое, и они не могут иметь ничего общего с другими.

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

Конкретный пример, который я привел студентам, состоит в том, что они должны написать

List myList = new ArrayList(); // programming to the List interface

вместо

ArrayList myList = new ArrayList(); // this is bad

Они выглядят точно так же в короткой программе, но если вы продолжите использовать myList 100 раз в вашей программе вы можете начать видеть разницу. Первое объявление гарантирует, что вы вызываете только методы myList которые определены List интерфейс (поэтому нет ArrayList конкретные методы). Если вы запрограммировали интерфейс таким образом, позже вы можете решить, что вам действительно нужно

List myList = new TreeList();

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

Преимущества становятся еще более очевидными (я думаю), когда вы говорите о параметрах метода и возвращаемых значениях. Возьмите это к примеру:

public ArrayList doSomething(HashMap map);

Это объявление метода связывает вас с двумя конкретными реализациями (ArrayList а также HashMap). Как только этот метод вызывается из другого кода, любые изменения этих типов, вероятно, означают, что вам также придется изменить вызывающий код. Было бы лучше программировать на интерфейсы.

public List doSomething(Map map);

Теперь не имеет значения, какого рода List вы вернетесь, или какой Map передается в качестве параметра. Изменения, которые вы делаете внутри doSomething Метод не заставит вас изменить код вызова.

Программирование интерфейса говорит: "Мне нужна эта функциональность, и мне все равно, откуда она".

Рассмотрим (на Java) List интерфейс по сравнению с ArrayList а также LinkedList конкретные классы. Если все, что меня волнует, это то, что у меня есть структура данных, содержащая несколько элементов данных, к которым я должен получить доступ через итерацию, я бы выбрал List (и это 99% времени). Если я знаю, что мне нужно постоянное время вставки / удаления из любого конца списка, я мог бы выбрать LinkedList конкретная реализация (или, более вероятно, использовать интерфейс очереди). Если я знаю, что мне нужен произвольный доступ по индексу, я бы выбрал ArrayList конкретный класс.

Программирование на интерфейсе не имеет абсолютно никакого отношения к абстрактным интерфейсам, как мы видим в Java или.NET. Это даже не концепция ООП.

Что это действительно означает, так это не шутите над внутренностями объекта или структуры данных. Используйте абстрактный программный интерфейс или API для взаимодействия с вашими данными. В Java или C# это означает использование открытых свойств и методов вместо необработанного доступа к полям. Для C это означает использование функций вместо необработанных указателей.

РЕДАКТИРОВАТЬ: И с базами данных это означает использование представлений и хранимых процедур вместо прямого доступа к таблице.

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

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

РЕДАКТИРОВАТЬ: Ниже приведена ссылка на статью, где Эрих Гамма обсуждает свою цитату "Программа для интерфейса, а не реализации".

http://www.artima.com/lejava/articles/designprinciples.html

Вы должны посмотреть на инверсию контроля:

В таком случае вы бы не написали так:

IInterface classRef = new ObjectWhatever();

Вы бы написали что-то вроде этого:

IInterface classRef = container.Resolve<IInterface>();

Это войдет в основанную на правилах настройку в container объект, и построить реальный объект для вас, который может быть ObjectWhh независимо. Важно то, что вы могли бы заменить это правило чем-то, что использовало бы совершенно другой тип объекта, и ваш код все еще работал бы.

Если мы оставим IoC вне стола, вы можете написать код, который знает, что он может общаться с объектом, который делает что-то конкретное, но не с тем, какой тип объекта или как он это делает.

Это пригодится при передаче параметров.

Что касается вашего заключенного в скобки вопроса "Кроме того, как вы могли бы написать метод, который принимает объект, который реализует интерфейс? Это возможно?", В C# вы просто использовали бы тип интерфейса для типа параметра, например так:

public void DoSomethingToAnObject(IInterface whatever) { ... }

Это включается прямо в "разговор с объектом, который делает что-то конкретное". Описанный выше метод знает, чего ожидать от объекта, что он реализует все в IInterface, но ему все равно, какой это тип объекта, только то, что он придерживается контракта, а именно интерфейс.

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

Это красота интерфейсов. Вы можете написать кусок кода, который знает, что ему будут переданы объекты, от которых он может ожидать определенного поведения. Не важно, что это за объект, только то, что он поддерживает необходимое поведение.

Позвольте мне привести вам конкретный пример.

У нас есть специальная система перевода для оконных форм. Эта система перебирает элементы управления в форме и переводит текст в каждом. Система знает, как обрабатывать базовые элементы управления, такие как свойство "тип элемента управления, который имеет текст", и аналогичные базовые элементы, но в случае чего-либо простого она не дотягивает.

Теперь, поскольку элементы управления наследуются от предопределенных классов, которые мы не можем контролировать, мы можем сделать одну из трех вещей:

  1. Создайте поддержку для нашей системы перевода, чтобы точно определить, с каким типом управления она работает, и перевести правильные биты (кошмар обслуживания)
  2. Встроить поддержку в базовые классы (невозможно, поскольку все элементы управления наследуются от разных предопределенных классов)
  3. Добавить поддержку интерфейса

Итак, мы сделали Nr. 3. Все наши элементы управления реализуют ILocalizable, который является интерфейсом, который дает нам один метод - возможность переводить "себя" в контейнер текста / правил перевода. Таким образом, форме не нужно знать, какой тип элемента управления она нашла, только что она реализует определенный интерфейс и знает, что существует метод, который можно вызвать для локализации элемента управления.

Код для интерфейса Не реализация не имеет ничего общего с Java, как и с интерфейсом конструкции.

Эта концепция была широко известна в книгах "Образцы / банды четырех", но, скорее всего, была задолго до этого. Эта концепция, безусловно, существовала задолго до появления Java.

Конструкция Java Interface была создана, чтобы помочь в этой идее (среди прочего), и люди стали слишком сосредоточены на конструкции как на центре смысла, а не на первоначальном намерении. Однако по этой причине у нас есть открытые и закрытые методы и атрибуты в Java, C++, C# и т. Д.

Это означает просто взаимодействовать с открытым интерфейсом объекта или системы. Не беспокойтесь и даже не ожидайте, что он сделает то, что делает внутри. Не беспокойтесь о том, как это реализовано. Вот почему в объектно-ориентированном коде у нас есть публичные и частные методы / атрибуты. Мы намерены использовать открытые методы, потому что закрытые методы существуют только для внутреннего использования внутри класса. Они составляют реализацию класса и могут быть изменены по мере необходимости без изменения общедоступного интерфейса. Предположим, что в отношении функциональности метод класса будет выполнять одну и ту же операцию с тем же ожидаемым результатом каждый раз, когда вы вызываете его с одинаковыми параметрами. Это позволяет автору изменять работу класса, его реализацию, не нарушая взаимодействия людей с ним.

И вы можете программировать для интерфейса, а не для реализации, даже не используя конструкцию интерфейса. Вы можете программировать на интерфейс, а не на реализацию в C++, которая не имеет конструкции Interface. Вы можете интегрировать две массивные корпоративные системы гораздо надежнее, если они взаимодействуют через открытые интерфейсы (контракты), а не вызывают методы для объектов, внутренних по отношению к системам. Ожидается, что интерфейсы всегда будут реагировать одинаково ожидаемым образом при одинаковых входных параметрах; если реализовано для интерфейса, а не для реализации. Концепция работает во многих местах.

Встряхните мысль о том, что интерфейсы Java как-то связаны с понятием "Программа для интерфейса, а не реализация". Они могут помочь применить концепцию, но они не являются концепцией.

Похоже, вы понимаете, как работают интерфейсы, но не знаете, когда их использовать и какие преимущества они предлагают. Вот несколько примеров, когда интерфейс имеет смысл:

// if I want to add search capabilities to my application and support multiple search
// engines such as google, yahoo, live, etc.

interface ISearchProvider
{
    string Search(string keywords);
}

тогда я мог бы создать GoogleSearchProvider, YahooSearchProvider, LiveSearchProvider и т. д.

// if I want to support multiple downloads using different protocols
// HTTP, HTTPS, FTP, FTPS, etc.
interface IUrlDownload
{
    void Download(string url)
}

// how about an image loader for different kinds of images JPG, GIF, PNG, etc.
interface IImageLoader
{
    Bitmap LoadImage(string filename)
}

затем создайте JpegImageLoader, GifImageLoader, PngImageLoader и т. д.

Большинство надстроек и плагинов работают с интерфейсами.

Другое популярное использование - шаблон Repository. Скажем, я хочу загрузить список почтовых индексов из разных источников

interface IZipCodeRepository
{
    IList<ZipCode> GetZipCodes(string state);
}

затем я мог бы создать XMLZipCodeRepository, SQLZipCodeRepository, CSVZipCodeRepository и т. д. Для своих веб-приложений я часто создаю XML-репозитории на ранней стадии, чтобы я мог что-то запустить и запустить до того, как база данных Sql будет готова. Как только база данных готова, я пишу SQLRepository, чтобы заменить версию XML. Остальная часть моего кода остается неизменной, поскольку он работает исключительно на интерфейсах.

Методы могут принимать интерфейсы, такие как:

PrintZipCodes(IZipCodeRepository zipCodeRepository, string state)
{
    foreach (ZipCode zipCode in zipCodeRepository.GetZipCodes(state))
    {
        Console.WriteLine(zipCode.ToString());
    }
}

Там много объяснений, но сделать это еще проще. Возьмите например List, Можно реализовать список с помощью:

  1. внутренний массив
  2. Связанный список
  3. другая реализация

Создавая интерфейс, скажем List, Вы только код для определения списка или что List значит в реальности.

Вы можете использовать любой тип реализации внутренне, скажем, array реализация. Но предположим, что вы хотите изменить реализацию по какой-то причине, например, из-за ошибки или производительности. Тогда вам просто нужно изменить декларацию List<String> ls = new ArrayList<String>() в List<String> ls = new LinkedList<String>(),

Нет, где-нибудь еще в коде, вам придется что-то менять; Потому что все остальное было построено на определении List,

Это делает ваш код намного более расширяемым и простым в обслуживании, когда у вас есть наборы похожих классов. Я младший программист, поэтому я не эксперт, но я только что закончил проект, который требовал чего-то подобного.

Я работаю над программным обеспечением на стороне клиента, которое взаимодействует с сервером, на котором работает медицинское устройство. Мы разрабатываем новую версию этого устройства, в которой есть некоторые новые компоненты, которые клиент должен настраивать время от времени. Есть два типа новых компонентов, и они разные, но они также очень похожи. По сути, мне пришлось создать две формы конфигурации, два списка списков, два из всего.

Я решил, что было бы лучше создать абстрактный базовый класс для каждого типа элемента управления, который будет содержать почти всю реальную логику, а затем производные типы, чтобы позаботиться о различиях между этими двумя компонентами. Однако базовые классы не смогли бы выполнять операции над этими компонентами, если бы мне приходилось все время беспокоиться о типах (ну, они могли бы это делать, но в каждом методе был бы оператор "if" или переключатель),

Я определил простой интерфейс для этих компонентов, и все базовые классы общаются с этим интерфейсом. Теперь, когда я что-то изменяю, это "работает" везде, и у меня нет дублирования кода.

Если вы программируете на Java, JDBC является хорошим примером. JDBC определяет набор интерфейсов, но ничего не говорит о реализации. Ваши приложения могут быть написаны для этого набора интерфейсов. Теоретически, вы выбираете драйвер JDBC, и ваше приложение будет работать. Если вы обнаружите, что есть более быстрый или "лучший" или более дешевый драйвер JDBC или по какой-либо причине, вы можете теоретически заново настроить файл свойств, и без внесения каких-либо изменений в ваше приложение, оно все равно будет работать.

Я опоздал на этот вопрос, но я хочу упомянуть здесь, что строка "Программа для интерфейса, а не для реализации" имела хорошее обсуждение в книге "Шаблоны проектирования GoF (Gang of Four)".

Указано на с. 18:

Программа для интерфейса, а не реализация

Не объявляйте переменные экземплярами определенных конкретных классов. Вместо этого фиксируйте только интерфейс, определенный абстрактным классом. Вы найдете, что это является общей темой шаблонов дизайна в этой книге.

и, кроме того, это началось с:

Есть два преимущества манипулирования объектами исключительно с точки зрения интерфейса, определенного абстрактными классами:

  1. Клиенты остаются в неведении о конкретных типах объектов, которые они используют, пока объекты придерживаются интерфейса, ожидаемого клиентами.
  2. Клиенты не знают классов, которые реализуют эти объекты. Клиенты знают только об абстрактных классах, определяющих интерфейс.

Другими словами, не пишите свои классы, чтобы quack() метод для уток, а затем bark() метод для собак, потому что они слишком специфичны для конкретной реализации класса (или подкласса). Вместо этого напишите метод, используя имена, которые являются достаточно общими для использования в базовом классе, например giveSound() или же move(), так что они могут быть использованы для уток, собак или даже автомобилей, и тогда клиент ваших классов может просто сказать .giveSound() а не думать о том, чтобы использовать quack() или же bark() или даже определить тип, прежде чем выдать правильное сообщение для отправки объекту.

Программирование на интерфейсах - это круто, это способствует слабой связи. Как упомянул @lassevk, Inversion of Control отлично подходит для этого.

Кроме того, посмотрите на твердые принципы. вот видео серия

Он проходит через жестко закодированный (сильно связанный пример), затем просматривает интерфейсы и, наконец, переходит к инструменту IoC/DI (NInject).

В дополнение к уже выбранному ответу (и различным информативным сообщениям здесь), я бы настоятельно рекомендовал взять копию Head First Design Patterns. Это очень легко прочитать, и он ответит на ваш вопрос напрямую, объяснит, почему это важно, и покажет вам много шаблонов программирования, которые вы можете использовать, чтобы использовать этот принцип (и другие).

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

Может быть полезно программировать на интерфейсы, даже когда мы не зависим от абстракций.

Программирование интерфейсов вынуждает нас использовать контекстуально подходящее подмножество объекта. Это помогает, потому что это:

  1. мешает нам делать неуместные в контексте вещи, и
  2. позволяет нам безопасно изменить реализацию в будущем.

Например, рассмотрим Person класс, который реализует Friend и Employee интерфейс.

class Person implements AbstractEmployee, AbstractFriend {

}

В контексте дня рождения человека мы программируем Friend интерфейс, чтобы предотвратить обращение с человеком как Employee,

function party() {
    const friend: Friend = new Person("Kathryn");
    friend.HaveFun();
}

В контексте работы человека мы программируем Employee интерфейс, чтобы предотвратить размывание границ рабочего места.

function workplace() {
    const employee: Employee = new Person("Kathryn");
    employee.DoWork();
}

Отлично. Мы вели себя соответствующим образом в разных контекстах, и наше программное обеспечение работает хорошо.

В далеком будущем, если наш бизнес переходит на работу с собаками, мы можем довольно легко изменить программное обеспечение. Сначала мы создаем Dog класс, который реализует оба Friend а также Employee, Тогда мы смело меняемся new Person() в new Dog(), Даже если обе функции имеют тысячи строк кода, это простое редактирование будет работать, потому что мы знаем следующее:

  1. функция party использует только Friend подмножество Person,
  2. функция workplace использует только Employee подмножество Person,
  3. Учебный класс Dog реализует как Friend а также Employee интерфейсы.

С другой стороны, если либо party или же workplace должны были запрограммировать против Person, существует риск того, что оба Personкод Изменение от Person в Dog потребует от нас прочесать код, чтобы уничтожить любой Personкод, который Dog не поддерживается.

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

Проще говоря... Если я пишу новый класс Swimmer для добавления функциональности swim() и нужно использовать объект класса скажем Dog, и этот класс Dog реализует интерфейс Animal, который объявляет swim()[Для лучшего понимания.... вы можете нарисовать диаграмму относительно того, о чем я говорю]. В верхней части иерархии (Животное) это очень абстрактно, а внизу (Собака) это очень конкретно. Я думаю о "программировании для интерфейсов" так, что, когда я пишу класс Swimmer, я хочу написать свой код в соответствии с интерфейсом, который находится настолько далеко от той иерархии, которая в данном случае является объектом Animal. Интерфейс свободен от деталей реализации и, следовательно, делает ваш код слабо связанным. Детали реализации могут изменяться со временем, однако это не повлияет на оставшийся код, поскольку все, с чем вы взаимодействуете, - это интерфейс, а не реализация. Вам не важно, на что похожа реализация... все, что вы знаете, это то, что будет класс, который будет реализовывать интерфейс.

Предыдущие ответы фокусировались на программировании на абстракцию ради расширяемости и слабой связи. Хотя это очень важные моменты, не менее важна удобочитаемость . Читаемость позволяет другим (и вам в будущем) понимать код с минимальными усилиями. Вот почему для удобства чтения используются абстракции.

Абстракция по определению проще, чем ее реализация. Абстракция опускает детали, чтобы передать сущность или цель вещи, но не более того. Поскольку абстракции проще, я могу вместить в голову гораздо больше из них за один раз по сравнению с реализациями.

Как программист (на любом языке) я хожу с общей идеей в голове все время. В частности, a разрешает произвольный доступ, дублирует элементы и поддерживает порядок. Когда я вижу такое заявление: List myList = new ArrayList()Я думаю, круто , это то, что используется (основным) способом, который я понимаю; и мне не нужно больше об этом думать.

С другой стороны, я не ношу в голове конкретные детали реализации. Итак, когда я вижу, ArrayList myList = new ArrayList(). Я думаю, ох-ох , это нужно использовать так, как это не предусмотрено интерфейсом. Теперь мне нужно отследить все случаи использования этого, чтобы понять, почему, потому что иначе я не смогу полностью понять этот код. Это становится еще более запутанной , когда я обнаружил , что 100% из использований этого сделать соответствовать интерфейсу. Тогда я остаюсь задаваться вопросом ... был ли какой-то код, основанный на ArrayListдетали реализации, которые были удалены? Был ли программист, создавший его, просто некомпетентным? Это приложение каким-то образом привязано к этой конкретной реализации во время выполнения? Способ, который я не понимаю?

Я сейчас сбит с толку и не уверен в этом приложении, и все, о чем мы говорим, - это просто. Что, если бы это был сложный бизнес-объект, игнорирующий его интерфейс? Тогда моих знаний в области бизнеса недостаточно, чтобы понять цель кода.

Так что даже когда мне нужен List строго в пределах private(ничего, что могло бы сломать другие приложения, если бы оно изменилось, и я мог бы легко найти / заменить каждое использование в моей среде IDE), он по-прежнему улучшает удобочитаемость для программирования в абстракции. Потому что абстракции проще деталей реализации. Можно сказать, что программирование на абстракциях - это один из способов придерживаться принципа KISS .

Короткая история: Почтальона просят пойти домой домой и получить обложки (письма, документы, чеки, подарочную карту, заявление, любовный бюллетень) с адресом, написанным на нем для доставки.

Предположим, что нет прикрытия, и попросите почтальона пойти домой домой и получить все вещи и доставить другому человеку, которого почтальон может запутать,

так что лучше оберните его крышкой (в нашей истории это интерфейс), тогда он отлично справится со своей работой.

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

Создать тип interface не фактический тип, но реализовать с фактическим типом.

Создание интерфейса означает, что ваши компоненты легко вписываются в остальную часть кода

Я приведу вам пример.

у вас есть интерфейс AirPlane, как показано ниже.

interface Airplane{
    parkPlane();
    servicePlane();
}

Предположим, у вас есть методы в вашем классе контроллеров плоскостей, такие как

parkPlane(Airplane plane)

а также

servicePlane(Airplane plane)

реализовано в вашей программе. Это не сломает ваш код. Я имею в виду, что это не должно измениться, если он принимает аргументы как AirPlane,

Потому что он примет любой самолет, несмотря на фактический тип, flyer, highflyr, fighter, так далее.

Также в коллекции:

List<Airplane> plane; // Приниму все ваши самолеты.

Следующий пример очистит ваше понимание.


У вас есть истребитель, который реализует его, так что

public class Fighter implements Airplane {

    public void  parkPlane(){
        // Specific implementations for fighter plane to park
    }
    public void  servicePlane(){
        // Specific implementatoins for fighter plane to service.
    }
}

То же самое для HighFlyer и других предметов:

public class HighFlyer implements Airplane {

    public void  parkPlane(){
        // Specific implementations for HighFlyer plane to park
    }

    public void  servicePlane(){
        // specific implementatoins for HighFlyer plane to service.
    }
}

Теперь думайте, что ваши классы контроллеров используют AirPlane несколько раз,

Предположим, что ваш класс Controller - это ControlPlane, как показано ниже,

public Class ControlPlane{ 
 AirPlane plane;
 // so much method with AirPlane reference are used here...
}

здесь магия приходит как

Вы можете сделать свой новый AirPlane набирайте экземпляры столько раз, сколько хотите, и вы не меняете

код ControlPlane учебный класс.

Вы можете добавить экземпляр..

JumboJetPlane // implementing AirPlane interface.
AirBus        // implementing AirPlane interface.

Вы также можете удалить экземпляры.. ранее созданных типов.

Это также хорошо для модульного тестирования, вы можете внедрить свои собственные классы (которые отвечают требованиям интерфейса) в класс, который зависит от него

Представьте, что у вас есть продукт под названием "Зебра", который можно расширить с помощью плагинов. Он находит плагины путем поиска DLL в некотором каталоге. Он загружает все эти библиотеки DLL и использует отражение, чтобы найти любые классы, которые реализуют IZebraPlugin, а затем вызывает методы этого интерфейса для связи с плагинами.

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

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

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

C++ объяснение.

Думайте об интерфейсе как о ваших открытых методах классов.

Затем вы можете создать шаблон, который "зависит" от этих открытых методов, чтобы выполнить свою собственную функцию (она делает вызовы функций, определенные в общедоступном интерфейсе классов). Допустим, этот шаблон является контейнером, как класс Vector, а интерфейс, от которого он зависит, является алгоритмом поиска.

Любой класс алгоритма, который определяет функции / интерфейс, к которым Vector обращается, будет удовлетворять "контракту" (как кто-то объяснил в первоначальном ответе). Алгоритмы даже не должны быть одного и того же базового класса; единственное требование состоит в том, чтобы функции / методы, от которых зависит Вектор (интерфейс), были определены в вашем алгоритме.

Смысл всего этого в том, что вы можете предоставить любой другой алгоритм / класс поиска, если он предоставляет интерфейс, от которого зависит Vector (поиск по пузырькам, последовательный поиск, быстрый поиск).

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

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

Что касается "упущения" в отношении того, как все работает; большой (по крайней мере, в C++), поскольку именно так работает большая часть стандартной библиотеки ШАБЛОНА.

Конечно, при использовании наследственных и абстрактных классов методология программирования интерфейса меняется; но принцип тот же, ваши публичные функции / методы - это интерфейс ваших классов.

Это огромная тема и один из основополагающих принципов проектирования шаблонов.

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

Например, я мог бы иметь интерфейс движения. Метод, который заставляет что-то "двигаться", и любой объект (Person, Car, Cat), который реализует интерфейс движения, может быть передан и задан для перемещения. Без метода каждый знает тип класса.

В Java все эти конкретные классы реализуют интерфейс CharSequence:

CharBuffer, String, StringBuffer, StringBuilder

Эти конкретные классы не имеют общего родительского класса, кроме Object, поэтому ничто не связывает их, кроме того факта, что каждый из них имеет какое-то отношение к массивам символов, представляя их или манипулируя ими. Например, символы String не могут быть изменены после создания экземпляра объекта String, тогда как символы StringBuffer или StringBuilder можно редактировать.

Тем не менее, каждый из этих классов способен соответствующим образом реализовать методы интерфейса CharSequence:

char charAt(int index)
int length()
CharSequence subSequence(int start, int end)
String toString()

В некоторых случаях классы библиотеки классов Java, которые раньше принимали String, были пересмотрены, чтобы теперь принимать интерфейс CharSequence. Поэтому, если у вас есть экземпляр StringBuilder, вместо извлечения объекта String (что означает создание экземпляра нового объекта), вы можете просто передать сам StringBuilder, поскольку он реализует интерфейс CharSequence.

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

BufferedWriter, CharArrayWriter, CharBuffer, FileWriter, FilterWriter, LogStream, OutputStreamWriter, PipedWriter, PrintStream, PrintWriter, StringBuffer, StringBuilder, StringWriter, Writer

Интерфейс подобен контракту, где вы хотите, чтобы ваш класс реализации реализовывал методы, написанные в контракте (Interface). Поскольку Java не обеспечивает множественное наследование, программирование для интерфейса - хороший способ достижения цели множественного наследования. Если у вас есть класс A, который уже расширяет какой-то другой класс B, но вы хотите, чтобы класс A также следовал определенным рекомендациям или реализовывал определенный контракт, тогда вы можете сделать это путем программирования для стратегии взаимодействия.

Программа для интерфейса - это термин из книги GOF. Я бы не сказал прямо, что это связано с java-интерфейсом, скорее с реальными интерфейсами. для достижения чистого разделения уровней вам нужно создать некоторое разделение между системами, например: допустим, у вас есть конкретная база данных, которую вы хотите использовать, вы никогда не будете "программировать для базы данных", вместо этого вы бы "запрограммировали для интерфейса хранилища". Точно так же вы никогда не будете "программировать для веб-службы", а скорее вы будете программировать для "клиентского интерфейса". это так, чтобы вы могли легко поменять местами.

Я считаю, что эти правила мне помогают:

1. мы используем интерфейс java, когда у нас есть несколько типов объекта. если у меня есть только один объект, я не вижу в этом смысла. если есть хотя бы две конкретные реализации какой-то идеи, то я бы использовал интерфейс java.

2. если, как я сказал выше, вы хотите перенести развязку от внешней системы (системы хранения) на вашу собственную систему (локальную БД), тогда также используйте интерфейс.

обратите внимание, что есть два способа определить, когда их использовать. надеюсь это поможет.

Q: - ... "Вы могли бы использовать любой класс, который реализует интерфейс?"
A: - Да.

Q: - ... "Когда тебе нужно будет это сделать?"
A: - Каждый раз, когда вам нужен класс (ы), который реализует интерфейс (ы).

Примечание: мы не смогли создать экземпляр интерфейса, не реализованного классом - True.

  • Зачем?
  • потому что интерфейс имеет только прототипы методов, а не определения (только имена функций, а не их логика)

AnIntf ​​anInst = new Aclass ();
// мы можем сделать это только если Aclass реализует AnIntf.
// anInst будет иметь ссылку на класс.


Замечания:
Теперь мы можем понять, что произойдет, если Bclass и Cclass реализуют один и тот же Dintf.

Dintf bInst = new Bclass();  
// now we could call all Dintf functions implemented (defined) in Bclass.

Dintf cInst = new Cclass();  
// now we could call all Dintf functions implemented (defined) in Cclass.

Что мы имеем:
одни и те же прототипы интерфейса (имена функций в интерфейсе) и вызов различных реализаций.

Список используемой литературы:
Прототипы - Википедия

Программирование интерфейса — это философия, а не конкретные языковые конструкции или шаблоны проектирования — оно указывает вам, каков правильный порядок шагов для создания лучших программных систем (например, более отказоустойчивых, более тестируемых, более масштабируемых, более расширяемых, и другие приятные черты).

Что это на самом деле означает:

===

Прежде чем переходить к реализации и кодированию (КАК), подумайте о ЧТО:

  • Какие черные ящики должны составлять вашу систему,
  • Какова ответственность каждой коробки,
  • Каким образом каждый «клиент» (то есть один из этих других ящиков, сторонние «ящики» или даже люди) должен взаимодействовать с ним (API каждого ящика).

После того , как вы разберетесь с вышеизложенным, приступайте к реализации этих блоков (КАК).

Думая в первую очередь о том, что такое коробка и каков ее API, вынуждает разработчика выделять ответственность за коробку и отмечать для себя и будущих разработчиков разницу между ее открытыми деталями («API») и ее скрытыми деталями (« детали реализации»), что является очень важным отличием.

Одно немедленное и легко заметное преимущество заключается в том, что команда может изменять и улучшать реализации, не затрагивая общую архитектуру. Это также делает систему НАМНОГО более тестируемой (это хорошо сочетается с подходом TDD).

===
Помимо черт, о которых я упоминал выше, вы также экономите МНОГО ВРЕМЕНИ, идя в этом направлении.

Micro Services и DDD, если все сделано правильно, являются отличными примерами «кодирования для интерфейса», однако эта концепция выигрывает во всех шаблонах, от монолитов до «бессерверных», от BE до FE, от ООП до функциональных и т. д. ....

Я настоятельно рекомендую этот подход для разработки программного обеспечения (и я в основном считаю, что он имеет смысл и в других областях).

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

IInterface classRef = new ObjectWhatever()

Вы могли бы использовать любой класс, который реализует IInterface? Когда вам нужно это сделать?

Посмотрите на этот вопрос SE для хорошего примера.

Почему интерфейс для Java-класса должен быть предпочтительным?

делает использование интерфейса хит производительности?

если так сколько?

Да. Это будет иметь незначительное снижение производительности в течение нескольких секунд. Но если ваше приложение требует динамического изменения реализации интерфейса, не беспокойтесь о влиянии на производительность.

как вы можете избежать этого без необходимости поддерживать два бита кода?

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

Один хороший пример использования: реализация шаблона стратегии:

Пример шаблона стратегии в реальном мире

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

Другие вопросы по тегам