Что значит "программировать интерфейс"?
Я видел это упоминалось несколько раз, и я не понимаю, что это значит. Когда и зачем ты это делаешь?
Я знаю, что делают интерфейсы, но тот факт, что я не совсем уверен в этом, заставляет меня думать, что мне не хватает их правильного использования.
Это просто так, если вы должны были сделать:
IInterface classRef = new ObjectWhatever()
Вы могли бы использовать любой класс, который реализует IInterface
? Когда вам нужно это сделать? Единственное, о чем я могу думать, это если у вас есть метод, и вы не уверены, какой объект будет передан, за исключением того, что он реализует IInterface
. Я не могу думать, как часто вам нужно будет это делать.
Кроме того, как вы могли бы написать метод, который принимает объект, который реализует интерфейс? Это возможно?
Ответы
Ответ 1
Здесь есть несколько замечательных ответов на эти вопросы, которые вовлекают в себя всевозможные подробные сведения о интерфейсах и слабосвязанный код, инверсию управления и т.д. Есть несколько довольно острых дискуссий, поэтому я хотел бы воспользоваться возможностью немного разобраться в том, почему интерфейс полезен.
Когда я впервые начал сталкиваться с интерфейсами, я тоже был смущен их релевантностью. Я не понимал, зачем вам это нужно. Если мы используем такой язык, как Java или С#, у нас уже есть наследование, и я рассматривал интерфейсы как более слабую форму наследования и мысли, "зачем беспокоиться"? В некотором смысле я был прав, вы можете думать о интерфейсах как о какой-то слабой форме наследования, но помимо этого я, наконец, понял их использование в качестве языковой конструкции, считая их как средство классификации общих черт или моделей поведения, которые были представлены потенциально много несвязанных классов объектов.
Например, скажем, у вас есть игра с 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
общими присущими характеристиками - им просто нужно удовлетворить контракт 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
который является вредителем, нам все равно, что это такое, и они могут не имеют ничего общего с другими.
Этот очень надуманный пример псевдокода (который тянулся намного дольше, чем я ожидал) просто предназначен для иллюстрации того, что, наконец, включило свет для меня с точки зрения того, когда мы могли бы использовать интерфейс. Я заранее извиняюсь за глупость примера, но надеюсь, что это поможет в вашем понимании. И, конечно же, другие опубликованные ответы, которые вы получили здесь, действительно охватывают диапазон использования интерфейсов сегодня в шаблонах проектирования и методологиях разработки.
Ответ 2
Конкретный пример, который я использовал студентам, заключается в том, что они должны писать
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
, не заставят вас изменить код вызова.
Ответ 3
Программирование на интерфейс говорит: "Мне нужна эта функциональность, и мне все равно, откуда она".
Рассмотрим (в Java) интерфейс List
по сравнению с ArrayList
и LinkedList
конкретными классами. Если все, о чем я забочусь, это то, что у меня есть структура данных, содержащая несколько элементов данных, к которым я должен обращаться через итерацию, я бы выбрал List
(и это 99% времени). Если я знаю, что мне нужно вставить/удалить постоянное время с любого конца списка, я мог бы выбрать конкретную реализацию LinkedList
(или, более вероятно, использовать Queue). Если я знаю, что мне нужен произвольный доступ по индексу, я бы выбрал конкретный класс ArrayList
.
Ответ 4
Использование интерфейсов является ключевым фактором, позволяющим легко проверить ваш код в дополнение к удалению ненужных связей между вашими классами. Создавая интерфейс, который определяет операции над вашим классом, вы разрешаете классам, которые хотят использовать эту функциональность, возможность использовать его, не зависимо от вашего класса реализации напрямую. Если позже вы решите изменить и использовать другую реализацию, вам нужно изменить только часть кода, в котором реализована реализация. Остальная часть кода не должна изменяться, поскольку она зависит от интерфейса, а не от класса реализации.
Это очень полезно при создании модульных тестов. В тестируемом классе вы зависите от интерфейса и вставляете экземпляр интерфейса в класс (или factory, который позволяет ему создавать экземпляры интерфейса по мере необходимости) через конструктор или определитель свойств. Класс использует предоставленный (или созданный) интерфейс в своих методах. Когда вы начинаете писать свои тесты, вы можете издеваться или подделывать интерфейс и предоставлять интерфейс, который отвечает данными, настроенными в вашем unit test. Вы можете сделать это, потому что ваш тестируемый класс имеет дело только с интерфейсом, а не с вашей конкретной реализацией. Любой класс, реализующий интерфейс, включая ваш макет или поддельный класс, будет делать.
РЕДАКТИРОВАТЬ: Ниже приведена ссылка на статью, где Эрих Гамма обсуждает свою цитату "Программа для интерфейса, а не реализация".
http://www.artima.com/lejava/articles/designprinciples.html
Ответ 5
Вы должны посмотреть на инверсию управления:
В таком сценарии вы не будете писать это:
IInterface classRef = new ObjectWhatever();
Вы бы написали что-то вроде этого:
IInterface classRef = container.Resolve<IInterface>();
Это приведет к установке на основе правил в объекте container
и построит для вас фактический объект, который может быть ObjectWhatever. Важно то, что вы можете заменить это правило чем-то, что вообще использует другой тип объекта, и ваш код все равно будет работать.
Если мы покинем IoC из таблицы, вы можете написать код, который знает, что он может разговаривать с объектом, который делает что-то конкретное, но не с каким типом объекта и с каким он его выполняет.
Это пригодится при передаче параметров.
Что касается вашего вопроса в круглых скобках "Также, как вы можете написать метод, который принимает объект, реализующий интерфейс? Возможно ли это?", в С# вы просто используете тип интерфейса для типа параметра, например:
public void DoSomethingToAnObject(IInterface whatever) { ... }
Это подключается прямо к "разговору с объектом, который делает что-то конкретное". Метод, определенный выше, знает, чего ожидать от объекта, что он реализует все на IInterface, но ему не важно, какой тип объекта он есть, только то, что он придерживается контракта, что является интерфейсом.
Например, вы, вероятно, знакомы с калькуляторами и, вероятно, использовали довольно много в свои дни, но большую часть времени они все разные. Вы, с другой стороны, знаете, как должен работать стандартный калькулятор, чтобы вы могли использовать их все, даже если вы не можете использовать конкретные функции, которые каждый калькулятор имеет, что ни один из них не имеет.
Это красота интерфейсов. Вы можете написать фрагмент кода, который знает, что он получит переданные ему объекты, от которых он может ожидать определенного поведения. Он не заботится о том, чтобы какой-то объект был тем, что он поддерживает требуемое поведение.
Позвольте мне привести конкретный пример.
У нас есть специальная система перевода для форм Windows. Эта система проходит через элементы управления в форме и преобразует текст в каждый. Система знает, как обрабатывать базовые элементы управления, такие как свойство type-of-of-control-that-has-a-Text и аналогичные базовые элементы, но для чего-то базового оно не подходит.
Теперь, поскольку элементы управления наследуют от предварительно определенных классов, которые мы не контролируем, мы могли бы сделать одну из трех вещей:
- Создайте поддержку нашей системы перевода, чтобы точно определить, с каким типом управления она работает, и перевести правильные биты (кошмар обслуживания)
- Поддержка построения базовых классов (невозможно, поскольку все элементы управления наследуются от разных заранее определенных классов)
- Добавить поддержку интерфейса
Итак, мы сделали nr. 3. Все наши элементы управления реализуют ILocalizable, который является интерфейсом, который дает нам один метод, способность переводить "сам" в контейнер текста перевода/правил. Таким образом, форма не должна знать, какой вид управления она обнаружила, только то, что она реализует конкретный интерфейс, и знает, что существует метод, в котором он может вызвать локализацию элемента управления.
Ответ 6
Программирование на интерфейс абсолютно не имеет отношения к абстрактным интерфейсам, как мы видим в Java или .NET. Это даже не концепция ООП.
То, что это на самом деле означает, - это не возиться с внутренностями объекта или структуры данных. Используйте интерфейс абстрактной программы или API для взаимодействия с вашими данными. В Java или С# это означает использование общедоступных свойств и методов вместо необработанного доступа к полям. Для C это означает использование функций вместо необработанных указателей.
EDIT: И с базами данных это означает использование представлений и хранимых процедур вместо прямого доступа к таблице.
Ответ 7
Код для интерфейса. Не реализация не имеет НИЧЕГО делать с Java, ни конструкцией интерфейса.
Эта концепция получила широкое распространение в книгах "Шаблоны/Банды четырех", но, скорее всего, была вокруг этого. Концепция, безусловно, существовала задолго до того, как существовала Java.
Конструкция интерфейса Java была создана для помощи в этой идее (среди прочего), и люди стали слишком сосредоточены на конструкции как на центре смысла, а не на первоначальном намерении. Однако это причина, по которой мы имеем общедоступные и частные методы и атрибуты в Java, C++, С# и т.д.
Это означает, что нужно просто взаимодействовать с общедоступным интерфейсом объекта или системы. Не беспокойтесь и даже не ожидайте, как он делает то, что он делает внутренне. Не беспокойтесь о том, как это реализовано. В объектно-ориентированном коде, поэтому мы имеем общедоступные и частные методы/атрибуты. Мы намерены использовать общедоступные методы, потому что частные методы существуют только для внутреннего использования внутри класса. Они составляют реализацию класса и могут быть изменены по мере необходимости без изменения открытого интерфейса. Предположим, что в отношении функциональности метод класса будет выполнять ту же операцию с тем же ожидаемым результатом каждый раз, когда вы вызываете его с теми же параметрами. Это позволяет автору изменять работу класса, его реализацию, не нарушая, как люди взаимодействуют с ним.
И вы можете запрограммировать интерфейс, а не реализацию, не используя конструкцию интерфейса. Вы можете запрограммировать интерфейс, а не реализацию в C++, которая не имеет конструкцию интерфейса. Вы можете интегрировать две огромные корпоративные системы гораздо надежнее, если они взаимодействуют через публичные интерфейсы (контракты), а не вызывают методы для объектов, внутренних для систем. Ожидается, что интерфейсы всегда будут реагировать на один и тот же ожидаемый путь с учетом тех же входных параметров; если реализован интерфейс, а не реализация. Концепция работает во многих местах.
Встряхните мысль о том, что Java-интерфейсы имеют что-то, что связано с концепцией "Программа для интерфейса, а не реализации". Они могут помочь применить концепцию, но это не концепция.
Ответ 8
Похоже, вы понимаете, как работают интерфейсы, но не уверены в том, когда их использовать и какие преимущества они предлагают. Вот несколько примеров того, когда интерфейс будет иметь смысл:
// 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 и т.д.
Большинство надстроек и модулей плагинов работают с интерфейсами.
Еще одно популярное использование для шаблона репозитория. Скажем, я хочу загрузить список почтовых индексов из разных источников.
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());
}
}
Ответ 9
Это делает ваш код намного более расширяемым и более простым в обслуживании, когда у вас есть аналогичные классы. Я младший программист, поэтому я не эксперт, но я только что закончил проект, требующий чего-то подобного.
Я работаю на клиентском программном обеспечении, которое говорит на сервере, на котором работает медицинское устройство. Мы разрабатываем новую версию этого устройства, в которой есть несколько новых компонентов, которые клиент должен настраивать время от времени. Есть два типа новых компонентов, и они разные, но они также очень похожи. В принципе, мне пришлось создать две формы конфигурации, два списка классов, два из всего.
Я решил, что было бы лучше создать абстрактный базовый класс для каждого типа управления, который будет содержать почти всю реальную логику, а затем производные типы, чтобы позаботиться о различиях между этими двумя компонентами. Однако базовые классы не могли бы выполнять операции над этими компонентами, если бы мне приходилось беспокоиться о типах все время (ну, они могли бы иметь, но в каждом методе был бы оператор "if" ).
Я определил простой интерфейс для этих компонентов, и все базовые классы общаются с этим интерфейсом. Теперь, когда я что-то меняю, он почти "работает" повсюду, и у меня нет дублирования кода.
Ответ 10
Если вы программируете на Java, JDBC - хороший пример. JDBC определяет набор интерфейсов, но ничего не говорит о реализации. Ваши приложения могут быть написаны против этого набора интерфейсов. Теоретически вы выбираете драйвер JDBC, и ваше приложение будет работать. Если вы обнаружите там более быстрый или "лучший" или более дешевый драйвер JDBC или по какой-либо причине, вы можете снова теоретически переконфигурировать свой файл свойств и без каких-либо изменений в вашем приложении, ваше приложение все равно будет работать.
Ответ 11
Программирование на интерфейсы является удивительным, оно способствует свободному соединению. Как отметил @lassevk, использование инверсии управления - это большое использование.
Кроме того, ознакомьтесь с принципами SOLID. вот видео-сериал
Он проходит через жестко закодированный (сильно связанный пример), затем смотрит на интерфейсы, и, наконец, переходит к инструменту IoC/DI (NInject)
Ответ 12
Я покойный на этот вопрос, но я хочу упомянуть здесь, что строка "Программа для интерфейса, а не реализация" имела хорошее обсуждение в книге шаблонов планов GoF (Gang of Four).
Об этом говорилось на с. 18:
Программа для интерфейса, а не для реализации
Не объявлять переменные как экземпляры конкретных конкретных классов. Вместо этого зафиксируйте только интерфейс, определенный абстрактным классом. Вы увидите, что это общая тема шаблонов проектирования в этой книге.
и выше, это началось с:
Существует два преимущества манипулирования объектами исключительно с точки зрения интерфейса, определенного абстрактными классами:
- Клиенты не знают о конкретных типах объектов, которые они используют, пока объекты придерживаются интерфейса, ожидаемого клиентами.
- Клиенты не знают о классах, которые реализуют эти объекты. Клиенты знают только об абстрактном классе (ов), определяющем интерфейс.
Иными словами, не записывайте его в свои классы, чтобы у него был метод quack()
для уток, а затем метод bark()
для собак, потому что они слишком специфичны для конкретной реализации класса ( или подкласс). Вместо этого напишите метод, используя имена, которые являются достаточно общими для использования в базовом классе, например giveSound()
или move()
, чтобы их можно было использовать для уток, собак или даже автомобилей, а затем для клиента вашего классы могут просто сказать .giveSound()
, а не думать о том, следует ли использовать quack()
или bark()
или даже определить тип перед выдачей правильного сообщения, которое должно быть отправлено объекту.
Ответ 13
В дополнение к уже выбранному ответу (и различным информационным сообщениям здесь), я настоятельно рекомендую захватить копию Head First Design Patterns, Это очень простое чтение и ответит на ваш вопрос напрямую, объясните, почему это важно, и покажите вам много шаблонов программирования, которые вы можете использовать, чтобы использовать этот принцип (и другие).
Ответ 14
Много объяснений там, но сделать его еще проще.
Возьмите, например, List
. Можно реализовать список с помощью:
- внутренний массив
- Связанный список
- другая реализация
Создав интерфейс, скажем List
. Вы только кодируете определение List или что List
означает в действительности.
Вы можете использовать любой тип реализации внутри, скажем, реализацию array
. Но предположим, что вы хотите изменить реализацию по какой-то причине, скажем, ошибку или производительность. Тогда вам просто нужно изменить объявление List<String> ls = new ArrayList<String>()
на List<String> ls = new LinkedList<String>()
.
Нет, где еще в коде, вам придется что-то изменить; Потому что все остальное было построено на определении List
.
Ответ 15
Чтобы добавить к существующим сообщениям, иногда кодирование в интерфейсах помогает в больших проектах, когда разработчики работают на отдельных компонентах одновременно. Все, что вам нужно, это определить интерфейсы заранее и написать код, а другие разработчики пишут код для интерфейса, который вы реализуете.
Ответ 16
Это также хорошо для Unit Testing, вы можете вводить свои собственные классы (отвечающие требованиям интерфейса) в класс, который зависит от него.
Ответ 17
Представьте, что у вас есть продукт под названием "Zebra", который может быть расширен плагинами. Он находит плагины, ища библиотеки DLL в некоторых каталогах. Он загружает все эти DLL и использует отражение для поиска любых классов, которые реализуют IZebraPlugin
, а затем вызывает методы этого интерфейса для связи с плагинами.
Это делает его полностью независимым от какого-либо определенного класса плагинов - ему все равно, что представляют собой классы. Он заботится только о том, чтобы они соответствовали спецификации интерфейса.
Интерфейсы - это способ определения точек расширяемости, подобных этому. Код, который говорит с интерфейсом, более слабо связан - на самом деле он не связан вообще с каким-либо другим конкретным кодом. Он может взаимодействовать с плагинами, написанными годами позже людьми, которые никогда не встречались с оригинальным разработчиком.
Вместо этого вы можете использовать базовый класс с виртуальными функциями - все плагины будут получены из базового класса. Но это гораздо более ограничивает, потому что класс может иметь только один базовый класс, тогда как он может реализовать любое количество интерфейсов.
Ответ 18
Итак, чтобы это было правильно, преимущество интерфейса в том, что я могу отделить вызов метода от любого конкретного класса. Вместо этого создается экземпляр интерфейса, где реализация предоставляется из того класса, который я выбираю, который реализует этот интерфейс. Таким образом, у меня есть много классов, которые имеют сходную, но немного отличающуюся функциональность, а в некоторых случаях (случаи, связанные с намерением интерфейса) не заботятся о том, какой именно объект.
Например, я мог бы иметь интерфейс перемещения. Метод, который делает что-то "движение", и любой объект (Person, Car, Cat), который реализует интерфейс движения, может быть передан и передан для перемещения. Без метода, каждый из которых знает тип класса.
Ответ 19
Описание на С++.
Подумайте о интерфейсе как об общедоступных методах классов.
Затем вы можете создать шаблон, который "зависит" от этих общедоступных методов, чтобы выполнять его собственную функцию (он вызывает вызовы функций, определенные в открытом интерфейсе классов). Предположим, что этот шаблон является контейнером, таким как векторный класс, а интерфейс, от которого он зависит, является алгоритмом поиска.
Любой класс алгоритмов, который определяет функции/интерфейс Vector, вызывает вызовы, которые будут удовлетворять "контракту" (как объяснил кто-то в исходном ответе). Алгоритмы даже не должны быть одного базового класса; единственным требованием является то, что функции/методы, которые вектор зависит от (интерфейса), определены в вашем алгоритме.
Все дело в том, что вы можете предоставить любой другой алгоритм поиска/класс до тех пор, пока он предоставляет интерфейс, от которого зависит вектор (поиск в пузырьках, последовательный поиск, быстрый поиск).
Возможно, вам также захочется спроектировать другие контейнеры (списки, очереди), которые будут использовать тот же алгоритм поиска, что и Vector, благодаря тому, что они выполняют интерфейс/контракт, от которых зависят ваши алгоритмы поиска.
Это экономит время (повторное использование кода принципа ООП), так как вы можете написать алгоритм один раз, а не снова и снова, конкретный для каждого нового объекта, который вы создаете, без чрезмерного усложнения проблемы с переросшим деревом наследования.
Что касается "отсутствия" в отношении того, как все работает; (по крайней мере, на С++), так как это работает с базой данных Standard TEMPLATE Library.
Конечно, при использовании наследования и абстрактных классов изменяется методология программирования на интерфейс; но принцип тот же, ваши общедоступные функции/методы - это ваш интерфейс классов.
Это огромная тема и один из краеугольных принципов дизайна шаблонов.
Ответ 20
В 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.
Добавляемый интерфейс, реализуемый некоторыми классами, имеет такое же преимущество для любой ситуации, когда символы могут быть добавлены к экземпляру конкретного конкретного экземпляра объекта класса. Все эти конкретные классы реализуют интерфейс Appendable:
BufferedWriter, CharArrayWriter, CharBuffer, FileWriter, FilterWriter, LogStream, OutputStreamWriter, PipedWriter, PrintStream, PrintWriter, StringBuffer, StringBuilder, StringWriter, Writer
Ответ 21
Простыми словами...
Если я пишу новый класс Swimmer для добавления функции swim() и вам нужно использовать объект класса say Dog, и этот класс Dog реализует интерфейс Animal, объявляющий swim() [Чтобы лучше понять... вы можете нарисовать диаграмма о том, о чем я говорю]. На вершине иерархии (Animal) он очень абстрактный, а внизу (Dog) он очень конкретный. То, как я думаю о "программировании для интерфейсов", заключается в том, что, когда я пишу класс Swimmer, я хочу написать свой код против интерфейса, который до этой иерархии, который в этом случае является объектом Animal. Интерфейс свободен от деталей реализации и, таким образом, делает ваш код слабосвязанным. Детали реализации могут быть изменены со временем, однако это не повлияет на оставшийся код, поскольку все, с чем вы взаимодействуете, связано с интерфейсом, а не с реализацией. Вам все равно, что такое реализация... все, что вы знаете, это то, что будет класс, который будет реализовывать интерфейс.
Ответ 22
Рассказ: почтальона просят поехать домой домой и получить обложки (письма, документы, чеки, подарочные карты, приложения, любовью) с адресом, написанным на нем для доставки.
Предположим, что нет обложки и попросите человека повести домой домой и получить все вещи и доставить другому человеку почтальон может запутаться,
так лучше
оберните его крышкой (в нашей истории это интерфейс), тогда он будет хорошо выполнять свою работу.
Теперь почтальон должен получать и доставлять только обложки.. (он не беспокоился о том, что находится внутри обложки).
Создать тип interface
не актуального типа, но реализовать с фактическим типом.
Создать для интерфейса означает, что ваши компоненты получают Легко вписывается в остальную часть кода
Я приведу вам пример.
у вас есть интерфейс AirPlane, как показано ниже.
interface Airplane{
parkPlane();
servicePlane();
}
Предположим, что у вас есть методы в классе Controller Planes, например
parkPlane(Airplane plane)
и
servicePlane(Airplane plane)
реализованный в вашей программе. Он не будет BREAK вашего кода.
Я имею в виду, что он не должен меняться, если он принимает аргументы как 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.
вы также можете удалить экземпляры ранее созданных типов.
Ответ 23
Интерфейс похож на контракт, где вы хотите, чтобы ваш класс реализации реализовывал методы, записанные в контракте (интерфейсе). Поскольку Java не обеспечивает множественное наследование, "программирование для интерфейса" является хорошим способом достижения множественного наследования.
Если у вас есть класс A, который уже расширяет какой-то другой класс B, но вы хотите, чтобы этот класс A также следовал определенным рекомендациям или реализовывал определенный контракт, то вы можете сделать это с помощью стратегии "программирования для взаимодействия".
Ответ 24
Может быть полезно программировать интерфейсы, даже если мы не зависим от абстракций.
Программирование на интерфейсы заставляет нас использовать контекстно-зависимое подмножество объекта. Это помогает, потому что он:
- не позволяет нам делать контекстуально неприемлемые вещи, а
- позволяет нам безопасно изменять реализацию в будущем.
Например, рассмотрим класс 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()
. Даже если обе функции имеют тысячи строк кода, это простое редактирование будет работать, потому что мы знаем, что верно следующее:
- Функция
party
использует только Friend
подмножество Person
.
- Функция
workplace
использует только Employee
подмножество Person
.
- Класс
Dog
реализует интерфейсы Friend
и Employee
.
С другой стороны, если либо party
, либо workplace
должны были запрограммироваться против Person
, существует риск того, что оба имеют Person
-специфический код. Переход от Person
в Dog
потребовал бы, чтобы мы проверили код, чтобы исключить любой Person
-специфический код, который Dog
не поддерживает.
Моральный: программирование на интерфейсы помогает нашему кодексу вести себя надлежащим образом и быть готовым к изменениям. Он также подготавливает наш код к зависимости от абстракций, что приносит еще больше преимуществ.
Ответ 25
Q: -... "Вы можете использовать любой класс, который реализует интерфейс?"
A: - Да.
Q: -... "Когда вам это нужно?"
A: - Каждый раз, когда вам нужен класс (ы), который реализует интерфейс (ы).
Примечание: мы не смогли создать интерфейс, не реализованный классом - True.
- почему?
- потому что интерфейс имеет только прототипы методов, а не определения (просто имена функций, а не их логика).
AnIntf anInst = new Aclass();
//мы могли бы сделать это , только если Aclass реализует AnIntf.
//anInst будет иметь ссылку Aclass.
Примечание:
Теперь мы могли понять, что происходит, если 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.
Что мы имеем:
те же интерфейсные прототипы (имена функций в интерфейсе) и вызывают разные реализации.
Библиография:
Прототипы - википедия
Ответ 26
Также здесь я вижу много хороших и объяснительных ответов, поэтому хочу изложить свою точку зрения здесь, включая дополнительную информацию, которую я заметил при использовании этого метода.
Тестирование устройств
В течение последних двух лет я написал проект для хобби, и я не писал для него модульные тесты. После того, как я написал около 50 тыс. Строк, я узнал, что действительно нужно будет писать модульные тесты.
Я не использовал интерфейсы (или очень экономно)... и когда я сделал свой первый unit test, я узнал, что это было сложно. Почему?
Потому что мне приходилось делать много экземпляров классов, используемых для ввода в качестве переменных класса и/или параметров. Таким образом, тесты больше походят на интеграционные тесты (необходимо сделать полную "структуру" классов, поскольку все было связано друг с другом).
Страх перед интерфейсами
Поэтому я решил использовать интерфейсы. Мой страх состоял в том, что мне приходилось выполнять всю функциональность везде (во всех используемых классах) несколько раз. В некотором роде это верно, однако, используя наследование, его можно значительно уменьшить.
Комбинация интерфейсов и наследования
Я узнал, что комбинация очень хороша для использования. Я даю очень простой пример.
public interface IPricable
{
int Price { get; }
}
public interface ICar : IPricable
public abstract class Article
{
public int Price { get { return ... } }
}
public class Car : Article, ICar
{
// Price does not need to be defined here
}
Таким образом, копирование кода не требуется, но при этом полезно использовать автомобиль в качестве интерфейса (ICar).
Ответ 27
Сначала начнем с некоторых определений:
Интерфейс n. Набор всех сигнатур, определяемых объектными операциями, называется интерфейсом к объекту
Тип n. Конкретный интерфейс
Простым примером интерфейса, как определено выше, были бы все методы объектов PDO, такие как query()
, commit()
, close()
и т.д., в целом, а не отдельно. Эти методы, то есть его интерфейс, определяют полный набор сообщений, запросы, которые могут быть отправлены объекту.
A тип, как определено выше, является конкретным интерфейсом. Я использую интерфейс с готовой формой, чтобы продемонстрировать: draw()
, getArea()
, getPerimeter()
и т.д.
Если объект имеет тип базы данных, мы подразумеваем, что он принимает сообщения/запросы интерфейса базы данных, query()
, commit()
и т.д. Объекты могут быть разных типов. Вы можете иметь объект базы данных типа формы, если он реализует свой интерфейс, и в этом случае это будет подтипа.
Многие объекты могут быть разных интерфейсов/типов и реализовать этот интерфейс по-разному. Это позволяет нам заменять объекты, позволяя нам выбирать, какой из них использовать. Также известен как полиморфизм.
Клиент будет знать интерфейс, а не реализацию.
Таким образом, по сути программирование на интерфейс потребует создания некоторого типа абстрактного класса, такого как Shape
с указанным интерфейсом, т.е. draw()
, getCoordinates()
, getArea()
и т.д. И тогда реализуются различные конкретные классы такие интерфейсы, как класс Circle, класс Square, класс Triangle. Следовательно, программа для интерфейса не является реализацией.
Ответ 28
Программа для интерфейса позволяет легко изменять реализацию контракта, определяемого интерфейсом. Он позволяет свободно связываться между контрактом и конкретными реализациями.
IInterface classRef = new ObjectWhatever()
Можно использовать любой класс, который реализует IInterface? Когда вам это нужно?
Взгляните на этот вопрос SE на хорошем примере.
Почему должен быть предпочтительным интерфейс для класса Java?
использует ли производительность интерфейса?
если да, то сколько?
Да. В субсекундах он будет иметь незначительные эксплуатационные издержки. Но если ваше приложение требует динамического изменения интерфейса, не беспокойтесь о влиянии производительности.
как вы можете избежать этого без необходимости поддерживать два бита кода?
Не пытайтесь избежать нескольких реализаций интерфейса, если ваше приложение им необходимо. В отсутствие жесткой связи интерфейса с одной конкретной реализацией вам может потребоваться развернуть патч, чтобы изменить одну реализацию на другую реализацию.
Один хороший вариант использования: реализация шаблона стратегии:
Реальный мир Пример шаблона стратегии
Ответ 29
Я не сохраняю interface
- самое важное в языке: он чаще всего использует наследование класса. Но в любом случае они важны!
Например (это код Java
, но он может просто адаптироваться к C#
или многим другим языкам):
interface Convertable<T> {
T convert();
}
public class NumerableText implements Convertable<Integer> {
private String text = "";
public NumerableText() { }
public NumerableText(String text) {
this.text = text;
}
public String getText() {
return this.text;
}
public void setText(String text) {
this.text = text;
}
public Integer convert() {
return this.text.hashCode();
}
}
public class NumerableTextArray implements Convertable<Integer> {
private String[] textArray = "";
public NumerableTextArray() { }
public NumerableTextArray(String[] textArray) {
this.textArray = textArray;
}
public String[] getTextArray() {
return this.textArray;
}
public void setTextArray(String[] text) {
this.textArray = textArray;
}
public Integer convert() {
Integer value = 0;
for (String text : textArray)
value += text.hashCode();
return value;
}
}
public class Foo {
public static void main() {
Convertable<Integer> num1 = new NumerableText("hello");
Convertable<Integer> num2 = new NumerableTextArray(new String[] { "test n°1", "test n°2" });
System.out.println(String.valueOf(num1.convert()));
System.out.println(String.valueOf(num2.convert()));
//Here are you two numbers generated from two classes of different type, but both with the method convert(), which allows you to get that number.
}
}
Ответ 30
"Программа для интерфейса" означает, что вы не предоставите жесткий код правильно, то есть ваш код должен быть расширен, не нарушая предыдущую функциональность. Просто расширения, а не редактирование предыдущего кода.