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


269

Причина интерфейсов действительно ускользает от меня. Насколько я понимаю, это своего рода обходной путь для несуществующего мульти-наследования, которого нет в 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
    }
}

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

7
стараясь быть хорошим и аккуратным пользователем, я склонен искать ответ сначала на разных форумах, прежде чем что-то публиковать. К сожалению, большинство из них началось на более позднем этапе, а остальные не помогли. Я уже боролся с основным "Зачем это делать?" как мне показалось, излишне усложнять это. Btw. Спасибо всем за очень быстрые ответы. Я должен сначала переварить их все, но я думаю, что теперь у меня есть достаточно хорошее представление о них. Кажется, я всегда смотрел на это с другой стороны. Большое спасибо за твою помощь.
Небелхом


1
Также интерфейсы помогают установить наследование как для structтипов.
ja72

3
Хм, ОП спрашивал: «Из того, что я понимаю, интерфейс - это своего рода обходной путь для несуществующего мульти-наследования, которого нет в C #. (Кроме того, в приведенном примере с пиццей из учебника) я бы просто использовать базовый класс вместо интерфейса ". И затем большинство ответов либо приводили пример, который может быть реализован (абстрактным) базовым классом, либо приводили пример, показывающий, как интерфейс необходим для сценария множественного наследования. Все эти ответы хороши, но разве они не повторяют то, что уже знает ОП? Неудивительно, что OP выбрал ответ без примеров. LOL
RayLuo

Ответы:


176

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

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

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

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


1
+1, хотя я бы не сказал, что интерфейс (в смысле контракта) может быть абстрактным или обычным классом.
Гру

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

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

1
Что ж, тогда возникает вопрос: «Зачем использовать статически типизированные языки, когда динамически типизированные языки легче программировать?». Хотя я не эксперт, чтобы ответить на этот вопрос, я могу сказать, что производительность является решающим вопросом. Когда выполняется вызов объекта Python, процесс должен решить во время выполнения, есть ли у этого объекта метод с именем «Order», тогда как, если вы делаете вызов объекту C #, уже установлено, что он реализует этот метод, и Звонить можно по такому адресу.
Болук Папуккуоглу

3
@BolucPapuccuoglu: Кроме того, со статически типизированным объектом, если кто-то знает, fooреализует IWidget, тогда программист, увидевший вызов, foo.woozle()может посмотреть документацию IWidgetи узнать, что должен делать этот метод . Программист может не знать, откуда поступит код для фактической реализации, но любой тип, соответствующий IWidgetконтракту интерфейса, будет реализован fooв соответствии с этим контрактом. В динамическом языке, напротив, не было бы четкого ориентира для того, что foo.woozle()должно означать.
суперкат

440

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

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

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

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


11
+1, мне понравился пример использования List для демонстрации использования интерфейса.
Даниэль Вуд

21
Хороший ответ, но, возможно, измените его, чтобы уточнить, почему интерфейс в этом случае лучше, чем просто использование абстрактного класса (для такого простого примера абстрактный класс действительно может быть лучше?)
Jez

3
Это лучший ответ. Есть небольшая опечатка, или вы можете просто скопировать и вставить код. В интерфейсе C # вы не объявляете модификаторы доступа, такие как public. Так что внутри интерфейса это должно быть простоvoid Prepare();
Dush

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

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

123

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

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

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

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

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

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


6
Разве это не раздражает, когда вы находите ответ, который пытается сказать, кем вы были, но делает это намного лучше - stackoverflow.com/a/93998/117215
Пэдди

18
Хорошая новость - теперь это мертвая страница, а ваша нет :). Хороший пример!
Sealer_05

1
Ваше обсуждение C немного потеряло меня. Но мне нравятся ваши обсуждения A и B, потому что они по сути объясняют, как можно использовать интерфейсы для обеспечения общих видов функциональности для нескольких классов. Область интерфейсов, которая все еще неясна для меня, - это то, как интерфейсы используются для более слабой связи. Возможно, именно об этом говорилось в вашей дискуссии на C? Если да, то мне нужен более подробный пример :)
user2075599

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

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

33

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

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

27

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

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

2
Мне пришлось прокрутить весь путь вниз, чтобы найти ответ, который фактически отвечает на вопрос «почему бы просто не использовать абстрактный класс вместо интерфейса?». Похоже, что на самом деле нужно обрабатывать только отсутствие множественного наследования в c #.
Синтаксис Правил

2
Да, это лучшее объяснение для меня. Единственный, который четко объясняет, почему интерфейс может быть более полезным, чем абстрактный класс. (то есть: пицца это MenuItem, а также еда, в то время как с абстрактным классом это может быть только один или другой, но не оба)
Maxter

25

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

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

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

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

  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: Преимущества использования интерфейса

Резюме

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

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


4
Плюс один для парня, который приходит на работу в толстовке.
Юша

11

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


3
+1 Правильно, если у вас есть утка, вам не нужны интерфейсы. Но они обеспечивают более строгую безопасность типов.
Гру

11

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

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

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


10

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

Возьмите визуальные элементы управления, например, в .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 ....
}

Да, я знаю, вы не должны использовать «есть», а затем бросить, оставить это в стороне.
Лассе В. Карлсен

4
вздох я знаю, но это случайно.
Лассе В. Карлсен

7

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

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

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

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


6

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



4

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

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

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

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

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

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

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


4

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

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

public void Order();

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

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

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


4

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

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

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

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


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

3

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

public class Car { ... }

public class Tree { ... }

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

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

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

2
Вопрос, который преследует меня в таких примерах: почему это помогает? Могу ли я теперь передать аргумент типа IBurnable методу, и все классы с интерфейсом IBurnable могут быть обработаны? Так же, как пример пиццы, который я нашел. Приятно, что вы можете сделать это, но я не вижу выгоды как таковой. Не могли бы вы расширить пример (потому что я сейчас очень толстый) или привести пример, который бы с ним не работал (опять же, потому что сейчас я чувствую себя действительно толстым). Большое спасибо.
Небелхом

1
Согласен. Интерфейс = "Можно сделать". Класс / Аннотация Calss = "Is A"
Питер. Ван

3

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

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


3

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

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

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

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


1
Этот ответ заслуживает больше очков! Интерфейсы сияют при использовании в разработанных шаблонах, например, в шаблоне состояний. См. Plus.google.com/+ZoranHorvat-Программирование для получения дополнительной информации
Алекс Nolasco

3

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

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-методы) от любого количества интерфейсов.


3

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


3

Какой ?

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

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


Почему вы добавляете ответ на 6-летний вопрос с десятками других ответов, проголосовавших сотни раз? Здесь нет ничего, что еще не было сказано.
Джонатон Рейнхарт

@JonathonReinhart, да, я так и думал, но потом я думал об этом примере, и то, как он объясняется, поможет лучше относиться к одному телу, чем к другим.
Клинт

2

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

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

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

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

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

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

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


1

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


1

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

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

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

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

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


1

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

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

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

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

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

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


1

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

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


1

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


1

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

Я просто приведу пример, поддерживаемый графикой ниже.

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

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

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

public interface IMachine
{
    bool Start();
    bool Stop();
}

public class Car : IMachine
{
    public bool Start()
    {
        Console.WriteLine("Car started");
        return true;
    }

    public bool Stop()
    {
        Console.WriteLine("Car stopped");
        return false;
    }
}

public class Tank : IMachine
{
    public bool Start()
    {
        Console.WriteLine("Tank started");
        return true;
    }

    public bool Stop()
    {
        Console.WriteLine("Tank stopped");
        return false;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var car = new Car();
        car.Start();
        car.Stop();

        var tank = new Tank();
        tank.Start();
        tank.Stop();

    }
}

1
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 - лучшие друзья, но Программа не разговаривает с Machine. Таким образом, Program и IMachine заключили сделку и решили, что IMachine расскажет Программе, как работать с помощью машины (как отражатель).

И программа учится бегать с помощью IMachine.

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

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


1

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

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

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.