Почему я не могу понять интерфейсы?

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

Я говорю об интерфейсах в контексте интерфейсов и абстрактных классов.


person Justin Obney    schedule 23.09.2008    source источник
comment
Не могли бы вы уточнить, имеете ли вы в виду API, графический интерфейс или о каком интерфейсе вы говорите?   -  person devinmoore    schedule 23.09.2008
comment
Заголовок следует писать обычным регистром. Интерфейсы ..   -  person Jacob T. Nielsen    schedule 23.09.2008
comment
Я говорю об интерфейсах в контексте интерфейсов и абстрактных классов.   -  person Justin Obney    schedule 23.09.2008
comment
Хороший вопрос, судя по количеству людей, пытающихся ответить на него по-своему, довольно ясно, что это запутанная тема.   -  person terjetyl    schedule 05.09.2010


Ответы (26)


Интерфейсы позволяют вам программировать по «описанию», а не по типу, что позволяет более свободно связывать элементы вашего программного обеспечения.

Подумайте об этом так: вы хотите поделиться данными с кем-то в кубе рядом с вами, поэтому вы вытаскиваете флешку и копируете / вставляете. Вы идете по соседству, и парень спрашивает: "Это USB?" а вы говорите да - все готово. Не имеет значения ни размер флешки, ни производитель - важно только то, что это USB.

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

public void Paint(Car car, System.Drawing.Color color)...

Это будет работать, пока ваш клиент не скажет: «Теперь я хочу покрасить грузовики», чтобы вы могли сделать следующее:

public void Paint (Vehicle vehicle, System.Drawing.Color color)...

это расширило бы ваше приложение ... пока ваш клиент не сказал: «Теперь я хочу покрасить дома!» То, что вы могли бы сделать с самого начала, - это создать интерфейс:

public interface IPaintable{
   void Paint(System.Drawing.Color color);
}

... и передал это в свой распорядок:

public void Paint(IPaintable item, System.Drawing.Color color){
   item.Paint(color);
}

Надеюсь, это имеет смысл - это довольно упрощенное объяснение, но мы надеемся, что оно дойдет до сути.

person Community    schedule 23.09.2008

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

Допустим, у вас есть пара классов под названием «Собака», «Кошка» и «Мышь». Каждый из этих классов является Pet, и теоретически вы можете унаследовать их все от другого класса, называемого Pet, но вот в чем проблема. Домашние животные сами по себе ничего не делают. Вы не можете пойти в магазин и купить питомца. Вы можете пойти и купить собаку или кошку, но домашнее животное - это абстрактное понятие, а не конкретное.

Итак, вы знаете, что домашние животные могут делать определенные вещи. Они могут спать, есть и т. Д. Итак, вы определяете интерфейс с именем IPet, и он выглядит примерно так (синтаксис C #)

public interface IPet
{
    void Eat(object food);
    void Sleep(int duration);
}

Каждый из ваших классов Dog, Cat и Mouse реализует IPet.

public class Dog : IPet

Итак, теперь у каждого из этих классов должна быть собственная реализация Eat and Sleep. Ура у вас есть контракт ... А теперь в чем смысл.

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

public class PetStore
{
     public static IPet GetRandomPet()
     {    
          //Code to return a random Dog, Cat, or Mouse
     } 
}

IPet myNewRandomPet = PetStore.GetRandomPet();
myNewRandomPet.Sleep(10);

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

Таким образом, этот ответ, возможно, вообще не был полезным, но общая идея заключается в том, что интерфейсы позволяют вам делать изящные вещи, такие как Injection Dependency и Inversion of Control, где вы можете получить объект, иметь четко определенный список вещей, которые объект может делать, никогда ДЕЙСТВИТЕЛЬНО зная, каков конкретный тип этого объекта.

person JoshReedSchramm    schedule 23.09.2008

Самый простой ответ - интерфейсы определяют, что может делать ваш класс. Это «контракт», в котором говорится, что ваш класс сможет выполнить это действие.

Public Interface IRollOver
    Sub RollOver()
End Interface

Public Class Dog Implements IRollOver
    Public Sub RollOver() Implements IRollOver.RollOver
        Console.WriteLine("Rolling Over!")
    End Sub
End Class

Public Sub Main()
    Dim d as New Dog()
    Dim ro as IRollOver = TryCast(d, IRollOver)
    If ro isNot Nothing Then
        ro.RollOver()
    End If
End Sub

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

person Bob King    schedule 23.09.2008
comment
Я думаю, что собаки и кошки - это де-факто демонстрация полиморфизма. - person Bob King; 23.09.2008
comment
Нит: это не контракт. Он просто требует, чтобы определенные методы и другие с определенными именами и подписями присутствовали в классе реализации. Однако он не делает никаких утверждений относительно того, что делают эти методы. - person Wedge; 24.09.2008
comment
Клин, это все еще контракт или, по крайней мере, его часть. - person Derek Park; 24.09.2008
comment
@Wedge - вы гарантируете, что ваш класс может обрабатывать эти методы и предоставлять эти свойства. Это все, что нужно сделать интерфейсу. Если реализация RollOver () Cat заставляет его прыгать на диван, это не ответственность вызывающего абонента. - person Bob King; 24.09.2008

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

person Alexander    schedule 23.09.2008
comment
это может быть не договор между производителями автомобилей и водителями автомобилей .. это скорее договор между производителями автомобилей и стандартами ISO. - person Dhananjay; 24.03.2012

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

С точки зрения .NET

  • Определение интерфейса - это список операций и / или свойств.
  • Методы интерфейса всегда общедоступны.
  • Сам интерфейс не обязательно должен быть публичным.

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

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

Пример более конкретный:

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

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

// First pass - not maintainable
void SubmitToWorkflow(object o, User u)
{
  if (o is StreetMap)
  {
     var map = (StreetMap)o;
     map.LastUpdated = DateTime.UtcNow;
     map.UpdatedByUser = u.UserID;
  }
  else if (o is Person)
  {
     var person = (Person)o;
     person.LastUpdated = DateTime.Now; // Whoops .. should be UtcNow
     person.UpdatedByUser = u.UserID;
  }
  // Whoa - very unmaintainable.

В приведенном выше коде SubmitToWorkflow() должен знать о каждом объекте. Кроме того, код представляет собой беспорядок с одним массивным переключателем if / else /, нарушающим не повторять себя (DRY) и требует, чтобы разработчики запоминали изменения копирования / вставки каждый раз, когда в систему добавляется новый объект.

// Second pass - brittle
void SubmitToWorkflow(object o, User u)
{
  if (o is DTOBase)
  {
     DTOBase dto = (DTOBase)o;
     dto.LastUpdated = DateTime.UtcNow;
     dto.UpdatedByUser = u.UserID;
  }

Немного лучше, но все равно хрупкий. Если мы хотим отправить другие типы объектов, нам все равно нужно больше операторов case. и Т. Д.

// Third pass pass - also brittle
void SubmitToWorkflow(DTOBase dto, User u)
{
  dto.LastUpdated = DateTime.UtcNow;
  dto.UpdatedByUser = u.UserID;

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

Интерфейсы, чем они могут помочь?

Если мы определим очень простой интерфейс:

public interface IUpdateTracked
{
  DateTime LastUpdated { get; set; }
  int UpdatedByUser { get; set; }
}

Любой класс, которому требуется автоматическое отслеживание обновлений, может реализовать интерфейс.

public class SomeDTO : IUpdateTracked
{
  // IUpdateTracked implementation as well as other methods for SomeDTO
}

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

void SubmitToWorkflow(object o, User u)
{
  IUpdateTracked updateTracked = o as IUpdateTracked;
  if (updateTracked != null)
  {
     updateTracked.LastUpdated = DateTime.UtcNow;
     updateTracked.UpdatedByUser = u.UserID;
  }
  // ...
  • Мы можем отметить, что вариант void SubmitToWorkflow(IUpdateTracked updateTracked, User u) гарантирует безопасность типов, однако в данных обстоятельствах он не кажется таким актуальным.

В некотором производственном коде, который мы используем, у нас есть генерация кода для создания этих классов DTO из определения базы данных. Единственное, что делает разработчик, - это правильно создать имя поля и украсить класс интерфейсом. Пока свойства называются LastUpdated и UpdatedByUser, это просто работает.

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

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

// Using an interface to bridge properties
public class LegacyDTO : IUpdateTracked
{
    public int LegacyUserID { get; set; }
    public DateTime LastSaved { get; set; }

    public int UpdatedByUser
    {
        get { return LegacyUserID; }
        set { LegacyUserID = value; }
    }
    public DateTime LastUpdated
    {
        get { return LastSaved; }
        set { LastSaved = value; }
    }
}

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

Это означает, что свойства IUpdateTracked будут видны только тогда, когда мы используем ссылку на IUpdateTracked. Обратите внимание, что в объявлении нет модификатора public, а объявление включает имя интерфейса.

// Explicit implementation of an interface
public class YetAnotherObject : IUpdatable
{
    int IUpdatable.UpdatedByUser
    { ... }
    DateTime IUpdatable.LastUpdated
    { ... }

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

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

Как я упоминал ранее и другими респондентами, вы можете создавать методы, которые принимают и / или возвращают ссылки на интерфейс, а не на конкретную ссылку на класс. Если бы мне нужно было найти дубликаты в списке, я мог бы написать метод, который принимает и возвращает IList (интерфейс, определяющий операции, которые работают со списками), и я не ограничен конкретным классом коллекции.

// Decouples the caller and the code as both
// operate only on IList, and are free to swap
// out the concrete collection.
public IList<T> FindDuplicates( IList<T> list )
{
    var duplicates = new List<T>()
    // TODO - write some code to detect duplicate items
    return duplicates;
}

Предупреждение о версиях

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

См. эту публикацию Haacked за хорошее обсуждение.

Интерфейсы против абстрактных (базовых) классов

Абстрактные классы могут обеспечивать реализацию, тогда как интерфейсы - нет. Абстрактные классы в некотором смысле более гибкие в аспекте управления версиями, если вы следуете некоторым рекомендациям, таким как шаблон NVPI (невиртуальный открытый интерфейс).

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

Внедрение зависимости

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

class AnnualRaiseAdjuster
   : ISalaryAdjuster
{
   AnnualRaiseAdjuster(IPayGradeDetermination payGradeDetermination) { ...  }

   void AdjustSalary(Staff s)
   {
      var payGrade = payGradeDetermination.Determine(s);
      s.Salary = s.Salary * 1.01 + payGrade.Bonus;
   }
}

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

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

person Community    schedule 23.09.2008

Это довольно «длинная» тема, но позвольте мне попытаться выразить ее проще.

Интерфейс - как «они его называют» - это Контракт. Но забудьте об этом слове.

Лучший способ понять их - использовать какой-то пример псевдокода. Вот как я их понимал давным-давно.

Предположим, у вас есть приложение, которое обрабатывает сообщения. Сообщение содержит что-то вроде темы, текста и т. Д.

Итак, вы пишете свой MessageController для чтения базы данных и извлечения сообщений. Очень приятно, пока вы вдруг не услышите, что скоро будут реализованы и факсы. Итак, теперь вам нужно будет читать «Факсы» и обрабатывать их как сообщения!

Это может легко превратиться в код Спагетти. То, что вы делаете, вместо того, чтобы иметь MessageController, а не только «Сообщения», вы позволяете ему работать с интерфейсом под названием IMessage (I - это обычное использование, но не обязательно).

Ваш интерфейс IMessage содержит некоторые основные данные, которые необходимы, чтобы убедиться, что вы можете обрабатывать Сообщение как таковое.

Поэтому, когда вы создаете свои классы EMail, Fax, PhoneCall, вы заставляете их реализовать интерфейс под названием IMessage.

Итак, в вашем MessageController вы можете иметь такой метод:

private void ProcessMessage(IMessage oneMessage)
{
    DoSomething();
}

Если бы вы не использовали интерфейсы, вам потребовалось бы:

private void ProcessEmail(Email someEmail);
private void ProcessFax(Fax someFax);
etc.

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

Почему и как?

Поскольку интерфейс представляет собой контракт, который определяет некоторые вещи, которые вы должны придерживаться (или реализовывать), чтобы иметь возможность его использовать. Думайте об этом как о значке. Если ваш объект «Факс» не имеет интерфейса IMessage, тогда ваш метод ProcessMessage не сможет работать с этим, он даст вам недопустимый тип, потому что вы передаете факс методу, ожидающему IMessage. объект.

Вы понимаете суть?

Думайте об интерфейсе как о «подмножестве» методов и свойств, которые вам будут доступны, несмотря на реальный тип объекта. Если исходный объект (факс, электронная почта, телефонный звонок и т. Д.) Реализует этот интерфейс, вы можете безопасно передать его через методы, которым нужен этот интерфейс.

Там спрятано еще больше магии, вы можете вернуть интерфейсы к их исходным объектам:

Факс myFax = (Факс) SomeIMessageThatIReceive;

У ArrayList () в .NET 1.1 был красивый интерфейс под названием IList. Если бы у вас был IList (очень «общий»), вы могли бы преобразовать его в ArrayList:

ArrayList ar = (ArrayList)SomeIList;

И есть тысячи образцов в дикой природе.

Такие интерфейсы, как ISortable, IComparable и т. Д., Определяют методы и свойства, которые вы должны реализовать в своем классе для достижения этой функциональности.

Чтобы расширить наш образец, у вас может быть Список ‹> электронных писем, факсов, телефонных звонков, все в одном и том же списке, если тип - IMessage, но вы не могли бы собрать их все вместе, если бы объектами были просто электронная почта, факс и т. Д. .

Если вы хотите отсортировать (или, например, перечислить) свои объекты, они понадобятся вам для реализации соответствующего интерфейса. В примере .NET, если у вас есть список объектов «Факс» и вы хотите иметь возможность сортировать их с помощью MyList.Sort (), вам необходимо сделать ваш класс факса выглядит следующим образом:

public class Fax : ISorteable 
{
   //implement the ISorteable stuff here.
}

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

предупреждение: не все хорошо в интерфейсах, с ними есть некоторые проблемы, сторонники ООП начнут с этим войну. Я останусь в стороне. Один из недостатков Interfce (по крайней мере, в .NET 2.0) заключается в том, что у вас не может быть ЧАСТНЫХ членов или они защищены, они должны быть общедоступными. В этом есть некоторый смысл, но иногда вы хотите, чтобы вы могли просто объявить материал как частный или защищенный.

person Martin Marconcini    schedule 23.09.2008
comment
ваш пример кода ArrayList ar = (ArrayList) SomeIList; это неверно. Вы можете использовать IList только в том случае, если он уже был ArrayList. Суть использования IList заключалась в том, чтобы я мог создать свой собственный объект, реализующий IList, и передать его любому методу, который принимает IList, без предварительного помещения его в ArrayList. - person Robert Paulson; 24.09.2008

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

Кодовую базу с хорошо спроектированными интерфейсами внезапно стало намного проще обсуждать. «Да, вам нужен CredentialsManager для регистрации новых удаленных серверов». «Передайте PropertyMap в ThingFactory, чтобы получить рабочий экземпляр».

Умение решать сложную задачу одним словом очень полезно.

person Internet Friend    schedule 23.09.2008

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

interface IReport
{
    string RenderReport();
}

class MyNewReport : IReport
{
    public string RenderReport()
    {
        return "Hello World Report!";

    }
}

class AnotherReport : IReport
{
    public string RenderReport()
    {
        return "Another Report!";

    }
}

//This class can process any report that implements IReport!
class ReportEmailer()
{
     public void EmailReport(IReport report)
     {
         Email(report.RenderReport());
     }
}

class MyApp()
{
    void Main()
    {
        //create specific "MyNewReport" report using interface
        IReport newReport = new MyNewReport();

        //create specific "AnotherReport" report using interface
        IReport anotherReport = new AnotherReport();

        ReportEmailer reportEmailer = new ReportEmailer();

        //emailer expects interface
        reportEmailer.EmailReport(newReport);
        reportEmailer.EmailReport(anotherReport);



    }

}
person Giovanni Galbo    schedule 23.09.2008

Интерфейсы также являются ключом к полиморфизму, одной из «ТРИ СТОЛБОВ УДАЧИ».

Некоторые люди уже упоминали об этом выше, полиморфизм просто означает, что данный класс может принимать разные «формы». Это означает, что если у нас есть два класса, «Dog» и «Cat», и оба реализуют интерфейс «INeedFreshFoodAndWater» (хе-хе) - ваш код может делать что-то вроде этого (псевдокод):

INeedFreshFoodAndWater[] array = new INeedFreshFoodAndWater[];
array.Add(new Dog());
array.Add(new Cat());

foreach(INeedFreshFoodAndWater item in array)
{
   item.Feed();
   item.Water();
}

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

person Sam Schutte    schedule 23.09.2008

Итак, речь идет об абстрактных классах и интерфейсах ...

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

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

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

person Alexander    schedule 23.09.2008

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

Интерфейсы описывают функциональность, а не реализацию.

person Darren Kopp    schedule 23.09.2008

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

person Arcturus    schedule 21.10.2012

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

person Spoike    schedule 23.09.2008

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

person Kevin Pang    schedule 23.09.2008

Одна из веских причин для использования интерфейса вместо абстрактного класса в Java заключается в том, что подкласс не может расширять несколько базовых классов, но МОЖЕТ реализовывать несколько интерфейсов.

person Eric Asberry    schedule 23.09.2008

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

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

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

person user21334    schedule 23.09.2008

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

Скажите, что у вас есть:

public interface ICommand
{
    void Execute();
}
public class PrintSomething : ICommand
{
    OutputStream Stream { get; set; }
    String Content {get; set;}
    void Execute()
    { 
        Stream.Write(content);
    }
}

Теперь у вас есть заменяемая командная структура. Любой экземпляр класса, реализующего IExecute, может быть сохранен в некотором списке, скажем что-то, что реализует IEnumerable, и вы можете пройти через это и выполнить каждый из них, зная, что каждый объект будет просто делать то, что нужно. Вы можете создать составную команду, реализовав CompositeCommand, у которого будет свой собственный список команд для запуска, или LoopingCommand для многократного выполнения набора команд, тогда у вас будет большая часть простого интерпретатора.

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

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

Посмотрите принцип подстановки Лискова, чтобы понять это.

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

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

  • Интерфейсы определяют возможности и особенности.
  • Абстрактные классы определяют базовую функциональность.

Как правило, перед созданием абстрактного класса я выполняю рефакторинг интерфейса извлечения. Я с большей вероятностью создам абстрактный класс, если считаю, что должен быть договор создания (в частности, что конкретный тип конструктора всегда должен поддерживаться подклассами). Однако я редко использую «чистые» абстрактные классы в C # / java. У меня гораздо больше шансов реализовать класс по крайней мере с одним методом, содержащим осмысленное поведение, и использовать абстрактные методы для поддержки шаблонных методов, вызываемых этим методом. Тогда абстрактный класс является базовой реализацией поведения, которым могут воспользоваться все конкретные подклассы без необходимости повторной реализации.

person JasonTrue    schedule 23.09.2008

Простой ответ: интерфейс - это набор сигнатур методов (+ тип возврата). Когда объект говорит, что он реализует интерфейс, вы знаете, что он предоставляет этот набор методов.

person David    schedule 23.09.2008

Интерфейсы - это способ реализации соглашений, по-прежнему строго типизированный и полиморфный.

Хорошим примером из реальной жизни может быть IDisposable в .NET. Класс, реализующий интерфейс IDisposable, заставляет этот класс реализовать метод Dispose (). Если класс не реализует Dispose (), вы получите ошибку компилятора при попытке сборки. Кроме того, этот шаблон кода:

using (DisposableClass myClass = new DisposableClass())
  {
  // code goes here
  }

Приведет к автоматическому выполнению myClass.Dispose () при выходе из внутреннего блока.

Однако, и это важно, нет никаких ограничений относительно того, что должен делать ваш метод Dispose (). Вы можете попросить метод Dispose () выбрать случайные рецепты из файла и отправить их по электронной почте в список рассылки, компилятору все равно. Цель шаблона IDisposable - упростить очистку ресурсов. Если экземпляры класса будут удерживать дескрипторы файлов, то IDisposable позволяет очень легко централизовать код освобождения и очистки в одном месте и продвигать стиль использования, который гарантирует, что освобождение всегда происходит.

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

person Wedge    schedule 23.09.2008

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

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

Например, интересующая вас последовательность не имеет ничего общего с первичными ключами в записях. Для объекта, реализующего интерфейс, вы можете сказать myObject.next () или myObject.prev ().

person fooledbyprimes    schedule 23.09.2008

У меня была та же проблема, что и у вас, и я нахожу объяснение "контракта" немного сбивающим с толку.

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

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

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

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

person terjetyl    schedule 05.09.2010

Самый простой способ понять интерфейсы - начать с рассмотрения того, что означает наследование классов. Он включает два аспекта:

  1. Members of a derived class can use public or protected members of a base class as their own.
  2. Members of a derived class can be used by code which expects a member of the base class (meaning they are substitutable).

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

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

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

person supercat    schedule 06.08.2012

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

person Raz Megrelidze    schedule 07.07.2014

Как здесь уже говорили другие, интерфейсы определяют контракт (как будут «выглядеть» классы, использующие интерфейс), а абстрактные классы определяют общие функциональные возможности.

Посмотрим, поможет ли код:

public interface IReport
{
    void RenderReport(); // This just defines the method prototype
}

public abstract class Reporter
{
    protected void DoSomething()
    {
        // This method is the same for every class that inherits from this class
    }
}

public class ReportViolators : Reporter, IReport
{
    public void RenderReport()
    {
        // Some kind of implementation specific to this class
    }
}

public class ClientApp
{
    var violatorsReport = new ReportViolators();

    // The interface method
    violatorsReport.RenderReport();

    // The abstract class method
    violatorsReport.DoSomething();
}
person Robert S.    schedule 23.09.2008

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

Цель состоит в том, чтобы без необходимости видеть код в классе, вы могли знать, можно ли его использовать для определенной задачи. Например, класс Integer в Java реализует сопоставимый интерфейс, поэтому, если вы видели только заголовок метода (открытый класс String реализует Comparable), вы бы знали, что он содержит метод compareTo ().

person cblades    schedule 23.09.2008

В вашем простом случае вы можете добиться чего-то похожего на то, что вы получаете с интерфейсами, используя общий базовый класс, который реализует show() (или, возможно, определяет его как абстрактный). Позвольте мне изменить ваши общие имена на более конкретные, Eagle и Hawk вместо MyClass1 и MyClass2. В этом случае вы можете написать такой код, как

Bird bird = GetMeAnInstanceOfABird(someCriteriaForSelectingASpecificKindOfBird);
bird.Fly(Direction.South, Speed.CruisingSpeed);

Это позволяет вам писать код, который может обрабатывать все, что является Bird. Затем вы можете написать код, который заставляет Bird выполнять свои функции (летать, есть, откладывать яйца и т. Д.), Действуя на экземпляр, который он рассматривает как Bird. Этот код будет работать независимо от того, является ли Bird действительно Eagle, Hawk или чем-либо еще, производным от Bird.

Однако эта парадигма становится запутанной, когда у вас нет настоящих отношений - это. Допустим, вы хотите написать код, который будет летать в небе. Если вы напишете этот код для принятия базового класса Bird, внезапно станет трудно развить этот код для работы с экземпляром JumboJet, потому что в то время как Bird и JumboJet, безусловно, могут летать, JumboJet определенно не Bird.

Войдите в интерфейс.

Что общего у BirdEagle, и Hawk) do, так это то, что все они умеют летать. Если вместо этого вы напишете приведенный выше код для работы с интерфейсом IFly, этот код можно будет применить ко всему, что обеспечивает реализацию этого интерфейса.

person Eric J.    schedule 05.08.2012