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

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

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

В приведенном ниже примере, взятом из другого потока интерфейсов С# при переполнении стека, я бы просто создал базовый класс с именем 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
    }
}

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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.
        }
    }
}

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

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

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 автоматически подготовит каждую пиццу в соответствии с ее типом, даже если коллекция имеет несколько типов пицц.

Ответ 3

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

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

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

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

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

// 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.

Ответ 4

Ниже приведены ваши примеры:

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
    }
}

Ответ 5

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

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();
    }
}

Ответ 6

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

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

Аналогия: Итак, я нападающий на строительной площадке.

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

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

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

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

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

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

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

Решение

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

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

Поэтому вместо этого:

If(electrician) then  electrician.FixCablesAndElectricity() 

if(plumber) then plumber.IncreaseWaterPressureAndFixLeaks() 

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

ITradesman tradie = Tradesman.Factory(); // in reality i know it a plumber, but in the real world you won't know who 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()!!

Какая польза?

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


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

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

Example 1: without an interface

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

Example 2: without an interface

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

Example 3: The benefits of Using an Interface

Резюме

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

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

Ответ 7

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

Ответ 8

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

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

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

Ответ 9

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

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

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

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

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

Вместо этого вы спуститесь с Button, TextBox, ListView, GridView и т.д. и добавьте свой код.

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

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

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

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

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

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 ....
}

Ответ 10

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

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

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

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

Ответ 11

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

Ответ 13

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

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

public void Order();

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

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

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

Ответ 14

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

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

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

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

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

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

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

Ответ 15

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

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

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

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

Ответ 16

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

Ответ 17

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

public class Car { ... }

public class Tree { ... }

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

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

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

Ответ 18

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

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

Ответ 19

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

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

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

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

Ответ 20

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

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));

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

Ответ 21

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

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

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

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

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

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

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

Ответ 22

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

Ответ 23

Тереза ​​спрашивают действительно отличные примеры.

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

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

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

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

Ответ 24

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

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

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

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

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

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

Ответ 25

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

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

Ответ 26

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

Ответ 27

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

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

Ответ 28

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

public class Robot{

    public void ShootPeopleWith( weapon IWeapon ){
        weapon.PressTrigger()
    }
}

interface IWeapon{
   PressTrigger()
}

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

Теперь прибывает дрон, который инструктирует Робота стрелять в людей. Это также обеспечивает это оружием.

public class Drone{
    List<Robots> allRobotsInArea = someList

    public void DelegateARobot(){
        robot = select a robot from allRobotsInArea
        IWeapon weapon = new MachineGun();    //procuring a machine gun
        robot.ShootPeopleWith(weapon);
    }

}

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

public class MachineGun : IWeapon{
    public void PressTrigger(){
        Fire40RoundsPerSecond();
    }
    ...
}

public class Sniper() : IWeapon{
    public void PressTrigger(){
        SayQuackQauck();
    }
}

Дрон теперь может легко передать любое оружие, такое как Снайпер и Пулемет, роботу.

public class Pumpkin{
    public void FreakPeopleOut(){
        GlowInTheDark();
    }
}

Дрон не может передать Тыкву, даже если он может использоваться как оружие как robot.ShootPeopleWith(new Pumpkin());, потому что у него нет триггера и он нарушает контракт, который ожидает Робот. Теперь вы можете утверждать, что почему робот не может ожидать оружие родительского класса, а не интерфейс IWeapon? Потому что человек, пишущий класс Robot, не хочет определять родительский класс. Человек, потребляющий Робота, в идеале должен определить родительский класс, если он еще не существует, основываясь на контракте, который Робот ожидает, если он хочет использовать Робота.

Ответ 29

Правильно ли я снова взглянуть на интерфейсы, графический интерфейс (winforms/WPF) подобен интерфейсу. Он отображает только то, с чем взаимодействует конечный пользователь. Затем конечный пользователь не должен знать, что входит в дизайн приложения, но будет знать, что с ним можно сделать, основываясь на доступных опциях в форме. В представлении ООП идея заключается в создании структурированного интерфейса, который информирует других пользователей ваших библиотек DLL, что доступно для использования, и что он как гарантия/контракт, что поле, методы и свойства будут доступны для использования (наследуют ваши классы).

Ответ 30

Я разделяю ваше мнение, что интерфейсы не нужны. Вот цитата из Cwalina pg 80 Framework Design Guidelines "Я часто здесь говорю, что интерфейсы определяют контракты. Я считаю, что это опасный миф. Интерфейсы сами по себе не указывают многое..." Он и соавтор Abrams управляли тремя релизами .Net для Microsoft. Далее он говорит, что "контракт" выражается в реализации класса. IMHO наблюдал за этим на протяжении десятилетий, было много людей, предупреждающих Microsoft о том, что принятие технической парадигмы к max в OLE/COM может показаться хорошим, но ее полезность более непосредственно связана с оборудованием. Особенно в 80-х и 90-х годах были введены кодифицированные стандарты взаимодействия. В нашем интернет-мире TCP/IP мало внимания уделяется аппаратной и программной гимнастике, с которой мы могли бы перейти, чтобы получить "проводные" решения между мейнфреймами, миникомпьютерами и микропроцессорами, из которых ПК были всего лишь небольшим меньшинством. Таким образом, кодирование для интерфейсов и их протоколов привело к вычислительной работе. И интерфейсы управлялись. Но что делает решение X.25 работать с вашим приложением вместе с публикацией рецептов праздников? Я много лет программировал С++ и С#, и я никогда не создавал его один раз.