Интерфейсы C# - какой смысл?

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

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

В приведенном ниже примере, взятом из другого потока интерфейсов C# при переполнении стека, я бы просто создал базовый класс с именем Pizza вместо интерфейса.

простой пример (взят из другого вклада переполнения стека)

public interface IPizza
{
    public void Order();

}

public class PepperoniPizza : IPizza
{
    public void Order()
    {
        //Order Pepperoni pizza
    }
}

public class HawaiiPizza : IPizza
{
    public void Order()
    {
        //Order HawaiiPizza
    }
}

33 ответа

Решение

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

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

Python не является статически типизированным, поэтому типы сохраняются и проверяются во время выполнения. Так что вы можете попробовать позвонить Order() метод на любом объекте. Среда выполнения счастлива, если у объекта есть такой метод, и, вероятно, просто пожимает плечами и говорит "Мех", если его нет. Не так в C#. Компилятор отвечает за правильные вызовы, и если он имеет какой-то случайный object компилятор еще не знает, будет ли экземпляр во время выполнения иметь этот метод. С точки зрения компилятора он недействителен, так как не может это проверить. (Вы можете делать такие вещи с отражением или dynamic ключевое слово, но сейчас я думаю, что это немного далеко.)

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

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

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

При написании этого в коде, технически вы могли бы просто сделать

public class Pizza()
{
    public void Prepare(PizzaType tp)
    {
        switch (tp)
        {
            case PizzaType.StuffedCrust:
                // prepare stuffed crust ingredients in system
                break;

            case PizzaType.DeepDish:
                // prepare deep dish ingredients in system
                break;

            //.... etc.
        }
    }
}

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

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

public interface IPizza
{
    void Prepare();
}

public class StuffedCrustPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for stuffed crust preparations
    }
}

public class DeepDishPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for deep dish preparations
    }
}

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

public PreparePizzas(IList<IPizza> pizzas)
{
    foreach (IPizza pizza in pizzas)
        pizza.Prepare();
}

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

Для меня смысл этого стал ясен только тогда, когда вы перестали смотреть на них как на вещи, которые делают ваш код легче / быстрее писать - это не их цель. У них есть ряд применений:

(Это приведет к потере аналогии с пиццей, так как это не очень легко визуализировать)

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

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

Вы могли бы написать это для начала, так как будут только тролли:

// This is our back-end implementation of a troll
class Troll
{
    void Walk(int distance)
    {
        //Implementation here
    }
}

Внешний интерфейс:

function SpawnCreature()
{
    Troll aTroll = new Troll();

    aTroll.Walk(1);
}

Спустя две недели, маркетологи решили, что вам также нужны орки, так как они читают о них в твиттере, так что вам придется сделать что-то вроде:

class Orc
{
    void Walk(int distance)
    {
        //Implementation (orcs are faster than trolls)
    }
}

Внешний интерфейс:

void SpawnCreature(creatureType)
{
    switch(creatureType)
    {
         case Orc:

           Orc anOrc = new Orc();
           anORc.Walk();

          case Troll:

            Troll aTroll = new Troll();
             aTroll.Walk();
    }
}

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

interface ICreature
{
    void Walk(int distance)
}

public class Troll : ICreature
public class Orc : ICreature 

//etc

Тогда передний конец:

void SpawnCreature(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();
}

Внешний интерфейс теперь заботится только об интерфейсе ICreature - он не беспокоится о внутренней реализации тролля или орка, а только о том, что они реализуют ICreature.

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

И вы можете извлечь творение на фабрику:

public class CreatureFactory {

 public ICreature GetCreature(creatureType)
 {
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    return creature;
  }
}

И тогда наш интерфейс станет:

CreatureFactory _factory;

void SpawnCreature(creatureType)
{
    ICreature creature = _factory.GetCreature(creatureType);

    creature.Walk();
}

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

B: Скажем, у вас есть функциональность, которую будут иметь только некоторые существа в вашей однородной структуре данных, например

interface ICanTurnToStone
{
   void TurnToStone();
}

public class Troll: ICreature, ICanTurnToStone

Тогда передний конец может быть:

void SpawnCreatureInSunlight(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();

    if (creature is ICanTurnToStone)
    {
       (ICanTurnToStone)creature.TurnToStone();
    }
}

C: Использование для внедрения зависимости

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

public interface ICreatureFactory {
     ICreature GetCreature(string creatureType);
}

Наш внешний интерфейс может затем внедрить это (например, контроллер MVC API) через конструктор (обычно):

public class CreatureController : Controller {

   private readonly ICreatureFactory _factory;

   public CreatureController(ICreatureFactory factory) {
     _factory = factory;
   }

   public HttpResponseMessage TurnToStone(string creatureType) {

       ICreature creature = _factory.GetCreature(creatureType);

       creature.TurnToStone();

       return Request.CreateResponse(HttpStatusCode.OK);
   }
}

С нашей структурой DI (например, Ninject или Autofac) мы можем настроить их так, чтобы во время выполнения экземпляр CreatureFactory создавался всякий раз, когда в конструкторе требуется ICreatureFactory - это делает наш код красивым и простым.

Это также означает, что когда мы пишем модульный тест для нашего контроллера, мы можем предоставить фиктивную ICreatureFactory (например, если конкретная реализация требует доступа к БД, мы не хотим, чтобы наши модульные тесты зависели от этого) и легко протестировать код в нашем контроллере,

D: Существуют и другие варианты использования, например, у вас есть два проекта A и B, которые по "устаревшим" причинам не имеют четкой структуры, и A имеет ссылку на B.

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

Вы можете иметь интерфейс, объявленный в B, который затем реализует класс в A. Вашему методу в B можно передать экземпляр класса, который без проблем реализует интерфейс, даже если конкретный объект имеет тип в A.

Простое объяснение по аналогии

Проблема для решения: какова цель полиморфизма?

Аналогия: Итак, я работница на стройке.

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

  1. Если это плотник, я говорю: строить деревянные леса.
  2. Если это сантехник, я говорю: "Настройте трубы"
  3. Если это электрик, я говорю: "Вытащите кабели и замените их оптоволоконными".

Проблема с вышеуказанным подходом заключается в том, что я должен: (i) знать, кто ходит в эту дверь, и в зависимости от того, кто это, я должен сказать им, что делать. Это означает, что я должен знать все о конкретной сделке. Есть затраты / выгоды, связанные с этим подходом:

Последствия знания того, что делать:

  • Это означает, что если код плотника меняется с: BuildScaffolding() в BuildScaffold() (т.е. небольшое изменение имени), то мне придется также изменить вызывающий класс (то есть Foreperson класс) - вам придется внести два изменения в код вместо (в основном) только одного. С полиморфизмом вам (в основном) нужно всего лишь сделать одно изменение, чтобы достичь того же результата.

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

  • с полиморфизмом вы можете легко добавлять дополнительные классы торговцев без изменения существующего кода. (т.е. второй из принципов проектирования SOLID: принцип Open-Close).

Решение

Представьте себе сценарий, в котором, независимо от того, кто входит в дверь, я могу сказать: "Работа ()", и они выполняют свою работу по уважению, в которой они специализируются: сантехник будет заниматься трубами, а электрик - проводами.

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

Итак, вместо этого:

If(electrician) then  electrician.FixCablesAndElectricity() 

if(plumber) then plumber.IncreaseWaterPressureAndFixLeaks() 

Я могу сделать что-то вроде этого:

ITradesman tradie = Tradesman.Factory(); // in reality i know it's a plumber, but in the real world you won't know who's on the other side of the tradie assignment.

tradie.Work(); // and then tradie will do the work of a plumber, or electrician etc. depending on what type of tradesman he is. The foreman doesn't need to know anything, apart from telling the anonymous tradie to get to Work()!!

В чем выгода?

Преимущество состоит в том, что если конкретные требования к работе плотника и т. Д. Изменятся, то руководителю не нужно будет менять свой код - ему не нужно знать или заботиться. Все, что имеет значение, так это то, что плотник знает, что подразумевается под Work(). Во-вторых, если на стройплощадку приходит новый тип строителя, то бригадир не должен ничего знать о торговле - все, что беспокоит бригадира, это если строительный рабочий (например, Welder, Glazier, Tiler и т. Д.) Может получить некоторую работу ().


Иллюстрированная проблема и решение (с интерфейсами и без):

Нет интерфейса (пример 1):

Пример 1: без интерфейса

Нет интерфейса (пример 2):

Пример 2: без интерфейса

С интерфейсом:

Пример 3: Преимущества использования интерфейса

Резюме

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

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

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

public abstract class Food {
    public abstract void Prepare();
}

public class Pizza : Food  {
    public override void Prepare() { /* Prepare pizza */ }
}

public class Burger : Food  {
    public override void Prepare() { /* Prepare Burger */ }
}

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

Более адекватным примером будет множественное наследование:

public abstract class MenuItem {
    public string Name { get; set; }
    public abstract void BringToTable();
}

// Notice Soda only inherits from MenuItem
public class Soda : MenuItem {
    public override void BringToTable() { /* Bring soda to table */ }
}


// All food needs to be cooked (real food) so we add this
// feature to all food menu items
public interface IFood {
    void Cook();
}

public class Pizza : MenuItem, IFood {
    public override void BringToTable() { /* Bring pizza to table */ }
    public void Cook() { /* Cook Pizza */ }
}

public class Burger : MenuItem, IFood {
    public override void BringToTable() { /* Bring burger to table */ }
    public void Cook() { /* Cook Burger */ }
}

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

public class Waiter {
    public void TakeOrder(IEnumerable<MenuItem> order) 
    {
        // Cook first
        // (all except soda because soda is not IFood)
        foreach (var food in order.OfType<IFood>())
            food.Cook();

        // Bring them all to the table
        // (everything, including soda, pizza and burger because they're all menu items)
        foreach (var menuItem in order)
            menuItem.BringToTable();
    }
}

Вот ваши примеры объяснены:

public interface IFood // not Pizza
{
    public void Prepare();

}

public class Pizza : IFood
{
    public void Prepare() // Not order for explanations sake
    {
        //Prepare Pizza
    }
}

public class Burger : IFood
{
    public void Prepare()
    {
        //Prepare Burger
    }
}

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

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

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

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

Рассмотрим случай, когда вы не контролируете или не владеете базовыми классами.

Возьмите визуальные элементы управления, например, в.NET для Winforms все они наследуются от базового класса Control, который полностью определен в.NET Framework.

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

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

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

Вместо этого вы перейдете из Button, TextBox, ListView, GridView и т. Д. И добавите свой код.

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

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

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

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

Это позволит вам написать код, подобный следующему:

foreach (Control ctrl in Controls)
{
    if (ctrl is IMyThemableControl)
        ((IMyThemableControl)ctrl).SetTheme(newTheme);
}

Это было бы невозможно без интерфейсов, вместо этого вам пришлось бы писать такой код:

foreach (Control ctrl in Controls)
{
    if (ctrl is MyThemableButton)
        ((MyThemableButton)ctrl).SetTheme(newTheme);
    else if (ctrl is MyThemableTextBox)
        ((MyThemableTextBox)ctrl).SetTheme(newTheme);
    else if (ctrl is MyThemableGridView)
        ((MyThemableGridView)ctrl).SetTheme(newTheme);
    else ....
}

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

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

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

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

Учтите, что вы не можете использовать множественное наследование в C#, а затем снова посмотрите на свой вопрос.

Интерфейс = контракт, используется для слабой связи (см. GRASP).

Я выполнил поиск по слову "композиция" на этой странице и не видел его ни разу. Этот ответ очень в дополнение к вышеупомянутым ответам.

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

Этот превосходный учебник "Шаблон декоратора" Дерека Банаса (который - как ни странно - также использует пиццу в качестве примера) является достойной иллюстрацией:

https://www.youtube.com/watch?v=j40kRwSm4VE

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

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

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

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

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

public class Car { ... }

public class Tree { ... }

Вы хотите добавить выгорающую функциональность для обоих классов. Но у каждого класса есть свои способы сжигания. так что вы просто делаете;

public class Car : IBurnable
{
public void Burn() { ... }
}

public class Tree : IBurnable
{
public void Burn() { ... }
}

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

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

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

Итак, вы называете фабричный метод: MyInterface i = MyGraphics.getInstance(), Затем у вас есть контракт, так что вы знаете, какие функции вы можете ожидать в MyInterface, Итак, вы можете позвонить i.drawRectangle или же i.drawCube и знайте, что если вы меняете одну библиотеку на другую, функции поддерживаются.

Это становится более важным, если вы используете Dependency Injection, так как в XML-файле вы можете заменить реализацию.

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

Это широко используется для коллекций в.NET, так как вы должны просто использовать, например, List переменные, и не волнуйтесь, был ли это ArrayList или LinkedList.

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

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

Какой?

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

В C# Имена интерфейсов по соглашению определяются с помощью префикса "I", поэтому, если вы хотите иметь интерфейс с именем shape, вы должны объявить его как IShapes

Теперь почему?

Improves code re-usability

Допустим, вы хотите рисовать Circle, Triangle. Вы можете сгруппировать их вместе и называть их Shapesи есть методы рисования Circle а также TriangleНо иметь конкретную реализацию было бы плохой идеей, потому что завтра вы можете решить иметь еще 2 Shapes Rectangle & Square. Теперь, когда вы добавляете их, есть большая вероятность, что вы можете сломать другие части вашего кода.

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


Живой сценарий, день 1

Вас попросили создать приложение для рисования круга и треугольника

interface IShapes
{
   void DrawShape();
   
 }

class Circle : IShapes
{
    
    public void DrawShape()
    {
        Console.WriteLine("Implementation to Draw a Circle");
    }
}

Class Triangle: IShapes
{
     public void DrawShape()
    {
        Console.WriteLine("Implementation to draw a Triangle");
    }
}
static void Main()
{
     List <IShapes> shapes = new List<IShapes>();
        shapes.Add(new Circle());
        shapes.Add(new Triangle());

        foreach(var shape in shapes)
        {
            shape.DrawShape();
        }
}

Живой сценарий, день 2

Если вас попросили добавить Square а также Rectangle к нему, все, что вам нужно сделать, это создать его реализацию в class Square: IShapes И в Main добавить в список shapes.Add(new Square());

Вот интерфейс для объектов, которые имеют прямоугольную форму:

interface IRectangular
{
    Int32 Width();
    Int32 Height();
}

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

Теперь давайте определим метод, который будет работать с любым объектом, который IRectangular:

static class Utils
{
    public static Int32 Area(IRectangular rect)
    {
        return rect.Width() * rect.Height();
    }
}

Это вернет область любого прямоугольного объекта.

Давайте реализуем класс SwimmingPool это прямоугольник:

class SwimmingPool : IRectangular
{
    int width;
    int height;

    public SwimmingPool(int w, int h)
    { width = w; height = h; }

    public int Width() { return width; }
    public int Height() { return height; }
}

И еще один класс House это тоже прямоугольник:

class House : IRectangular
{
    int width;
    int height;

    public House(int w, int h)
    { width = w; height = h; }

    public int Width() { return width; }
    public int Height() { return height; }
}

Учитывая это, вы можете позвонить Area метод на домах или бассейнах:

var house = new House(2, 3);

var pool = new SwimmingPool(3, 4);

Console.WriteLine(Utils.Area(house));
Console.WriteLine(Utils.Area(pool));

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

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

В вашем примере интерфейс создан, потому что тогда все, что IS A Pizza, что означает реализацию интерфейса Pizza, гарантированно будет реализовано

public void Order();

После упомянутого вами кода вы можете получить что-то вроде этого:

public void orderMyPizza(IPizza myPizza) {
//This will always work, because everyone MUST implement order
      myPizza.order();
}

Таким образом, вы используете полиморфизм, и все, что вас волнует, - это то, что ваши объекты реагируют на order().

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

Возможно, вы знакомы с принципами SOLID объектно-ориентированного проектирования. В итоге:

S - Принцип единой ответственности O - Открытый / закрытый принцип L - Лисковский принцип замещения I - Принцип разделения интерфейса D - Принцип инверсии зависимостей

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

"Управление зависимостями является ключевой проблемой в программном обеспечении в любом масштабе" (Дональд Кнут)

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

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

Интерфейс определяет контракт между поставщиком определенной функциональности и соответствующими потребителями. Он отделяет реализацию от контракта (интерфейса). Вы должны взглянуть на объектно-ориентированную архитектуру и дизайн. Вы можете начать с Википедии: https://en.wikipedia.org/wiki/Interface_(computing)

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

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

interface ICRUD{
      void InsertData(); // will insert data
      void UpdateData(); // will update data
      void DeleteData(); // will delete data
}

Важное примечание: интерфейсы ВСЕГДА общедоступны.

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

Так много ответов! Я делаю лучший снимок. хе-хе.

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

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

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

Теперь вы могли бы сказать, почему бы не использовать абстрактный класс?

Итак, когда вам нужно добиться 100% абстракции, вам нужно использовать . Но когда вам нужна абстракция вместе с конкретной реализацией, используйте класс. Это значит.

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

      public abstract class Pizza{
    // concrete method which is common to all pizzas.
    public PizzaBase PreparePizzaBase(){
        // code for pizza base preparation.
    }
    public abstract void Prepare();
}

public class DeluxePizza: Pizza{
    public void Prepare(){
        var base=PreparePizzaBase();
        // prepare deluxe pizza on pizza base.
    }
}
class Program {
    static void Main(string[] args) {
        IMachine machine = new Machine();
        machine.Run();
        Console.ReadKey();
    }

}

class Machine : IMachine {
    private void Run() {
        Console.WriteLine("Running...");
    }
    void IMachine.Run() => Run();
}

interface IMachine
{
    void Run();
}

Позвольте мне описать это с другой точки зрения. Давайте создадим историю по примеру, который я показал выше;

Программа, Машина и IMachine - действующие лица нашей истории. Программа хочет работать, но у нее нет такой способности, а Машина умеет работать. Machine и IMachine - лучшие друзья, но Program не разговаривает с Machine. Итак, Program и IMachine заключили сделку и решили, что IMachine сообщит Program, как работать, глядя на Machine(как на отражатель).

Программа учится запускать с помощью IMachine.

Интерфейс обеспечивает общение и разработку слабосвязанных проектов.

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

Для меня преимущество / преимущество интерфейса в том, что он более гибкий, чем абстрактный класс. Поскольку вы можете наследовать только 1 абстрактный класс, но можете реализовать несколько интерфейсов, изменения в системе, которая наследует абстрактный класс во многих местах, становятся проблематичными. Если оно унаследовано в 100 местах, изменение требует изменений для всех 100. Но с помощью интерфейса вы можете поместить новое изменение в новый интерфейс и просто использовать этот интерфейс там, где это необходимо (Интерфейсная последовательность из SOLID). Кроме того, использование памяти выглядит так, как если бы интерфейс был меньше, поскольку объект в примере интерфейса используется только один раз в памяти, несмотря на то, сколько мест реализует интерфейс.

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

O = "Классы должны быть открыты для расширения, но закрыты для модификации"

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

Там есть действительно хорошие примеры.

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

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

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

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

Самый простой способ думать об интерфейсах - это распознать, что означает наследование. Если класс CC наследует класс C, это означает, что:

  1. Класс CC может использовать любые открытые или защищенные члены класса C, как если бы они были его собственными, и, таким образом, ему нужно только реализовать вещи, которых нет в родительском классе.
  2. Ссылка на CC может быть передана или присвоена подпрограмме или переменной, которая ожидает ссылку на C.

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

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

Одна хорошая особенность этого (недостаточно используется в.net framework, IMHO) заключается в том, что они позволяют декларативно указывать, что может делать объект. Некоторые объекты, например, будут нуждаться в объекте источника данных, из которого они могут извлекать вещи по индексу (как это возможно в List), но им не нужно ничего там хранить. Другие подпрограммы будут нуждаться в объекте хранилища данных, где они могут хранить вещи не по индексу (как в Collection.Add), но им не нужно будет что-либо читать обратно. Некоторые типы данных разрешают доступ по индексу, но не разрешают запись; другие позволят писать, но не разрешат доступ по индексу. Некоторые, конечно, позволят и то и другое.

Если бы ReadableByIndex и Appendable были несвязанными базовыми классами, было бы невозможно определить тип, который мог бы передаваться как вещам, ожидающим ReadableByIndex, так и вещам, ожидающим Appendable. Можно попытаться смягчить это, используя ReadableByIndex или Appendable, производные от другого; производный класс должен был бы сделать доступными общедоступные члены для обеих целей, но предупредите, что некоторые общедоступные члены могут фактически не работать. Некоторые классы и интерфейсы Microsoft делают это, но это довольно странно. Более чистый подход состоит в том, чтобы иметь интерфейсы для различных целей, а затем объекты должны реализовывать интерфейсы для того, что они действительно могут делать. Если бы у одного был интерфейс IReadableByIndex, а у другого интерфейса IAppendable, классы, которые могли бы делать один или другой, могли бы реализовать соответствующие интерфейсы для вещей, которые они могут сделать.

Другие вопросы по тегам