Подтвердить что ты не робот

Понимание интерфейсов

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

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

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

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

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

Я также не понимаю, что вы можете сделать ссылку на такой объект:

ICalculator myInterface = new JustSomeClass();

Итак, теперь, если я подойду к myInterface, точка и intellisense потянутся, я увижу только методы интерфейса, а не другие методы в JustSomeClass. Поэтому я пока не вижу смысла.

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

Как, например, этот пример:

public AuthenticationController(IFormsAuthentication formsAuth)
{
    FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();
}

public class FormsAuthenticationWrapper : IFormsAuthentication
{
    public void SetAuthCookie(string userName, bool createPersistentCookie)
    {
        FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
    }
    public void SignOut()
    {
        FormsAuthentication.SignOut();
    }
}

public IFormsAuthentication FormsAuth
{
    get;
    set;
}

Как и зачем делать этот интерфейс? Почему бы просто не сделать FormsAuthenticationWrapper с помощью методов в нем и назвать его днем? Почему сначала создать интерфейс, а затем Wrapper реализовать интерфейс, а затем, наконец, написать методы?

Тогда я не понимаю, что говорит оператор.

Как я теперь знаю, что утверждение говорит об этом

FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

если formsAuth имеет значение null, затем создайте новый FormsAuthenticationWrapper и затем назначьте его свойству, являющемуся интерфейсом.

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

Затем в файле тестирования они:

var formsAuthenticationMock = new Mock<AuthenticationController.IFormsAuthentication>();

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

Но, глядя на новый Mock (из moq), он принимает класс или интерфейс. Почему бы просто не сделать так, чтобы класс-оболочка помещал эти методы, а затем в новый Mock-вызов?

Разве это не просто сделает для вас заглушки?

4b9b3361

Ответ 1

Интерфейсы определяют контракты.

В примере, который вы предоставляете, оператор ?? просто предоставляет значение по умолчанию, если вы передаете null конструктору и на самом деле не имеете ничего общего с интерфейсами.

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

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

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

void OutputValues(string[] values)
{
   foreach (string value in values)
   {
       Console.Writeline(value);
   }
}

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

void OutputValues(IEnumerable<string> values)
{
   foreach (string value in values)
   {
       Console.Writeline(value);
   }
}

Этот код по-прежнему принимает массив и выводит его на консоль. Но он также принимает List<string> или что-то еще, что вам нужно дать, чтобы реализовать IEnumerable<string>. Итак, мы взяли интерфейс и использовали его, чтобы сделать простой блок кода намного более мощным.

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

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

Ответ 2

Хорошо, сначала мне было трудно понять, поэтому не беспокойтесь об этом.

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

public class Character
{
}

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

interface IWeapon
{
    public Use();
}

Итак, дайте персонажу оружие:

public class Character
{
    IWeapon weapon;

    public void GiveWeapon(IWeapon weapon)
    {
        this.weapon = weapon;
    }

    public void UseWeapon()
    {
        weapon.Use();
    }
}

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

public class Gun : IWeapon
{
    public void Use()
    {
        Console.Writeline("Weapon Fired");
    }
}

Затем вы можете склеить его:

Character bob = new character();
Gun pistol = new Gun();
bob.GiveWeapon(pistol);
bob.UseWeapon();

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

Ответ 3

Возможно, лучший способ получить хорошее понимание интерфейсов - использовать пример из платформы .NET.

Рассмотрим следующую функцию:

void printValues(IEnumerable sequence)
{
    foreach (var value in sequence)
        Console.WriteLine(value);
}

Теперь я мог бы написать эту функцию, чтобы принять List<T>, object[] или любой другой тип конкретной последовательности. Но так как я написал эту функцию для принятия параметра типа IEnumerable, это означает, что я могу передать любой конкретный тип в эту функцию, реализующую интерфейс IEnumerable.

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

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

Ответ 4

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

У класса есть методы и свойства, и каждый из методов и свойств класса имеет подпись и тело

public int Add(int x, int y)
{
return x + y;
}

Подпись метода Add - это все до первого фигурного фигурного фигурного скобка

public int Add(int x, int y)

Цель сигнатуры метода - назначить имя методу, а также описать его уровень защиты (общедоступный, защищенный, внутренний, закрытый и/или виртуальный), который определяет, к каким способам можно получить доступ из кода

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

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

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

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

Другими словами, интерфейс может описывать действия (методы) класса, но он никогда не должен описывать, как должно выполняться действие.

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

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

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

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

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

Итак, вы решили разрешить пользователям разрабатывать свои собственные плагины, чтобы они могли <blink 'до тех пор, пока корова не вернутся домой.

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

public class Plugin
{
public void Run (PluginHost browser)
{
//do stuff here....
}
}

Но как вы могли бы разумно реализовать этот метод? Вы не можете точно знать, как будет работать каждый плагиновый будущий плагин.

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

public interface IPlugin
{
void Run(PluginHost browser);
}

public class PluginHost
{
public void RunPlugins (IPlugin[] plugins)
{
foreach plugin in plugins
{
plugin.Run(this);
}
}
}

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

Чтобы продемонстрировать "can-be-a" аспект отношения между классом и интерфейсом, я напишу плагин для хоста плагина ниже, который реализует тег <blink>.

public class BlinkPlugin: IPlugin
{
private void MakeTextBlink(string text)
{
//code to make text blink.
}
public void Run(PluginHost browser)
{
MakeTextBlink(browser.CurrentPage.ParsedHtml);
}
}

С этой точки зрения вы можете видеть, что плагин определен в классе с именем BlinkPlugin, но поскольку он также реализует интерфейс IPlugin, его также можно назвать объектом IPlugin, как это делает класс PluginHost, поскольку он не знать или заботиться о том, какой тип класса на самом деле есть, просто это может быть IPlugin

Надеюсь, это помогло, я действительно не собирался так долго.

Ответ 5

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

Я бы пошел

IEnumerable<T> sequence;

когда когда-либо я могу и вряд ли когда-либо (единственный случай, который я могу придумать, - это если мне действительно нужен метод ForEach)

List<T> sequence;

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

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

решение может быть: создать одну веб-страницу, скажем, с помощью datagrid и Injection Dependency IDataRetriever (где IDataRetriver - это некоторый интерфейс, позволяющий получать данные, из которых вы должны знать, откуда поступали данные (DB, XML, веб-службы или...) или как они были извлечены (данные заминированы, SQL Quering in house data или read from файл).

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

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

Ответ 6

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

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

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

Ответ 7

Как я понял, это было:

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

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

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

Ответ 8

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

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

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

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

например.

Интерфейс liveBeings

- speak()//говорит кто-нибудь, кто IS живет. Необходимо определить, как они говорят.

собака класса реализует живые существа

- говорить() {bark;}//реализация говорить как собака

класс птиц реализует живые существа

- говорить() {chirp;}//реализация говорить как птица

Ответ 9

ICalculator myInterface = new JustSomeClass();
JustSomeClass myObject = (JustSomeClass) myInterface;

Теперь у вас есть оба "интерфейса" для работы с объектом.

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

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

Надеюсь, что это поможет.