Я понимаю, что они заставляют вас внедрять методы и тому подобное, но я не могу понять, почему вы хотите их использовать. Может ли кто-нибудь дать мне хороший пример или объяснение, почему я хотел бы это реализовать.
Зачем мне использовать интерфейсы?
Ответы (19)
Один конкретный пример: интерфейсы - хороший способ указать контракт, которому должен соответствовать чужой код.
Если я пишу библиотеку кода, я могу написать код, который действителен для объектов с определенным набором поведения. Лучшее решение - указать это поведение в интерфейсе (без реализации, просто описание), а затем использовать ссылки на объекты, реализующие этот интерфейс, в коде моей библиотеки.
Затем может прийти любой случайный человек, создать класс, реализующий этот интерфейс, создать экземпляр объекта этого класса и передать его в код моей библиотеки и ожидать, что он заработает. Примечание: конечно, можно строго реализовать интерфейс, игнорируя намерение интерфейса, поэтому простая реализация интерфейса не гарантирует, что все будет работать. Глупый всегда находит выход! :-)
Другой конкретный пример: две команды, работающие над разными компонентами, должны взаимодействовать. Если две команды сядут в первый день и согласуют набор интерфейсов, то они могут пойти разными путями и реализовать свои компоненты вокруг этих интерфейсов. Команда A может создавать тестовые жгуты, которые имитируют компонент команды B для тестирования, и наоборот. Параллельная разработка и меньше ошибок.
Ключевым моментом является то, что интерфейсы обеспечивают уровень абстракции, так что вы можете писать код, игнорирующий ненужные детали.
Канонический пример, который используется в большинстве учебников, - это процедуры сортировки. Вы можете сортировать любой класс объектов, если у вас есть способ сравнить любые два объекта. Таким образом, вы можете сделать любой класс сортируемым, реализовав интерфейс IComparable, который заставляет вас реализовать метод для сравнения двух экземпляров. Все процедуры сортировки написаны для обработки ссылок на объекты IComparable, поэтому, как только вы реализуете IComparable, вы можете использовать любую из этих процедур сортировки для коллекций объектов вашего класса.
Типичный пример - архитектура плагина. Разработчик A пишет основное приложение и хочет убедиться, что все плагины, написанные разработчиком B, C и D, соответствуют тому, что его приложение ожидает от них.
Интерфейсы определяют контракты, и это ключевое слово.
Вы используете интерфейс, когда вам нужно определить контракт в вашей программе, но вы действительно не заботитесь об остальных свойствах класса, который выполняет этот контракт, пока он это делает.
Итак, давайте посмотрим на пример. Предположим, у вас есть метод, позволяющий сортировать список. Во-первых ... что за список? Вам действительно важно, какие элементы он содержит, чтобы отсортировать список? Ваш ответ должен быть отрицательным ... В .NET (например) у вас есть интерфейс с именем IList, который определяет операции, которые ДОЛЖЕН поддерживать список, поэтому вам не нужны фактические детали, скрытые под поверхностью.
Вернемся к примеру, вы на самом деле не знаете класс объектов в списке ... вас это не волнует. Если вы можете просто сравнить объекты, вы можете отсортировать их. Итак, вы объявляете договор:
interface IComparable
{
// Return -1 if this is less than CompareWith
// Return 0 if object are equal
// Return 1 if CompareWith is less than this
int Compare(object CompareWith);
}
этот контракт указывает, что метод, который принимает объект и возвращает int, должен быть реализован, чтобы быть сопоставимым. Теперь вы определили контракт, и на данный момент вас интересует не сам объект, а контракт, поэтому вы можете просто сделать:
IComparable comp1 = list.GetItem(i) as IComparable;
if (comp1.Compare(list.GetItem(i+1)) < 0)
swapItem(list,i, i+1)
PS: Я знаю, что примеры немного наивны, но это примеры ...
IComparable не указано, что вы возвращаете 1, 0 и -1, но вы должны возвращать >0, 0 и <0. Это позволяет оптимизировать, например, сортировку по int, просто выполняя return this.intNum - CompareWith.intNum. Вам все равно, насколько он больше или меньше 0, а только тот факт, что он больше или меньше.
- person Scott Chamberlain; 28.11.2013
Самый простой способ понять интерфейсы - это то, что они позволяют различным объектам предоставлять ОБЩИЕ функциональные возможности. Это позволяет программисту писать намного более простой и короткий код, который программирует интерфейс, и тогда, пока объекты реализуют этот интерфейс, он будет работать.
Пример 1: Существует много разных поставщиков баз данных, MySQL, MSSQL, Oracle и т. д. Однако все объекты базы данных могут выполнять одни и те же действия, поэтому вы найдете множество интерфейсов для объектов базы данных. Если объект реализует IDBConnection, он предоставляет методы Open () и Close (). Поэтому, если я хочу, чтобы моя программа не зависела от поставщика базы данных, я программирую для интерфейса, а не для конкретных поставщиков.
IDbConnection connection = GetDatabaseConnectionFromConfig()
connection.Open()
// do stuff
connection.Close()
Смотрите, запрограммировав интерфейс (IDbconnection), теперь я могу заменить любого поставщика данных в моей конфигурации, но мой код остается неизменным. Эта гибкость может быть чрезвычайно полезной и простой в обслуживании. Обратной стороной этого является то, что я могу выполнять только «общие» операции с базой данных и не могу полностью использовать силу, которую предлагает каждый конкретный поставщик, так как со всем в программировании у вас есть компромисс, и вы должны определить, какой сценарий принесет вам наибольшую пользу.
Пример 2. Если вы заметили, что почти все коллекции реализуют этот интерфейс под названием IEnumerable. IEnumerable возвращает IEnumerator, у которого есть MoveNext (), Current и Reset (). Это позволяет C # легко перемещаться по вашей коллекции. Причина, по которой он может это сделать, заключается в том, что он предоставляет интерфейс IEnumerable, который ЗНАЕТ, что объект предоставляет методы, необходимые для его выполнения. Это делает две вещи. 1) циклы foreach теперь будут знать, как перечислить коллекцию, и 2) теперь вы можете применять мощные выражения LINQ к своей коллекции. И снова причина, по которой интерфейсы здесь так полезны, заключается в том, что во всех коллекциях есть что-то в ОБЩЕМ, их можно перемещать. Каждую коллекцию можно перемещать по-разному (связанный список или массив), но прелесть интерфейсов заключается в том, что реализация скрыта и не имеет отношения к потребителю интерфейса. MoveNext () дает вам следующий элемент в коллекции, неважно, КАК он это делает. Довольно мило, да?
Пример 3: когда вы разрабатываете свой собственный интерфейс, вам просто нужно задать себе один вопрос. Что у этих вещей общего? Как только вы найдете все, что является общим для объектов, вы абстрагируете эти свойства / методы в интерфейс, чтобы каждый объект мог наследовать от него. Затем вы можете программировать против нескольких объектов, используя один интерфейс.
И, конечно же, я должен привести мой любимый пример полиморфизма C ++ - пример с животными. Все животные обладают определенными характеристиками. Допустим, они могут двигаться, говорить, и у всех есть имя. Поскольку я только что определил, что общего у всех моих животных, я могу абстрагировать эти качества в интерфейсе IAnimal. Затем я создаю объект Bear, объект Owl и объект Snake, реализующие этот интерфейс. Причина, по которой вы можете хранить вместе разные объекты, реализующие один и тот же интерфейс, заключается в том, что интерфейсы представляют собой репликацию IS-A. Медведь - это животное, сова - это животное, поэтому я могу собрать их всех как животных.
var animals = new IAnimal[] = {new Bear(), new Owl(), new Snake()} // here I can collect different objects in a single collection because they inherit from the same interface
foreach (IAnimal animal in animals)
{
Console.WriteLine(animal.Name)
animal.Speak() // a bear growls, a owl hoots, and a snake hisses
animal.Move() // bear runs, owl flys, snake slithers
}
Вы можете видеть, что, хотя эти животные выполняют каждое действие по-разному, я могу программировать против них всех в одной унифицированной модели, и это лишь одно из многих преимуществ интерфейсов.
Итак, опять же, самое важное в интерфейсах - это то, что у объектов общего, чтобы вы могли программировать для РАЗНЫХ объектов ОДИНАКОВЫМ способом. Экономит время, создает более гибкие приложения, скрывает сложность / реализацию, моделирует реальные объекты / ситуации и многие другие преимущества.
Надеюсь это поможет.
Педали на автомобиле реализуют интерфейс. Я из США, где мы едем по правой стороне дороги. Наши рули находятся с левой стороны машины. Педали механической коробки передач слева направо: сцепление -> тормоз -> акселератор. Когда я поехал в Ирландию, движение было задним. Рулевые колеса автомобилей находятся справа, и они движутся по левой стороне дороги ... но педали, э-э, педали ... они реализовали тот же интерфейс ... все три педали были в одном порядке ... так что даже если класс был другим и сеть, в которой работал класс, была другой, мне все равно было комфортно с интерфейсом педали. Мой мозг был в состоянии задействовать мои мускулы в этой машине, как и в любой другой машине.
Подумайте о многочисленных непрограммируемых интерфейсах, без которых мы не можем жить. Тогда ответьте на свой вопрос.
Интерфейсы абсолютно необходимы в объектно-ориентированной системе, которая предполагает эффективное использование полиморфизма.
Классическим примером может служить IVehicle, у которого есть метод Move (). У вас могут быть классы Car, Bike и Tank, реализующие IVehicle. Все они могут использовать Move (), и вы можете написать код, который не заботится о том, с каким транспортным средством он имеет дело, просто чтобы он мог Move ().
void MoveAVehicle(IVehicle vehicle)
{
vehicle.Move();
}
Интерфейсы - это форма полиморфизма. Пример:
Предположим, вы хотите написать код регистрации. Ведение журнала будет куда-то отправлено (возможно, в файл или последовательный порт на устройстве, на котором работает основной код, или в сокет, или выброшено, как / dev / null). Вы не знаете где: пользователь вашего кода регистрации должен быть свободен, чтобы определить это. Фактически, вашему коду ведения журнала все равно. Ему просто нужно то, во что он может записывать байты.
Итак, вы изобретаете интерфейс, который называется «то, на что можно записывать байты». Коду регистрации предоставляется экземпляр этого интерфейса (возможно, во время выполнения, возможно, он настроен во время компиляции. Это все еще полиморфизм, только разные виды). Вы пишете один или несколько классов, реализующих интерфейс, и можете легко изменить место ведения журнала, просто изменив, какой из них будет использовать код ведения журнала. Кто-то другой может изменить место ведения журнала, написав собственные реализации интерфейса без изменения вашего кода. По сути, это то, что составляет полиморфизм - знание об объекте ровно столько, чтобы использовать его определенным образом, при этом позволяя ему варьироваться во всех отношениях, о которых вам не нужно знать. Интерфейс описывает то, что вам нужно знать.
Дескрипторы файлов C - это, по сути, интерфейс, «что-то, что я могу читать и / или записывать байты из и / или в», и почти каждый типизированный язык имеет такие интерфейсы, скрытые в его стандартных библиотеках: потоки или что-то еще. Нетипизированные языки обычно имеют неформальные типы (возможно, называемые контрактами), которые представляют потоки. Таким образом, на практике вам почти никогда не придется самостоятельно изобретать этот конкретный интерфейс: вы используете то, что дает вам язык.
Ведение журнала и потоки - это лишь один пример: интерфейсы возникают всякий раз, когда вы можете абстрактно описать, что объект должен делать, но не хотите связывать его с конкретной реализацией / классом / чем-то еще.
Для этого есть ряд причин. Когда вы используете интерфейс, вы готовы в будущем, когда вам понадобится рефакторинг / переписать код. Вы также можете предоставить своего рода стандартизированный API для простых операций.
Например, если вы хотите написать алгоритм сортировки, такой как быстрая сортировка, все, что вам нужно для сортировки любого списка объектов, - это то, что вы можете успешно сравнить два объекта. Если вы создаете интерфейс, скажем, ISortable, то любой, кто создает объекты, может реализовать интерфейс ISortable и использовать ваш код сортировки.
Если вы пишете код, который использует хранилище базы данных, и вы пишете в интерфейс хранилища, вы можете заменить этот код в дальнейшем.
Интерфейсы поощряют более слабое связывание вашего кода, чтобы вы могли иметь большую гибкость.
Представьте себе следующий базовый интерфейс, который определяет базовый механизм CRUD:
interface Storable {
function create($data);
function read($id);
function update($data, $id);
function delete($id);
}
Из этого интерфейса вы можете сказать, что любой объект, который его реализует, должен иметь функции для создания, чтения, обновления и удаления данных. Это может быть соединение с базой данных, средство чтения файлов CSV и средство чтения файлов XML или любой другой механизм, который может захотеть использовать операции CRUD.
Таким образом, теперь у вас может быть что-то вроде следующего:
class Logger {
Storable storage;
function Logger(Storable storage) {
this.storage = storage;
}
function writeLogEntry() {
this.storage.create("I am a log entry");
}
}
Этот регистратор не заботится о том, передаете ли вы соединение с базой данных или что-то, что управляет файлами на диске. Все, что ему нужно знать, это то, что он может вызвать для него create (), и он будет работать, как ожидалось.
В связи с этим возникает следующий вопрос: если базы данных, файлы CSV и т. Д. Могут хранить данные, разве они не должны быть унаследованы от общего объекта Storable и, таким образом, избавиться от необходимости в интерфейсах? Ответ на этот вопрос - нет ... не каждое соединение с базой данных может реализовывать операции CRUD, и то же самое относится к каждому читателю файлов.
Интерфейсы определяют, на что способен объект и как вам нужно его использовать ... а не на то, что это такое!
В статье в своем блоге я кратко описываю три назначения интерфейсов.
Интерфейсы могут иметь разное назначение:
- Предлагайте разные реализации для одной и той же цели. Типичным примером является список, который может иметь разные реализации для разных вариантов использования производительности (LinkedList, ArrayList и т. Д.).
- Разрешить изменение критериев. Например, функция сортировки может принимать интерфейс Comparable для предоставления любого вида критериев сортировки на основе того же алгоритма.
- Скрыть детали реализации. Это также облегчает пользователю чтение комментариев, поскольку в теле интерфейса есть только методы, поля и комментарии, а не длинные фрагменты кода, которые нужно пропускать.
Вот полный текст статьи: http://weblogs.manas.com.ar/ary/2007/11/
Лучший код Java, который я когда-либо видел, определял почти все ссылки на объекты как экземпляры интерфейсов вместо экземпляров классов. Это явный признак качественного кода, разработанного для гибкости и изменений.
Как вы заметили, интерфейсы хороши, когда вы хотите заставить кого-то сделать это в определенном формате.
Интерфейсы хороши, когда данные не в определенном формате могут означать опасные предположения в вашем коде.
Например, сейчас я пишу приложение, которое преобразует данные из одного формата в другой. Я хочу заставить их разместить эти поля, чтобы я знал, что они будут существовать и иметь больше шансов на правильную реализацию. Меня не волнует, выйдет ли другая версия, и она не компилируется для них, потому что в любом случае более вероятно, что данные потребуются.
Из-за этого интерфейсы используются редко, поскольку обычно вы можете делать предположения или на самом деле не требовать данных для того, чтобы делать то, что вам нужно.
Интерфейс определяет просто интерфейс. Позже вы можете определить метод (в других классах), который принимает интерфейсы как параметры (или, точнее, объект, реализующий этот интерфейс). Таким образом, ваш метод может работать с большим количеством объектов, единственная общая черта которых состоит в том, что они реализуют этот интерфейс.
Во-первых, они предоставляют дополнительный уровень абстракции. Вы можете сказать: «Для этой функции этот параметр должен быть объектом, который имеет эти методы с этими параметрами». И вы, вероятно, захотите также установить значение этих методов в несколько абстрактных терминах, но позволяя вам рассуждать о коде. На языках с утиным типом вы получаете это бесплатно. Нет необходимости в явных синтаксических «интерфейсах». Тем не менее, вы, вероятно, все еще создаете набор концептуальных интерфейсов, что-то вроде контрактов (как в Design by Contract).
Более того, интерфейсы иногда используются для менее «чистых» целей. В Java их можно использовать для имитации множественного наследования. В C ++ их можно использовать для сокращения времени компиляции.
В общем, они уменьшают взаимосвязь в вашем коде. Это хорошая вещь.
Ваш код также может быть проще протестировать таким образом.
Допустим, вы хотите отслеживать коллекцию вещей. Указанные коллекции должны поддерживать множество вещей, например, добавление и удаление элементов, а также проверку наличия элемента в коллекции.
Затем вы можете указать интерфейс ICollection с помощью методов add (), remove () и contains ().
Код, которому не нужно знать, какая коллекция (список, массив, хеш-таблица, красно-черное дерево и т. Д.) Может принимать объекты, реализующие интерфейс, и работать с ними, не зная их фактического типа.
В .Net я создаю базовые классы и наследую от них, когда классы как-то связаны. Например, базовый класс Person может быть унаследован Employee и Customer. Человек может иметь общие свойства, такие как поля адреса, имя, телефон и т. Д. Сотрудник может иметь собственное имущество отдела. Заказчик имеет другие эксклюзивные свойства.
Поскольку класс может наследовать только от одного другого класса в .Net, я использую интерфейсы для дополнительных общих функций. Иногда интерфейсы разделяются классами, которые в остальном не связаны. Использование интерфейса создает контракт, который, как известно разработчикам, используется всеми другими классами, реализующими его. Я также заставляю эти классы реализовывать все свои члены.
В C # интерфейсы также чрезвычайно полезны для разрешения полиморфизма для классов, которые не используют одни и те же базовые классы. Это означает, что поскольку у нас не может быть множественного наследования, вы можете использовать интерфейсы, чтобы разрешить использование разных типов. Это также способ предоставить вам доступ к закрытым членам для использования без отражения (явная реализация), поэтому это может быть хорошим способом реализации функциональности, сохраняя при этом вашу объектную модель чистой.
Например:
public interface IExample
{
void Foo();
}
public class Example : IExample
{
// explicit implementation syntax
void IExample.Foo() { ... }
}
/* Usage */
Example e = new Example();
e.Foo(); // error, Foo does not exist
((IExample)e).Foo(); // success
Я думаю, вам нужно хорошо разбираться в шаблонах проектирования, чтобы увидеть их силу.
Ознакомьтесь с шаблонами проектирования Head First