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

118

Я понимаю, что это очень простой вопрос, но интервьюер задал мне очень хитрый тон, и я был беспомощен :(

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

В интерфейсе тоже ничего не понимаю. например, мы используем

conn.Dispose();в блоке finally. Но я не вижу, что этот класс реализует или наследует класс IDisposableinterface ( SqlConnection), я имею в виду. Мне интересно, как я могу просто назвать имя метода. Кроме того, я не понимаю, как работает метод Dispose, поскольку нам нужно реализовать тело функции с нашей собственной реализацией для всех методов интерфейса. Итак, как интерфейсы принимаются или называются контрактами? Эти вопросы крутились у меня в голове до сих пор, и, честно говоря, я никогда не видел ни одной хорошей темы, которая бы объясняла мои вопросы так, как я могу понять.

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

Интервьюер сказал:

У него есть 5 методов, и он счастлив реализовать их в классе напрямую, но если вам нужно выбрать абстрактный класс или интерфейс, какой из них вы выберете и почему? Я ответил ему на все материалы, которые я читал в различных блогах, говоря о преимуществах и недостатках как абстрактного класса, так и интерфейса, но он не убежден, он пытается понять «Почему интерфейс» в целом. "Почему именно абстрактный класс" вообще, даже если я могу реализовать одни и те же методы только один раз и не собираюсь их менять.

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

ученик
источник
4
Я также изо всех сил пытался понять интерфейсы. Хороший вопрос.
Брайан
4
программирование на абстрактный контракт, а не на конкретную реализацию ... Короче говоря, это означает, что вы можете заменить любой объект, реализующий интерфейс, когда интерфейс требуется.
Mitch Wheat
7
SqlConnectionнаследует, System.ComponentModel.Componentкоторый реализует IDisposable.
Ли
2
@MitchWheat - Это не для примера, вопрос в том, как это SqlConnectionреализовать IDisposable.
Ли
О, Ли, это заставило меня понять, спасибо. Но я до сих пор не понимаю, как и где определяется функциональность метода Dispose.
Learner

Ответы:

92

Интерфейсы прекрасны, когда нужно создать что-то подобное:

using System;

namespace MyInterfaceExample
{
    public interface IMyLogInterface
    {
        //I want to have a specific method that I'll use in MyLogClass
        void WriteLog();       
    }

    public class MyClass : IMyLogInterface
    {

        public void WriteLog()
        {
            Console.Write("MyClass was Logged");
        }
    }

    public class MyOtherClass : IMyLogInterface
    {

        public void WriteLog()
        {
            Console.Write("MyOtherClass was Logged");
            Console.Write("And I Logged it different, than MyClass");
        }
    }

    public class MyLogClass
    {
        //I created a WriteLog method where I can pass as a parameter any object that implements IMyLogInterface.
        public static void WriteLog(IMyLogInterface myLogObject)
        {
            myLogObject.WriteLog(); //So I can use WriteLog here.
        }
    }

    public class MyMainClass
    {
        public void DoSomething()
        {
            MyClass aClass = new MyClass();
            MyOtherClass otherClass = new MyOtherClass();

            MyLogClass.WriteLog(aClass);//MyClass can log, and have his own implementation
            MyLogClass.WriteLog(otherClass); //As MyOtherClass also have his own implementation on how to log.
        }
    }
}

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

Гильерме де Хесус Сантос
источник
3
Эй, похоже, это очень хороший ингредиент для меня, я очень ценю это, большое спасибо :) :)
Ученик
14
У меня вопрос: если вы создаете экземпляр, MyClassи MyOtherClassпочему бы вам просто не позвонить, aClass.WriteLog()зачем добавлять этот дополнительный шаг. Реализация WriteLog()будет разной для каждого из классов, но у вас уже есть объект, так зачем передавать его классу обработчика?
Zach M.
Хм, может случиться так, что если вы поместите свой пример ведения журнала на самородок, другим будет проще использовать ваш регистратор, не зная деталей ... но с другой стороны, все же это не какой-то универсальный класс (например, я мог бы написать интерфейс с ведением журнала и уровнями аллергии) интерфейсы по-прежнему находятся только в вашей области. так что, кроме себя, кому это выгодно?
user3800527
2
@ZachM. Если я прав, ответ означает, что он не будет создавать экземпляры классов, но другие разработчики будут создавать экземпляры классов и передавать их в качестве параметра MyLogClass WriteLogметоду. Таким образом, его метод может обрабатывать любой объект, который реализует IMyLogInterface. Вот еще один интересный пост.
Shaijut
1
У меня вопрос, почему интерфейс ??? Вышеупомянутый сценарий также может быть реализован с помощью абстрактного класса со всеми абстрактными методами.
Abhijit
52

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

public void DoSomething(Account account) {
  // Do awesome stuff here.
}

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

public void DoSomething(IAccount account) {
  // Do awesome stuff here.
}

Это решение не привязано к реализации, что означает, что я могу передать ему SuperSavingsAccount или ExclusiveAccount (оба реализуют интерфейс IAccount) и получить различное поведение для каждой реализованной учетной записи.

user2211290
источник
45

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

Почему интерфейс

  • У вас нет реализации по умолчанию или общего кода
  • Вы хотите обмениваться контрактами на данные (веб-сервисы, SOA)
  • У вас есть разные реализации для каждого разработчика интерфейса ( IDbCommandесть SqlCommandи OracleCommandкоторые реализуют интерфейс определенными способами )
  • Вы хотите поддерживать множественное наследование .

Почему абстракция

SliverNinja - MSFT
источник
2
@Silver Я читал большую часть того, что вы напечатали в блогах, но пытаюсь понять практически. Я сделал службы WCF, открытые интерфейсы (но это было всего лишь отдельное приложение без восходящего или нисходящего потока). Следовательно, я не мог понять это должным образом, хотя я очень хорошо спроектировал и реализовал интерфейсы. Мой вопрос в том, что на практике вы просто разделяете контракт с именами методов, верно? КАК ЭТО ПОЛЕЗНО :( Я знаю, что это просто вынуждает реализовать все методы, но еще как? В вашем сообщении выше по интерфейсу, 2-й пункт говорит о совместном использовании, означает, что вы можете привести практический пример этого в реальном времени
Учащийся
1
В качестве практического примера, касающегося интерфейсов и SOA, мы разделяем наши интерфейсы WCF ( DataContracts) в сборке .NET ( например, Contracts.Shared.dll ), чтобы потребители клиентов .NET могли легко взаимодействовать друг с другомChannelFactory ( избегая генерации кода с помощью добавления ссылки на службу и т. Д.). ) или с помощью добавления ссылки на службу с
общими
Если я объявлю только абстрактные методы внутри абстрактного класса, тогда абстрактный класс будет действовать как интерфейс, тогда зачем нам интерфейс?
Abhijit
25

введите описание изображения здесь

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

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

Бриджеш Рана
источник
В этом есть смысл, но я все еще пытаюсь понять, не могли бы вы просто не создать базовый класс для PowerSocket и все остальные вещи, наследовавшие его при необходимости. Технически Power Sockets не знают о других классах.
altaaf.hussein
Я думаю, потому что множественное наследование запрещено в C #
Хью Сигрейвс,
22

Одним словом - из-за полиморфизма !

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

  • Изучите «Внедрение зависимостей» и обязательно прочтите « Шаблоны проектирования - элементы многоразового объектно-ориентированного программного обеспечения» от GOF.
Габриэль С. Троя
источник
4

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

Erix
источник
3
Вы можете получить что-то вроде утиного ввода в .net4 с динамическим типом.
Тони Хопкинсон
4

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

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

Теперь давайте исследуем базу по уже приведенной мной ссылке.

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

public class clsBike:IChoice
{
   #region IChoice Members
    public string Buy()
    {
       return ("You choose Bike");
    }
    #endregion
}

и

public class clsCar:IChoice
{
   #region IChoice Members
    public string Buy()
    {
       return ("You choose Car");
    }
    #endregion
}

и у обоих есть контракт IChoice, в котором просто говорится, что у моего класса должен быть метод Buy

public interface IChoice
{
    string Buy();
}

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

public abstract class Choice
{
    public abstract string Discount { get; }
    public abstract string Type { get; }
    public string Buy()
    {
       return "You buy" + Type + " with " + Discount;
}
public class clsBike: Choice
{
    public abstract string Discount { get { return "10% Discount Off"; } }
    public abstract string Type { get { return "Bike"; } }
}

public class clsCar:Choice
{
    public abstract string Discount { get { return " $15K Less"; } }
    public abstract string Type { get { return "Car"; } }
}

Теперь, используя Factory Class, вы можете добиться того же, но, используя abstract, вы позволяете базовому классу выполнять Buy()метод.

Резюме: Контракты интерфейса позволяют классу-наследнику выполнять реализацию, в то время как Контракты абстрактного класса могут инициализировать реализацию (что может быть отменено классом Inherit)

dr.Crow
источник
2

С помощью интерфейса вы можете делать следующее:

1) Создавайте отдельные интерфейсы, которые предлагают различные варианты вашей реализации, что позволяет создать более целостный интерфейс.

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

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

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

Я уверен, что есть еще много причин, и это лишь некоторые из них.

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

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

В качестве реального образца ответа @ user2211290:

Оба они Arrayи Listимеют интерфейс IList. Ниже у нас есть a string[]и a, List<string>и мы проверяем их оба одним методом с помощью IList :

string[] col1 = { "zero", "one", "two", "three", "four"};
List<string> col2 = new List<string>{ "zero", "one", "two", "three"};

//a methode that manipulates both of our collections with IList
static void CheckForDigit(IList collection, string digit)
{
    Console.Write(collection.Contains(digit));
    Console.Write("----");
    Console.WriteLine(collection.ToString()); //writes the type of collection
}

static void Main()
{
    CheckForDigit(col1, "one");   //True----System.String[]
    CheckForDigit(col2, "one");   //True----System.Collections.Generic.List`1[System.String]



//Another test:

    CheckForDigit(col1, "four");   //True----System.String[]
    CheckForDigit(col2, "four");   //false----System.Collections.Generic.List`1[System.String]
}
Фарзин Канзи
источник
1

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

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

Чжоу Ли Хуан
источник
1

И абстрактный класс, и интерфейс являются контрактами.

Идея контракта заключается в том, чтобы вы определяли какое-то поведение. Если вы говорите, что выполнили, вы согласились с контрактом.

Выбор абстрактного интерфейса есть.

Любой неабстрактный потомок абстрактного класса будет реализовывать контракт.

против

Любой класс, реализующий интерфейс, будет реализовывать контракт.

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

Тони Хопкинсон
источник
1

Интерфейсы должны делать абстракцию (архетип) абстракции (классов) реальности (объектов).

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

Интерфейсы имеют характеристики:

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

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

Что такое интерфейс?

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

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

По сути, это еще не класс, хотя в UML мы называем его классом на диаграмме классов, потому что мы можем определить частные и защищенные члены, чтобы начать глубокое представление об артефакте. Не путайте здесь, потому что в UML интерфейс немного отличается от интерфейса в C #: он похож на частичную точку доступа к атому абстракции. Таким образом, мы сказали, что класс может реализовывать несколько интерфейсов. По сути, это одно и то же, но не потому, что интерфейсы в C # используются для абстрагирования абстракции и для ограничения этой абстракции как точки доступа. Это два разных использования. Таким образом, класс в UML представляет собой интерфейс полной связи с классом программирования, тогда как интерфейс UML представляет интерфейс разделения раздела класса программирования. На самом деле, диаграмма классов в UML не заботится о реализации, и все ее артефакты находятся на уровне интерфейса программирования. Хотя мы сопоставляем классы UML с классами программирования, это преобразование абстрактной абстракции в конкретную абстракцию. Есть тонкость, объясняющая дихотомию между областью дизайна и областью программирования. Итак, класс в UML - это класс программирования с точки зрения интерфейса программирования с учетом внутренних скрытых вещей.

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

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

unit UnitName;

interface

type
  TheClass = class
  public
    procedure TheMethod;
  end;

implementation

class procedure TheClass.TheMethod;
begin
end;

Здесь раздел интерфейса соответствует дизайну класса UML, в то время как типы интерфейсов, таким образом, другие.

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

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

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

Что такое класс?

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

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

Таким образом, абстрактный класс в некотором смысле эквивалентен интерфейсу с точки зрения компилятора.

Больше информации

Протокол (объектно-ориентированное программирование)

C # - Интерфейсы

C # - Классы

Оливье Рожье
источник
1

Позвольте рассказать вам о летающих тостерах.

летающий тостер

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

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

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

Эти нетривиальные конструкции связаны с множественным наследованием, которое в его «истинной» форме - это когда класс наследуется не только от одного базового класса, но от двух или более базовых классов. Эта истинная форма невозможна в C #, но до появления таких языков, как C # и Java, правилом был C ++, который полностью поддерживал истинное множественное наследование. К сожалению, истинное множественное наследование оказалось не очень хорошей идеей, потому что оно чрезвычайно усложняет дизайн языка, а также порождает различные проблемы, например знаменитую «проблему алмаза». (См. Ответ Дж. Фрэнсиса «В чем именно заключается проблема множественного наследования?» )

Таким образом, если кто-то хочет создать класс «летающий тостер», он унаследует его от какого-то существующего класса «тостер», а также от некоторого существующего класса «летающий». Проблема, с которой они, вероятно, столкнулись, заключалась в том, что источником питания класса тостеров, вероятно, была стенная розетка, в то время как источником питания класса летательных аппаратов, вероятно, был корм для голубей, и в результате новый класс либо как-то есть оба, иначе было бы неясно, какой из них. (Проблема с бриллиантом.)

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

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

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

Майк Накис
источник
0

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

Пой
источник
Хорошо сказано в вашем первом заявлении. Но я не понимаю ваше второе утверждение, не могли бы вы уточнить пример в реальном времени?
Learner
0

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

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

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

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

Массимилиано Пелузо
источник
-2

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

Например, если у меня есть две сущности, такие как Animal и Human, тогда я выберу интерфейс, где, как если бы мне нужно было подробно рассказать, скажем, Tiger, lion и хочу установить связь с Animal, тогда выберу Animal Abstract class

будет выглядеть ниже

   Interface             
   ____|____
  |        |
Animal   Human



  Animal (Abstract class)
   __|___
  |      |
Tiger   Lion
Манодж
источник