Інтерфейси - в чому сенс?


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 # (крім іншого, у цитованому прикладі піци з підручника), я просто використовувати базовий клас замість інтерфейсу ". І тоді більшість відповідей або наводили приклад, який може бути реалізований (абстрактним) базовим класом, або дають приклад, щоб показати, як інтерфейс необхідний для сценарію багатонаступництва. Ці відповіді гарні, але хіба вони просто не повторюють щось, про що вже знає ОП? Недарма ОП вирішив обрати відповідь без прикладів. LOL
RayLuo

Відповіді:


176

Справа в тому, що інтерфейс являє собою договір . Набір загальнодоступних методів, якими повинен володіти будь-який клас реалізації. Технічно інтерфейс керує лише синтаксисом, тобто які методи існують, які аргументи вони отримують і що вони повертають. Зазвичай вони також інкапсулюють семантику, хоча це лише документацією.

Тоді ви можете мати різні реалізації інтерфейсу та замінювати їх за бажанням. У вашому прикладі, оскільки кожен екземпляр піци - це IPizzaви можете використовувати, IPizzaде ви обробляєте екземпляр невідомого типу піци. Будь-який екземпляр, тип якого успадковується від IPizza, гарантовано є упорядкованим, оскільки він має Order()метод.

Python не має статичного типу, тому типи зберігаються та переглядаються під час виконання. Тож ви можете спробувати викликати Order()метод на будь-якому об’єкті. Час виконання є щасливим, доки в об'єкта є такий метод, і, ймовірно, просто знизує плечима і каже «Мех. Не так у C #. Компілятор несе відповідальність за правильні виклики, і якщо у нього просто є випадкові objectвипадки, компілятор ще не знає, чи буде цей примірник під час виконання цього методу. З точки зору компілятора, він недійсний, оскільки не може його перевірити. (Ви можете робити такі речі за допомогою відображення чи dynamicключового слова, але зараз, напевно, це йде далеко).

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


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

3
Я додам, що ви не можете розраховувати зрозуміти інтерфейси за пару хвилин. Я думаю, що нерозумно розуміти інтерфейси, якщо у вас немає років об’єктно-орієнтованого досвіду програмування. Ви можете додати кілька посилань на книги. Я б запропонував: Ін'єкція залежності в .NET, яка справді наскільки глибока пробійна яма, а не просто легке введення.
кнут

2
Ах, тут проблема в тому, що я не маю поняття про DI. Але я думаю, головна проблема запитувача полягала в тому, чому вони потрібні, коли в Python все працює без. Ось що намагався надати найбільший абзац моєї відповіді. Я не думаю, що потрібно копатись у кожному шаблоні та практиці, яка використовує тут інтерфейси.
Joey

1
Ну, тоді ваше запитання стає таким: "Чому використовувати статично набрані мови, коли динамічно набрані мови простіше програмувати?". Хоча я не фахівець, який би відповів на це питання, але я можу ризикнути сказати, що ефективність - це вирішальне питання. Коли здійснюється виклик об’єкту python, процес повинен вирішити протягом часу виконання, чи має цей об'єкт метод під назвою "Порядок", тоді як якщо ви здійснюєте виклик об'єкту C #, то вже встановлено, що він реалізує цей метод, і дзвінок можна здійснити за такою адресою.
Болук Папукуоглу

3
@BolucPapuccuoglu: Крім того, зі статично типовим об'єктом, якщо хтось знає, fooреалізує IWidget, тоді програміст, який бачить дзвінок, foo.woozle()може подивитися документацію IWidgetі знати, що цей метод повинен робити . Програміст може не знати, звідки походить код фактичної реалізації, але будь-який тип, який дотримується IWidgetдоговору інтерфейсу, буде реалізовуватися fooтаким чином, що відповідає цьому контракту. У динамічній мові, навпаки, не було б чіткого орієнтиру, що foo.woozle()має означати.
supercat

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, мені сподобався приклад використання списку для показу інтерфейсу.
danielunderwood

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

3
Це найкраща відповідь. Існує невеликий помилок або ви можете просто скопіювати та вставити код. У інтерфейсі C # ви не оголошуєте модифікаторів доступу, таких як public. Тож всередині інтерфейсу воно повинно бути простоvoid Prepare();
Dush

26
Я не бачу, як це відповідає на питання. Цей приклад міг би бути так само легко виконаний з базовим класом та абстрактним методом, саме на що вказувалося в первинному питанні.
Джеймі Кітсон

4
Інтерфейси насправді не вступають у їхнє право, поки у вас їх багато не буде. Ви можете успадковувати лише один клас, абстрактний чи ні, але ви можете реалізувати стільки різних інтерфейсів в одному класі, скільки вам подобається. Раптом вам не потрібні двері у дверній рамі; все, що буде OOnanable, чи буде це двері, вікно, поштова скринька, ви це назвете.
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);
}

Через два тижні, маркетинг вирішує, що вам також потрібні орки, оскільки вони читають про них на Twitter, тож вам доведеться зробити щось на кшталт:

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, для якої потрібно викликати метод уже в А. Ви не можете це зробити за допомогою конкретних реалізацій, оскільки отримаєте циркулярну посилання.

Ви можете мати інтерфейс, оголошений в B, який реалізує клас у А. Ваш метод в B може бути переданий екземпляр класу, який реалізує інтерфейс без проблем, навіть якщо конкретний об'єкт має тип А.


6
Чи не прикро, коли ви знайдете відповідь, яка намагалася сказати, що ви були, але чи набагато краще - stackoverflow.com/a/93998/117215
Падді

18
Хороша новина - це мертва сторінка, а ваша - не :). Хороший приклад!
Sealer_05

1
Ваша дискусія на С трохи втратила мене. Але мені подобаються ваші дискусії A і B, тому що вони по суті пояснюють, як інтерфейси можна використовувати для надання загальних видів функціональності для кількох класів. Площа інтерфейсів, яка досі нечітка для мене, - це те, як інтерфейси використовуються для більш слабкого з'єднання. Можливо, саме це стосувалося вашої дискусії C? Якщо так, я думаю, мені потрібен більш детальний приклад :)
user2075599

1
Мені здається, у вашому прикладі, що ICreature було б краще підходити до абстрактного базового класу (Creature?) Для тролів та орків. Тоді там може бути реалізована будь-яка загальна логіка між істотами. Що означає, що вам взагалі не потрібен інтерфейс ICreature ...
Skarsnik

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 #.
SyntaxRules

2
Так, це найкраще пояснення для мене. Єдиний, який чітко пояснює, чому інтерфейс може бути кориснішим, ніж абстрактний клас. (тобто: піца - це MenuItem AND - це також їжа, а з абстрактним класом це може бути лише те чи інше, але не те і інше)
Maxter

25

Просте пояснення з аналогією

Проблема, яку потрібно вирішити: яка мета поліморфізму?

Аналогія: Тож я є людиною на будівельному майданчику.

Торговці весь час гуляють на будівельному майданчику. Я не знаю, хто збирається пройти через ці двері. Але я в основному кажу їм, що робити.

  1. Якщо це столяр, я кажу: будуйте дерев'яні риштування.
  2. Якщо це сантехнік, я кажу: "Налаштуйте труби"
  3. Якщо це електрик, я кажу: "Вийміть кабелі та замініть їх волоконно-оптичними".

Проблема вищезгаданого підходу полягає в тому, що я повинен: (i) знати, хто ходить у ті двері, і залежно від того, хто це, я повинен сказати їм, що робити. Це означає, що я повинен знати все про конкретну торгівлю. З цим підходом пов'язані витрати / вигоди:

Наслідки знати, що робити:

  • Це означає, що якщо код столяра зміниться з: BuildScaffolding()на BuildScaffold()(тобто незначна зміна імені), тоді мені доведеться також змінити клас виклику (тобто Forepersonклас) - вам доведеться внести дві зміни в код замість (в основному ) тільки один. З поліморфізмом вам (в основному) потрібно внести лише одну зміну, щоб досягти того ж результату.

  • По-друге, вам не доведеться постійно запитувати: хто ви? гаразд це ... хто ти? ОК, зробіть це ..... поліморфізм - це ДРУГИ цей код і дуже ефективний у певних ситуаціях:

  • за допомогою поліморфізму ви можете легко додавати додаткові класи торговців, не змінюючи жодного існуючого коду. (тобто другий із принципів проектування SOLID: принцип відкритого закриття).

Рішення

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

Перевага такого підходу полягає в тому, що: (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 (). По-друге, якщо новий будівельний працівник виходить на місце роботи, тоді бригадиру нічого не потрібно знати про торгівлю - все бригадир дбає, якщо будівельник (.eg Welder, Glazier, Tiler тощо) може завершити роботу ().


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

Немає інтерфейсу (Приклад 1):

Приклад 1: без інтерфейсу

Немає інтерфейсу (Приклад 2):

Приклад 2: без інтерфейсу

З інтерфейсом:

Приклад 3: Переваги використання інтерфейсу

Підсумок

Інтерфейс дозволяє змусити людину виконувати роботу, яку їм призначено, без того, щоб ви знали, хто саме вони є, або особливості того, що вони можуть зробити. Це дозволяє легко додавати нові типи (торгівлі), не змінюючи існуючий код (ну технічно ви це змінюєте крихітним бітком), і це справжня користь підходу OOP проти більш функціональної методології програмування.

Якщо ви нічого не розумієте з вищезазначеного або якщо вам не зрозуміло, запитайте в коментарі, і я спробую зробити відповідь кращою.


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

11

За відсутності набору качок, оскільки ви можете використовувати її в Python, C # покладається на інтерфейси для надання абстракцій. Якщо залежності класу були всі конкретні типи, ви не могли передати жоден інший тип - використовуючи інтерфейси, ви можете передавати будь-який тип, який реалізує інтерфейс.


3
+1 Правильно, якщо ви набираєте качок, вам не потрібні інтерфейси. Але вони забезпечують більш високу безпеку типу.
Groo

11

Приклад піци поганий, тому що ви повинні використовувати абстрактний клас, який обробляє замовлення, а піци повинні просто переохочувати тип піци, наприклад.

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

Інтерфейс - це лише договір, який повідомляє вам про деякі речі, які може зробити об'єкт, які параметри та типи повернення очікувати.


10

Розглянемо випадок, коли ви не контролюєте або не володієте базовими класами.

Наприклад, візуальні елементи керування, наприклад, у .NET for Winforms вони успадковують від базового класу Control, який повністю визначений у .NET-рамках.

Припустимо, ви займаєтеся створенням спеціальних елементів керування. Ви хочете створити нові кнопки, текстові поля, перегляди списків, сітки, що-небудь, і ви хочете, щоб вони мали певні функції, унікальні для вашого набору елементів управління.

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

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

Замість цього ви зійдете з кнопки, 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

Інтерфейс - це дійсно договір, якого повинні дотримуватися класи впровадження, він фактично є базовим для майже всіх моделей дизайну, які я знаю.

У вашому прикладі інтерфейс створений, оскільки тоді гарантовано, що все, що є Pizza, тобто реалізує інтерфейс Pizza,

public void Order();

Після згаданого коду у вас може бути щось подібне:

public void orderMyPizza(IPizza myPizza) {
//This will always work, because everyone MUST implement order
      myPizza.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
Погодьтеся. Інтерфейс = "Можна зробити". Class / Abstract Calss = "Is A"
Peter.Wang

3

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

Тепер, коли ви знаєте, що таке інтерфейси, просто код без них. Рано чи пізно у вас виникне проблема, де використання інтерфейсів буде найприроднішим ділом.


3

Я здивований, що не так багато публікацій містить одну найважливішу причину інтерфейсу: Шаблони дизайну . Це більша картина використання контрактів, і хоча це синтаксичне прикраса машинного коду (якщо чесно, компілятор, ймовірно, просто їх ігнорує), абстракція та інтерфейси є ключовими для OOP, людського розуміння та складної системної архітектури.

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

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

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


1
Ця відповідь заслуговує більше балів! Інтерфейси блищать при використанні в розроблених моделях, наприклад, The State Pattern. Див. Plus.google.com/+ZoranHorvat-програмування для додаткової інформації
Alex 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));

Таким чином ваші класи можуть "успадкувати" поведінку (статичні методи) з будь-якої кількості інтерфейсів.


3

Інтерфейс визначає договір між провайдером певної функціональності та споживачами-кореспондентами. Це відокремлює виконання від контракту (інтерфейс). Ви повинні подивитися на об'єктно-орієнтовану архітектуру та дизайн. Ви можете почати з wikipedia: http://en.wikipedia.org/wiki/Interface_(computing)


3

Що ?

Інтерфейси - це в основному договір, якому повинні слідувати всі класи, що реалізують Інтерфейс. Вони схожі на клас, але не мають реалізації.

У C#назвах інтерфейсів за умовами визначається префіксація "Я", тому якщо ви хочете мати інтерфейс під назвою фігури, ви оголосите його як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

Тереза ​​просять справді чудові приклади.

Інший, що стосується оператора перемикання, у вас більше немає необхідності підтримувати та перемикати кожен раз, коли ви хочете, щоб ріо виконував завдання певним чином.

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

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

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


1

Найпростіший спосіб подумати про інтерфейси - це визнати, що означає успадкування. Якщо клас CC успадковує клас C, це означає і те, що:

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

Ці дві функції успадкування в деякому сенсі незалежні; хоча спадкування застосовується як одночасно, так і друге можна застосувати без першого. Це корисно, тому що дозволити об'єкту успадковувати членів з двох або більше неспоріднених класів набагато складніше, ніж дозволяти одному типу речей замінювати кілька типів.

Інтерфейс дещо схожий на абстрактний базовий клас, але з ключовою різницею: об’єкт, який успадковує базовий клас, не може успадкувати жоден інший клас. Навпаки, об’єкт може реалізувати інтерфейс, не впливаючи на його здатність успадкувати будь-який бажаний клас або реалізувати будь-які інші інтерфейси.

Однією приємною особливістю цього (недостатньо використовуваного в .net рамках, IMHO) є те, що вони дозволяють декларативно вказати на те, що об'єкт може зробити. Деякі об'єкти, наприклад, захочуть об'єкт-джерело даних, з якого вони можуть отримати речі за індексом (як це можливо зі списком), але їм там нічого не потрібно буде зберігати. Іншим процедурам знадобиться об’єкт сховища даних, де вони можуть зберігати речі не за індексом (як у Collection.Add), але їм нічого не потрібно буде читати назад. Деякі типи даних дозволять отримати доступ за індексами, але не дозволяють писати; інші дозволять писати, але не дозволятимуть доступ за індексом. Деякі, звичайно, дозволять і те, і інше.

Якщо ReadableByIndex і Appendable були не пов'язаними базовими класами, неможливо було б визначити тип, який може бути переданий як речам, які очікують ReadableByIndex, так і речам, які очікують на додавання. Можна спробувати пом'якшити це, отримавши ReadableByIndex або Appendable від іншого; похідний клас повинен був би бути доступним для публічних членів для обох цілей, але попередити, що деякі громадські члени можуть насправді не працювати. Деякі з класів та інтерфейсів Microsoft це роблять, але це досить прискіпливо. Більш чіткий підхід - мати інтерфейси для різних цілей, а потім об'єкти реалізувати інтерфейси для речей, які вони насправді можуть робити. Якщо один мав інтерфейс IReadableByIndex, а інший інтерфейс IAppendable,


1

Інтерфейси також можуть бути ромашковими, щоб створити ще один інтерфейс. Ця можливість впроваджувати декілька інтерфейсів дає перевагу розробнику додавати функціональність до своїх класів, не змінюючи функціональність поточного класу (принципи SOLID)

O = "Класи повинні бути відкритими для розширення, але закритими для модифікації"


1

Для мене перевагою / перевагою інтерфейсу є те, що він є більш гнучким, ніж абстрактний клас. Оскільки ви можете успадкувати лише 1 абстрактний клас, але ви можете реалізувати декілька інтерфейсів, зміни в системі, яка успадковує абстрактний клас у багатьох місцях, стають проблематичними. Якщо він успадковується в 100 місцях, для зміни потрібні зміни на всі 100. Але, використовуючи інтерфейс, ви можете розмістити нову зміну в новому інтерфейсі і просто використовувати той інтерфейс там, де це потрібно (Interface Seq. Від SOLID). Крім того, використання пам'яті, здається, було б менше з інтерфейсом, оскільки об'єкт у прикладі інтерфейсу використовується лише один раз у пам'яті, незважаючи на те, скільки місць реалізує інтерфейс.


1

Інтерфейси використовуються для керування послідовністю таким чином, що вони зв'язані слабко, що робить його відмінним від абстрактного класу, який є щільно сполученим. Тому його також зазвичай визначають як контракт. Будь-які класи, що реалізують інтерфейс, дотримуються "правил / синтаксису" визначений інтерфейсом, і в ньому немає конкретних елементів.

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

Уявіть, що на заводі є 3 типи машин. Машина прямокутника, трикутник і багатокутник. Часи є конкурентоспроможними, і ви хочете впорядкувати навчання операторів. Ви просто хочете навчити їх за однією методикою запуску і зупинки машин, щоб мати зелену кнопку запуску та червону кнопку зупинки. Отже, на трьох різних машинах у вас є послідовний спосіб запуску та зупинки 3-х різних типів машин. Тепер уявіть собі, що ці машини - це класи, і класи повинні мати методи запуску та зупинки, як ви збираєтеся керувати послідовністю в цих класах, які можуть бути дуже різними? Інтерфейс - це відповідь.

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

Простий приклад, який допоможе вам візуалізувати, можна запитати, чому б не використовувати абстрактний клас? За допомогою інтерфейсу об'єкти не повинні бути безпосередньо пов’язані або успадковані, і ви все одно можете керувати послідовністю у різних класах.

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

Дозвольте описати це з іншого погляду. Створимо розповідь за прикладом, який я показав вище;

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

І програма вчиться запускатись за допомогою 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.