Interfejsy - o co chodzi?


269

Powód interfejsów naprawdę mi umyka. Z tego, co rozumiem, jest to rodzaj obejścia nieistniejącego dziedziczenia, które nie istnieje w C # (a przynajmniej tak mi powiedziano).

Widzę tylko, że predefiniujesz niektóre elementy i funkcje, które następnie trzeba ponownie zdefiniować w klasie. W ten sposób interfejs jest zbędny. To po prostu wydaje się składniowe… cóż, śmiecie dla mnie (proszę, nie obrażaj mnie. Śmieci jak w bezużytecznych rzeczach).

W poniższym przykładzie zaczerpniętym z innego wątku interfejsu C # na przepełnieniu stosu, po prostu utworzę klasę bazową o nazwie Pizza zamiast interfejsu.

prosty przykład (wzięty z innego wkładu przepełnienia stosu)

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

Mam wrażenie, że są tutaj duplikaty tego pytania na SO, ale wszystkie wydają się wyjaśniać tylko część kontraktową interfejsu, więc nie jestem pewien, czy mają zastosowanie.
Lasse V. Karlsen

7
starając się być miłym i uporządkowanym użytkownikiem, zwykle szukam odpowiedzi na różnych forach, zanim coś opublikuję. Niestety większość z nich rozpoczęła się na późniejszym etapie, a reszta nie pomogła. Walczyłem już z podstawowym „Dlaczego to robisz?” wydawało mi się to niepotrzebnym komplikowaniem. Btw. Dziękujemy wszystkim za bardzo szybkie odpowiedzi. Najpierw muszę je wszystkie przetrawić, ale myślę, że mam teraz dość dobry pogląd na ich sens. Wygląda na to, że zawsze patrzyłem na to z innej perspektywy. Bardzo dziękuję za Twoją pomoc.
Nebelhom,


1
Również interfejsy pomagają ustalić dziedziczenie jak dla structtypów.
ja72

3
Hmm, OP pytał: „Z tego, co rozumiem, interfejs jest swego rodzaju obejściem dla nieistniejącego dziedziczenia, które nie istnieje w języku C #. (Poza tym, w cytowanym podręczniku na przykładzie pizzy) po prostu użyj klasy bazowej zamiast interfejsu ". A następnie większość odpowiedzi podała przykład, który może być zaimplementowany przez (abstrakcyjną) klasę podstawową, lub podała przykład pokazujący, jak interfejs jest niezbędny w scenariuszu wielokrotnego dziedziczenia. Wszystkie te odpowiedzi są dobre, ale czy nie powtarzają po prostu czegoś, co OP już wie? Nic dziwnego, że OP ostatecznie wybrało odpowiedź bez przykładów. LOL
RayLuo

Odpowiedzi:


176

Chodzi o to, że interfejs reprezentuje umowę . Zestaw metod publicznych, które musi posiadać każda klasa implementująca. Technicznie interfejs rządzi tylko składnią, tzn. Jakie są metody, jakie argumenty otrzymują i co zwracają. Zwykle zawierają również semantykę, chociaż tylko w dokumentacji.

Następnie możesz mieć różne implementacje interfejsu i wymieniać je do woli. W twoim przykładzie, ponieważ każda instancja pizzy jest taka, z IPizzaktórej możesz korzystać IPizzawszędzie tam, gdzie masz do czynienia z instancją o nieznanym typie pizzy. Każda instancja, której typ dziedziczy, IPizzajest gwarantowana jako uporządkowana, ponieważ ma Order()metodę.

Python nie jest typowany statycznie, dlatego typy są zachowywane i sprawdzane w czasie wykonywania. Możesz więc spróbować wywołać Order()metodę na dowolnym obiekcie. Środowisko wykonawcze jest szczęśliwe, dopóki obiekt ma taką metodę i prawdopodobnie tylko wzrusza ramionami i mówi „Meh.”, Jeśli nie ma. Nie tak w C #. Kompilator jest odpowiedzialny za wykonywanie poprawnych wywołań, a jeśli ma tylko trochę losowości, objectkompilator nie wie jeszcze, czy instancja w czasie wykonywania będzie miała tę metodę. Z punktu widzenia kompilatora jest on nieprawidłowy, ponieważ nie może go zweryfikować. (Możesz robić takie rzeczy za pomocą refleksji lub dynamicsłowa kluczowego, ale myślę, że teraz idzie to trochę daleko).

Zauważ też, że interfejs w zwykłym znaczeniu niekoniecznie musi być C # interface, może to być również klasa abstrakcyjna lub nawet normalna (co może się przydać, jeśli wszystkie podklasy muszą współdzielić jakiś wspólny kod - w większości przypadków interfacewystarcza jednak ).


1
+1, chociaż nie powiedziałbym, że interfejs (w sensie kontraktu) może być klasą abstrakcyjną lub normalną.
Groo,

3
Dodam, że nie można spodziewać się zrozumienia interfejsów za kilka minut. Myślę, że zrozumienie interfejsów nie jest rozsądne, jeśli nie masz wieloletniego doświadczenia w programowaniu obiektowym. Możesz dodać linki do książek. Sugerowałbym: Wstrzyknięcie zależności w .NET, czyli tak naprawdę głębokość dziury rabit, a nie tylko delikatne wprowadzenie.
knut

2
Ach, jest problem z tym, że nie mam pojęcia o DI. Myślę jednak, że głównym problemem pytającego było to, dlaczego są potrzebne, gdy w Pythonie wszystko działa bez. Właśnie to próbował podać największy akapit mojej odpowiedzi. Nie sądzę, że konieczne jest kopanie w każdym schemacie i ćwiczeniu, które wykorzystuje tutaj interfejsy.
Joey,

1
Cóż, wtedy twoje pytanie brzmi: „Po co używać języków o typie statycznym, gdy łatwiej jest programować w językach dynamicznych?”. Chociaż nie jestem ekspertem, aby odpowiedzieć na to pytanie, mogę zaryzykować stwierdzenie, że decydującą kwestią jest wydajność. Gdy wykonywane jest wywołanie obiektu Python, proces musi zdecydować w czasie wykonywania, czy ten obiekt ma metodę o nazwie „Kolejność”, natomiast jeśli wykonasz wywołanie do obiektu C #, już ustalono, że implementuje on tę metodę oraz można zadzwonić na taki adres.
Boluc Papuccuoglu

3
@BolucPapuccuoglu: Poza tym, ze statycznie typowanym obiektem, jeśli ktoś zna fooimplementacje IWidget, programista widzi wezwanie, aby foo.woozle()przejrzeć dokumentację IWidgeti wiedzieć, co ma zrobić ta metoda . Programista może nie mieć możliwości dowiedzenia się, skąd pochodzi kod dla rzeczywistej implementacji, ale każdy typ zgodny z umową IWidgetinterfejsu zostanie wdrożony foow sposób zgodny z tą umową. Natomiast w dynamicznym języku nie byłoby wyraźnego punktu odniesienia dla tego, co foo.woozle()powinno znaczyć.
supercat

440

Nikt tak naprawdę nie wyjaśnił wprost, w jaki sposób interfejsy są użyteczne, więc dam temu szansę (i ukradnę pomysł z odpowiedzi Shamima).

Pomyślmy o usłudze zamawiania pizzy. Możesz mieć wiele rodzajów pizzy, a wspólne działanie dla każdej pizzy przygotowuje zamówienie w systemie. Każda pizza musi być przygotowana, ale każda pizza jest przygotowywana inaczej . Na przykład, gdy zamawia się pizzę z nadziewaną skórką, system prawdopodobnie musi sprawdzić, czy niektóre składniki są dostępne w restauracji i odłożyć te, które nie są potrzebne do pizzy z głębokim daniem.

Pisząc to w kodzie, technicznie możesz po prostu to zrobić

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

Jednak pizze głębokie (w języku C #) mogą wymagać ustawienia innych właściwości Prepare()niż nadziewana skorupa, w wyniku czego otrzymujesz wiele opcjonalnych właściwości, a klasa nie skaluje się dobrze (co jeśli dodasz nowy rodzaje pizzy).

Właściwym sposobem rozwiązania tego jest użycie interfejsu. Interfejs deklaruje, że wszystkie pizze można przygotować, ale każdą pizzę można przygotować inaczej. Więc jeśli masz następujące interfejsy:

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

Teraz kod obsługi zamówień nie musi dokładnie wiedzieć, jakie rodzaje pizzy zostały zamówione, aby obsłużyć składniki. Ma po prostu:

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

Mimo że każdy rodzaj pizzy jest przygotowywany inaczej, ta część kodu nie musi dbać o to, z jakim typem pizzy mamy do czynienia, po prostu wie, że jest wywoływana do pizzy i dlatego każde połączenie Prepareautomatycznie przygotuje każdą pizzę poprawnie w oparciu o jego typ, nawet jeśli kolekcja zawiera wiele rodzajów pizzy.


11
+1, podoba mi się przykład użycia listy do pokazania użycia interfejsu.
danielunderwood

21
Dobra odpowiedź, ale być może poprawię ją, aby wyjaśnić, dlaczego interfejs w tym przypadku jest lepszy niż zwykłe użycie klasy abstrakcyjnej (na tak prosty przykład klasa abstrakcyjna może być rzeczywiście lepsza?)
Jez

3
To najlepsza odpowiedź. Jest trochę literówki lub możesz po prostu skopiować i wkleić kod. W interfejsie C # nie deklarujesz modyfikatorów dostępu takich jak public. Tak więc w interfejsie powinno być po prostuvoid Prepare();
Dush,

26
Nie rozumiem, jak to odpowiada na pytanie. Przykład ten można równie łatwo wykonać klasą bazową i metodą abstrakcyjną, co dokładnie wskazywało pierwotne pytanie.
Jamie Kitson,

4
Interfejsy tak naprawdę nie mają racji, dopóki nie pojawi się ich wiele. Możesz dziedziczyć tylko z jednej klasy, abstrakcyjnej lub nie, ale możesz zaimplementować tyle różnych interfejsów w jednej klasie, ile chcesz. Nagle nie potrzebujesz drzwi w ościeżnicy; zrobi wszystko, co IOpenable, czy to drzwi, okno, skrzynka na listy, jak to nazwiesz.
mtnielsen

123

Dla mnie, kiedy zaczynam, ich znaczenie stało się jasne, kiedy przestajesz patrzeć na nie jako na rzeczy ułatwiające / przyspieszające pisanie kodu - to nie jest ich cel. Mają wiele zastosowań:

(To straci analogię do pizzy, ponieważ wizualizacja zastosowania tego nie jest łatwa)

Załóżmy, że tworzysz prostą grę na ekranie i będzie ona zawierała stworzenia, z którymi będziesz współdziałać.

Odp .: Mogą ułatwić utrzymanie kodu w przyszłości, wprowadzając luźne połączenie między interfejsem użytkownika a implementacją zaplecza.

Możesz to napisać na początek, ponieważ będą tylko trolle:

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

Przód:

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

    aTroll.Walk(1);
}

Dwa tygodnie później marketing decyduje, że potrzebujesz również Orków, ponieważ czytają o nich na Twitterze, więc musisz zrobić coś takiego:

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

Przód:

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

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

          case Troll:

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

I możesz zobaczyć, jak zaczyna się robić bałagan. Możesz tutaj użyć interfejsu, aby twój interfejs został napisany raz i (tutaj jest ważny bit) przetestowany, a następnie możesz w razie potrzeby podłączyć kolejne elementy zaplecza:

interface ICreature
{
    void Walk(int distance)
}

public class Troll : ICreature
public class Orc : ICreature 

//etc

Interfejs jest wtedy:

void SpawnCreature(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();
}

Interfejs teraz dba tylko o interfejs ICreature - nie przejmuje się wewnętrzną implementacją trolla lub orka, a jedynie faktem, że implementują ICreature.

Ważną kwestią, na którą należy zwrócić uwagę, patrząc na to z tego punktu widzenia, jest to, że można łatwo użyć abstrakcyjnej klasy stworzeń i z tej perspektywy ma to ten sam efekt.

I możesz wyodrębnić kreację do fabryki:

public class CreatureFactory {

 public ICreature GetCreature(creatureType)
 {
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    return creature;
  }
}

A nasz front stałby się wtedy:

CreatureFactory _factory;

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

    creature.Walk();
}

Interfejs użytkownika nie musi już nawet zawierać odwołania do biblioteki, w której zaimplementowano Troll i Orka (pod warunkiem, że fabryka znajduje się w osobnej bibliotece) - nie musi nic o nich wiedzieć.

B: Powiedzmy, że masz funkcjonalność, którą tylko niektóre stworzenia będą miały w twojej jednorodnej strukturze danych , np

interface ICanTurnToStone
{
   void TurnToStone();
}

public class Troll: ICreature, ICanTurnToStone

Front może być wtedy:

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: Zastosowanie do wstrzykiwania zależności

Większość platform wstrzykiwania zależności jest łatwiejsza w obsłudze, gdy istnieje bardzo luźne połączenie między kodem frontonu a implementacją back-endu. Jeśli weźmiemy powyższy przykład fabryki i wdrożymy interfejs w naszej fabryce:

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

Nasz interfejs może wtedy zostać wstrzyknięty (np. Kontroler API MVC) przez konstruktor (zazwyczaj):

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

Za pomocą naszego frameworka DI (np. Ninject lub Autofac) możemy je skonfigurować tak, aby w czasie wykonywania utworzono instancję CreatureFactory, ilekroć w konstruktorze jest potrzebna ICreatureFactory, co czyni nasz kod ładnym i prostym.

Oznacza to również, że kiedy piszemy test jednostkowy dla naszego kontrolera, możemy zapewnić wyśmiewany ICreatureFactory (np. Jeśli konkretna implementacja wymaga dostępu do DB, nie chcemy, aby nasze testy jednostkowe zależały od tego) i łatwo przetestować kod w naszym kontrolerze .

D: Istnieją inne zastosowania, np. Masz dwa projekty A i B, które z przyczyn „starszych” nie są dobrze zorganizowane, a A ma odniesienie do B.

Następnie znajdziesz funkcjonalność w B, która musi wywołać metodę już w A. Nie możesz tego zrobić za pomocą konkretnych implementacji, ponieważ otrzymujesz cykliczne odwołanie.

Możesz zadeklarować interfejs w B, który następnie implementuje klasa A. Do metody w B można przekazać instancję klasy, która bez problemu implementuje interfejs, nawet jeśli konkretny obiekt jest typu w A.


6
Czy to nie denerwujące, gdy znajdziesz odpowiedź, która próbowała powiedzieć, kim jesteś, ale czy jest o wiele lepsza - stackoverflow.com/a/93998/117215
Paddy

18
Dobra wiadomość jest teraz martwa, a twoja nie jest :). Dobry przykład!
Sealer_05

1
Twoja dyskusja w C trochę mnie trochę straciła. Ale podoba mi się wasza dyskusja A i B, ponieważ obie zasadniczo wyjaśniają, w jaki sposób można wykorzystać interfejsy do zapewnienia powszechnych rodzajów funkcjonalności w wielu klasach. Obszar interfejsów, który wciąż jest dla mnie niewyraźny, to sposób, w jaki interfejsy są używane do luźniejszych połączeń. Być może do tego właśnie odnosiła się Twoja dyskusja w C? Jeśli tak, to chyba potrzebuję bardziej szczegółowego przykładu :)
user2075599

1
Wydaje mi się, że w twoim przykładzie ICreature lepiej pasowałaby do bycia abstrakcyjną klasą podstawową (Stworzenie?) Dla Trolla i Orka. Wtedy mogłaby zostać zaimplementowana jakakolwiek wspólna logika między stworzeniami. Co oznacza, że ​​wcale nie potrzebujesz interfejsu ICreature ...
Skarsnik

1
@Skarsnik - całkiem, o co chodzi w tej notatce: „Ważnym punktem, na który należy zwrócić uwagę, patrząc na to z tego punktu widzenia, jest to, że można łatwo użyć abstrakcyjnej klasy stworzeń i z tej perspektywy ma to to samo efekt."
Paddy

33

Oto wyjaśnione przykłady:

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

Powyższe przykłady nie mają większego sensu. Możesz wykonać wszystkie powyższe przykłady za pomocą klas (klasa abstrakcyjna, jeśli chcesz, aby zachowywała się tylko jak kontrakt ):

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

Otrzymujesz takie samo zachowanie jak w przypadku interfejsu. Możesz stworzyć List<Food>i iterować bez wiedzy, która klasa znajduje się na górze.

Bardziej odpowiednim przykładem może być wielokrotne dziedziczenie:

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

Następnie możesz użyć ich wszystkich jako MenuItemi nie przejmować się tym, jak obsługują każde wywołanie metody.

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
Musiałem przewinąć do końca tutaj, aby znaleźć odpowiedź, która faktycznie odpowiada na pytanie „dlaczego nie po prostu użyć abstrakcyjnej klasy zamiast interfejsu?”. Wydaje się, że naprawdę jest to tylko kwestia braku wielokrotnego dziedziczenia c #.
SyntaxRules

2
Tak, to dla mnie najlepsze wytłumaczenie. Jedyny, który jasno wyjaśnia, dlaczego interfejs może być bardziej przydatny niż klasa abstrakcyjna. (tj. pizza to MenuItem AND to także Jedzenie, podczas gdy z klasą abstrakcyjną może być tylko jedno lub drugie, ale nie jedno i drugie)
Maxter

25

Proste objaśnienie z analogią

Problem do rozwiązania: jaki jest cel polimorfizmu?

Analogia: Więc jestem przodkiem na budowie.

Kupcy cały czas chodzą po placu budowy. Nie wiem, kto przejdzie przez te drzwi. Ale w zasadzie mówię im, co mają robić.

  1. Jeśli jest to stolarz, mówię: buduj drewniane rusztowania.
  2. Jeśli to hydraulik, mówię: „Ustaw rury”
  3. Jeśli to elektryk, mówię: „Wyciągnij kable i zamień je na światłowody”.

Problem z powyższym podejściem polega na tym, że muszę: (i) wiedzieć, kto chodzi za tymi drzwiami, i w zależności od tego, kto to jest, muszę powiedzieć im, co mają robić. Oznacza to, że muszę wiedzieć wszystko o danym handlu. Z tym podejściem wiążą się koszty / korzyści:

Implikacje wiedzy, co robić:

  • Oznacza to, że jeśli kod stolarza zmieni się z: BuildScaffolding()na BuildScaffold()(tj. Niewielka zmiana nazwy), wówczas będę musiał również zmienić klasę wywołującą (tj. ForepersonKlasę) - będziesz musiał dokonać dwóch zmian w kodzie zamiast (w zasadzie ) tylko jeden. W przypadku polimorfizmu (w zasadzie) wystarczy dokonać jednej zmiany, aby osiągnąć ten sam wynik.

  • Po drugie, nie będziesz musiał ciągle pytać: kim jesteś? ok, zrób to ... kim jesteś? ok, zrób to ... polimorfizm - SUSZA ten kod i jest bardzo skuteczny w niektórych sytuacjach:

  • dzięki polimorfizmowi możesz łatwo dodawać dodatkowe klasy handlowców bez zmiany istniejącego kodu. (tj. druga z zasad projektowania SOLID: zasada otwartego zamknięcia).

Rozwiązanie

Wyobraź sobie scenariusz, w którym bez względu na to, kto wejdzie do drzwi, mogę powiedzieć: „Work ()” i wykonują swoje szacunkowe prace, w których się specjalizują: hydraulik zajmowałby się rurami, a elektryk drutami.

Zaletą tego podejścia jest to, że: (i) nie muszę dokładnie wiedzieć, kto wchodzi przez te drzwi - wszystko, co muszę wiedzieć, to, że będą rodzajem zawodu i że mogą wykonywać pracę, a po drugie , (ii) nie muszę nic wiedzieć o tej konkretnej transakcji. Tradie się tym zajmie.

Zamiast tego:

If(electrician) then  electrician.FixCablesAndElectricity() 

if(plumber) then plumber.IncreaseWaterPressureAndFixLeaks() 

Mogę zrobić coś takiego:

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()!!

Jaka jest korzyść?

Korzyścią jest to, że jeśli zmienią się specyficzne wymagania stolarza itp., To osoba nadzorująca nie będzie musiała zmieniać kodu - nie musi wiedzieć ani się tym przejmować. Liczy się tylko to, że stolarz wie, co należy rozumieć przez Work (). Po drugie, jeśli nowy typ pracownika budowlanego przyjdzie na miejsce pracy, to brygadzista nie musi nic wiedzieć o handlu - wszystko, co robi brygadzista, to to, czy pracownik budowlany (np. Spawacz, szklarz, glazurnik itp.) Może zrób trochę pracy ().


Ilustrowany problem i rozwiązanie (z interfejsami i bez nich):

Brak interfejsu (Przykład 1):

Przykład 1: bez interfejsu

Brak interfejsu (przykład 2):

Przykład 2: bez interfejsu

Z interfejsem:

Przykład 3: Korzyści z używania interfejsu

Podsumowanie

Interfejs pozwala zmusić osobę do wykonania pracy, do której została przydzielona, ​​bez wiedzy dokładnie, kim ona jest, ani specyfiki tego, co może zrobić. Pozwala to łatwo dodawać nowe typy (handel) bez zmiany istniejącego kodu (technicznie zmieniamy go trochę), a to jest prawdziwa zaleta podejścia OOP w porównaniu z bardziej funkcjonalną metodologią programowania.

Jeśli nie rozumiesz któregokolwiek z powyższych lub jeśli nie jest jasne, poproś w komentarzu, a ja postaram się poprawić odpowiedź.


4
Plus jeden dla faceta, który zaczyna pracę w bluzie z kapturem.
Yusha,

11

W przypadku braku pisania kaczego, ponieważ można go używać w Pythonie, C # opiera się na interfejsach w celu zapewnienia abstrakcji. Gdyby wszystkie zależności były konkretnymi typami, nie można przekazać żadnego innego typu - za pomocą interfejsów można przekazać dowolny typ implementujący interfejs.


3
+1 Zgadza się, jeśli piszesz kaczkę, nie potrzebujesz interfejsów. Ale wymuszają większe bezpieczeństwo typu.
Groo,

11

Przykład pizzy jest zły, ponieważ powinieneś używać klasy abstrakcyjnej, która obsługuje porządkowanie, a pizze powinny po prostu zastąpić typ pizzy.

Interfejsów używasz, gdy masz wspólną właściwość, ale twoje klasy dziedziczą z różnych miejsc lub gdy nie masz wspólnego kodu, którego mógłbyś użyć. Na przykład, używane są rzeczy, które można usunąć IDisposable, wiesz, że zostaną usunięte, po prostu nie wiesz, co się stanie, gdy zostaną usunięte.

Interfejs to po prostu umowa, która mówi ci o pewnych rzeczach, które może zrobić obiekt, jakich parametrów i jakich typów zwrotów się spodziewać.


10

Rozważ przypadek, w którym nie kontrolujesz ani nie posiadasz klas podstawowych.

Weźmy na przykład formanty wizualne. W .NET for Winforms wszystkie dziedziczą po klasach Control, które są całkowicie zdefiniowane w środowisku .NET.

Załóżmy, że zajmujesz się tworzeniem niestandardowych elementów sterujących. Chcesz budować nowe przyciski, pola tekstowe, listy, siatki itp. I chciałbyś, aby wszystkie miały pewne funkcje unikalne dla twojego zestawu kontrolek.

Na przykład może być potrzebny wspólny sposób obsługi tematów lub wspólny sposób obsługi lokalizacji.

W takim przypadku nie możesz „po prostu utworzyć klasy bazowej”, ponieważ jeśli to zrobisz, musisz zaimplementować wszystko , co dotyczy elementów sterujących.

Zamiast tego zejdziesz z Button, TextBox, ListView, GridView itp. I dodasz swój kod.

Ale to stanowi problem, w jaki sposób możesz teraz określić, które kontrolki są „twoje”, jak możesz zbudować kod, który mówi „dla wszystkich kontrolek w formularzu, które są moje, ustaw motyw na X”.

Wprowadź interfejsy.

Interfejsy to sposób patrzenia na obiekt w celu ustalenia, czy obiekt przestrzega określonej umowy.

Utworzysz „YourButton”, zejdziesz z przycisku i dodasz obsługę wszystkich potrzebnych interfejsów.

Umożliwi to napisanie kodu w następujący sposób:

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

Nie byłoby to możliwe bez interfejsów, zamiast tego musiałbyś napisać taki kod:

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

Tak, wiem, nie powinieneś używać „jest”, a następnie rzucać, odłóż to na bok.
Lasse V. Karlsen

4
westchnienie wiem, ale to przypadek tutaj.
Lasse V. Karlsen

7

W takim przypadku możesz (i prawdopodobnie) po prostu zdefiniujesz klasę bazową Pizza i odziedziczysz po nich. Istnieją jednak dwa powody, dla których interfejsy pozwalają robić rzeczy, których nie można osiągnąć w inny sposób:

  1. Klasa może implementować wiele interfejsów. Określa tylko cechy, które musi posiadać klasa. Implementacja szeregu interfejsów oznacza, że ​​klasa może spełniać wiele funkcji w różnych miejscach.

  2. Interfejs może być zdefiniowany w większym zakresie niż klasa lub obiekt wywołujący. Oznacza to, że możesz oddzielić funkcjonalność, oddzielić zależność od projektu i zachować funkcjonalność w jednym projekcie lub klasie oraz implementację tego w innym miejscu.

Jedną z implikacji 2 jest to, że możesz zmienić używaną klasę, wymagając tylko, aby zaimplementował odpowiedni interfejs.


6

Weź pod uwagę, że nie można użyć wielokrotnego dziedziczenia w języku C #, a następnie ponownie spójrz na swoje pytanie.



4

Jeśli pracuję nad interfejsem API do rysowania kształtów, mogę użyć DirectX lub wywołań graficznych lub OpenGL. Stworzę więc interfejs, który wyodrębni moją implementację z tego, co nazywacie.

Więc wywołać metodę fabryczną: MyInterface i = MyGraphics.getInstance(). Następnie masz umowę, więc wiesz, w jakich funkcjach możesz się spodziewać MyInterface. Możesz więc zadzwonić i.drawRectanglelub i.drawCubewiedzieć, że jeśli zamienisz jedną bibliotekę na inną, funkcje są obsługiwane.

Staje się to ważniejsze, jeśli używasz Dependency Injection, ponieważ wtedy możesz, w pliku XML, wymieniać implementacje.

Tak więc możesz mieć jedną bibliotekę kryptograficzną, którą można wyeksportować, która jest do użytku ogólnego, a druga, która jest przeznaczona do sprzedaży tylko amerykańskim firmom, a różnica polega na tym, że zmieniasz plik konfiguracyjny, a reszta programu nie jest zmienione.

Jest to bardzo przydatne w przypadku kolekcji w .NET, ponieważ powinieneś po prostu używać na przykład Listzmiennych i nie martw się, czy była to ArrayList czy LinkedList.

Tak długo, jak kodujesz do interfejsu, programista może zmienić rzeczywistą implementację, a reszta programu pozostanie niezmieniona.

Jest to również przydatne podczas testowania jednostkowego, ponieważ możesz wyśmiewać całe interfejsy, więc nie muszę iść do bazy danych, ale do wyśmiewanej implementacji, która zwraca tylko dane statyczne, więc mogę przetestować moją metodę, nie martwiąc się, czy baza danych jest wyłączona z powodu konserwacji lub nie.


4

Interfejs jest tak naprawdę kontraktem, do którego muszą się stosować klasy implementacyjne, w rzeczywistości jest podstawą niemal każdego wzorca projektowego, jaki znam.

W twoim przykładzie interfejs został utworzony, ponieważ wówczas zagwarantowane jest, że wszystko, co IS A Pizza, co oznacza, że ​​implementuje interfejs Pizza,

public void Order();

Po wspomnianym kodzie możesz mieć coś takiego:

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

W ten sposób używasz polimorfizmu i zależy Ci tylko na tym, aby Twoje obiekty reagowały na kolejność ().


4

Szukałem słowa „kompozycja” na tej stronie i nie widziałem go ani razu. Ta odpowiedź stanowi uzupełnienie wyżej wymienionych odpowiedzi.

Jednym z absolutnie kluczowych powodów korzystania z interfejsów w projekcie zorientowanym obiektowo jest to, że pozwalają one preferować kompozycję nad dziedziczeniem. Implementując interfejsy, możesz oddzielić swoje implementacje od różnych algorytmów, które do nich stosujesz.

Ten wspaniały samouczek „Wzór dekoratora” autorstwa Dereka Banasa (który - co zabawne - używa również pizzy jako przykładu) jest wartą zachodu ilustracją:

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


2
Jestem naprawdę zszokowany, że nie jest to najlepsza odpowiedź. Interfejsy w całej swojej użyteczności mają największe zastosowanie w kompozycji.
Maciej Sitko

3

Interfejsy służą do stosowania połączenia między różnymi klasami. na przykład masz klasę na samochód i drzewo;

public class Car { ... }

public class Tree { ... }

chcesz dodać funkcjonalność dla obu klas. Ale każda klasa ma swoje własne sposoby na spalenie. więc po prostu robicie;

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

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

2
Pytanie, które mnie prześladuje w takich przykładach, brzmi: dlaczego to pomaga? Czy mogę teraz przekazać argument typu IBurnable do metody i wszystkie klasy z interfejsem IBurnable mogą być obsługiwane? Tak jak znalazłem przykład pizzy. Fajnie, że możesz to zrobić, ale nie widzę korzyści jako takiej. Czy możesz rozszerzyć przykład (ponieważ jestem naprawdę gruby w tej chwili) lub podać przykład, który nie działałby z nim (ponownie, ponieważ w tej chwili czuję się naprawdę gruby). Wielkie dzięki.
Nebelhom

1
Zgodzić się. Interfejs = „Potrafi to zrobić”. Klasa / Streszczenie Calss = "Is A"
Peter.Wang

3

Dostaniesz interfejsy, kiedy będziesz ich potrzebować :) Możesz uczyć się przykładów, ale potrzebujesz Aha! efekt, aby naprawdę je zdobyć.

Teraz, gdy wiesz już, jakie są interfejsy, po prostu koduj bez nich. Wcześniej czy później napotkasz problem, w którym użycie interfejsów będzie najbardziej naturalne.


3

Dziwi mnie, że niewiele postów zawiera jeden najważniejszy powód interfejsu: Wzory projektowe . Jest to szerszy obraz wykorzystania kontraktów i chociaż jest to dekoracja składni kodu maszynowego (szczerze mówiąc, kompilator prawdopodobnie po prostu je ignoruje), abstrakcja i interfejsy są kluczowe dla OOP, zrozumienia przez człowieka i złożonych architektur systemowych.

Rozwińmy analogię pizzy, aby powiedzieć pełnoprawny 3-daniowy posiłek. Nadal będziemy mieli podstawowy Prepare()interfejs dla wszystkich naszych kategorii żywności, ale mielibyśmy również abstrakcyjne deklaracje dotyczące wyboru dań (przystawka, danie główne, deser) i różne właściwości dla rodzajów żywności (pikantne / słodkie, wegetariańskie / niewegetariańskie, bezglutenowe itp.).

W oparciu o te specyfikacje moglibyśmy wdrożyć wzorzec Fabryki Abstrakcyjnej w celu konceptualizacji całego procesu, ale użyć interfejsów, aby zapewnić, że tylko fundamenty były konkretne. Wszystko inne może stać się elastyczne lub zachęcić do polimorfizmu, zachowując jednocześnie enkapsulację między różnymi klasami Coursetego ICourseinterfejsu.

Gdybym miał więcej czasu, chciałbym sporządzić pełny przykład tego, albo ktoś może to dla mnie rozszerzyć, ale podsumowując, interfejs C # byłby najlepszym narzędziem w projektowaniu tego typu systemu.


1
Ta odpowiedź zasługuje na więcej punktów! Interfejsy świecą, gdy są używane w zaprojektowanych wzorach, np. Wzorze stanu. Dodatkowe informacje można znaleźć na stronie plus.google.com/+ZoranHorvat-Programming
Alex Nolasco

3

Oto interfejs dla obiektów o kształcie prostokątnym:

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

Wszystko, czego wymaga, to wdrożenie metod dostępu do szerokości i wysokości obiektu.

Teraz zdefiniujmy metodę, która będzie działać na każdym obiekcie, który jest IRectangular:

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

To zwróci obszar dowolnego obiektu prostokątnego.

Zaimplementujmy klasę, SwimmingPoolktóra jest prostokątna:

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

I kolejna klasa, Housektóra jest również prostokątna:

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

Biorąc to pod uwagę, możesz wywołać tę Areametodę na domach lub basenach:

var house = new House(2, 3);

var pool = new SwimmingPool(3, 4);

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

W ten sposób Twoje klasy mogą „odziedziczyć” zachowanie (metody statyczne) z dowolnej liczby interfejsów.


3

Interfejs definiuje umowę między dostawcą określonej funkcjonalności a odpowiadającymi jej konsumentami. Oddziela implementację od umowy (interfejsu). Powinieneś przyjrzeć się obiektowej architekturze i projektowaniu. Możesz zacząć od wikipedii: http://en.wikipedia.org/wiki/Interface_(computing)


3

Co ?

Interfejsy są w zasadzie umową, którą wdrażają wszystkie klasy której powinny przestrzegać interfejs. Wyglądają jak klasa, ale nie mają implementacji.

W C#nazwach interfejsów według konwencji definiuje się przez prefiks „ja”, więc jeśli chcesz mieć interfejs o nazwie kształty, zadeklaruj go jakoIShapes

Teraz dlaczego ?

Improves code re-usability

Powiedzmy, że chcesz narysować Circle, Triangle. Można je zgrupować i zadzwonić do nich Shapesi mają metody do rysowania Circlei Triangle ale o konkretną realizację byłby zły pomysł, ponieważ jutro może zdecydujesz się jeszcze 2 Shapes Rectangle& Square. Teraz, gdy je dodasz, istnieje duża szansa, że ​​możesz uszkodzić inne części kodu.

Dzięki interfejsowi izolujesz różne wdrożenia od umowy


Scenariusz na żywo Dzień 1

Zostałeś poproszony o utworzenie aplikacji do rysowania koła i trójkąta

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

Scenariusz na żywo Dzień 2

Jeśli zostaniesz poproszony o dodanie Squarei Rectangledo niego, wszystko, co musisz zrobić, to stworzyć dla niego implantację class Square: IShapesw Mainliście i dodać do listyshapes.Add(new Square());


Dlaczego dodajesz odpowiedź na 6-letnie pytanie z dziesiątkami innych odpowiedzi ocenianych setki razy? Nic tu nie zostało powiedziane.
Jonathon Reinhart

@JonathonReinhart, tak, tak myślałem, ale potem pomyślałem o tym przykładzie i sposobie, w jaki jego wyjaśnienie pomoże w powiązaniu z jednym ciałem lepiej niż inne.
Clint

2

Jest tu wiele dobrych odpowiedzi, ale chciałbym spróbować z nieco innej perspektywy.

Być może znasz SOLIDNE zasady projektowania obiektowego. W podsumowaniu:

S - Zasada pojedynczej odpowiedzialności O - Zasada otwarta / zamknięta L - Zasada podstawienia Liskowa I - Zasada podziału segmentu interfejsu D - Zasada inwersji zależności

Przestrzeganie zasad SOLID pomaga tworzyć kod, który jest czysty, dobrze przemyślany, spójny i luźno powiązany. Jeśli się uwzględni:

„Zarządzanie zależnościami jest kluczowym wyzwaniem w oprogramowaniu na każdą skalę” (Donald Knuth)

wtedy wszystko, co pomaga w zarządzaniu zależnościami, jest dużą wygraną. Interfejsy i zasada inwersji zależności naprawdę pomagają oddzielić kod od zależności od konkretnych klas, dzięki czemu kod może być napisany i uzasadniony pod względem zachowań a nie implementacji. Pomaga to podzielić kod na komponenty, które można skomponować w czasie wykonywania, a nie na czas kompilacji, a także oznacza, że ​​komponenty te można dość łatwo podłączać i odłączać bez konieczności zmiany reszty kodu.

Interfejsy pomagają w szczególności w Zasadzie Inwersji Zależności, w której kod można komponować w zbiór usług, przy czym każdą usługę opisuje interfejs. Usługi można następnie „wstrzykiwać” do klas w czasie wykonywania, przekazując je jako parametr konstruktora. Ta technika naprawdę staje się krytyczna, jeśli zaczniesz pisać testy jednostkowe i użyjesz programowania opartego na testach. Spróbuj! Szybko zrozumiesz, w jaki sposób interfejsy pomagają rozdzielić kod na porcje, które można indywidualnie testować oddzielnie.


1

Głównym celem interfejsów jest zawarcie umowy między tobą a dowolną inną klasą, która implementuje ten interfejs, co powoduje, że kod jest oddzielony i umożliwia rozbudowę.


1

Istnieją naprawdę świetne przykłady.

Po drugie, w przypadku instrukcji switch, nie musisz już utrzymywać i przełączać za każdym razem, gdy chcesz, aby rio wykonało zadanie w określony sposób.

W twoim przykładzie pizzy, jeśli chcesz zrobić pizzę, wystarczy interfejs, od tego momentu każda pizza zajmuje się własną logiką.

Pomaga to zmniejszyć sprzężenie i złożoność cyklomatyczną. Musisz jeszcze wdrożyć logikę, ale na szerszym obrazie będzie mniej rzeczy, które musisz śledzić.

Dla każdej pizzy możesz następnie śledzić informacje dotyczące tej pizzy. To, co mają inne pizze, nie ma znaczenia, ponieważ tylko inne pizze muszą wiedzieć.


1

Najprostszym sposobem myślenia o interfejsach jest rozpoznanie, co oznacza dziedziczenie. Jeśli klasa CC dziedziczy klasę C, oznacza to, że:

  1. Klasa CC może wykorzystywać dowolnych publicznych lub chronionych członków klasy C tak, jakby byli jej własnymi, a zatem musi jedynie implementować rzeczy, które nie istnieją w klasie nadrzędnej.
  2. Odwołanie do CC można przekazać lub przypisać do procedury lub zmiennej, która oczekuje odwołania do C.

Te dwie funkcje dziedziczenia są w pewnym sensie niezależne; chociaż dziedziczenie dotyczy obu naraz, możliwe jest również zastosowanie drugiego bez pierwszego. Jest to przydatne, ponieważ zezwalanie obiektowi na dziedziczenie elementów z dwóch lub więcej niepowiązanych klas jest znacznie bardziej skomplikowane niż zezwalanie na to, że jeden typ rzeczy może być zastąpiony wieloma typami.

Interfejs przypomina trochę abstrakcyjną klasę podstawową, ale z kluczową różnicą: obiekt, który dziedziczy klasę podstawową, nie może odziedziczyć żadnej innej klasy. Natomiast obiekt może implementować interfejs bez wpływu na jego zdolność do dziedziczenia dowolnej pożądanej klasy lub implementacji innych interfejsów.

Jedną z fajnych cech tego (niewykorzystanego w ramach .net, IMHO) jest to, że umożliwiają deklaratywne wskazanie rzeczy, które może zrobić obiekt. Na przykład niektóre obiekty będą chciały obiektu źródła danych, z którego mogą pobierać rzeczy według indeksu (jak to jest możliwe w przypadku Listy), ale nie będą musiały niczego tam przechowywać. Inne procedury będą potrzebowały obiektu przechowującego dane, w którym będą mogły przechowywać rzeczy nie według indeksu (jak w Collection.Add), ale nie będą musiały niczego odczytywać. Niektóre typy danych umożliwiają dostęp według indeksu, ale nie pozwalają na zapis; inni pozwolą na pisanie, ale nie zezwalają na dostęp według indeksu. Niektóre oczywiście pozwolą na jedno i drugie.

Gdyby ReadableByIndex i Appendable były niepowiązanymi klasami podstawowymi, niemożliwe byłoby zdefiniowanie typu, który mógłby być przekazywany zarówno do rzeczy oczekujących ReadableByIndex, jak i rzeczy oczekujących Appendable. Można próbować temu zaradzić, jeśli ReadableByIndex lub Appendable pochodzą od drugiej; klasa pochodna musiałaby udostępnić członków publicznych dla obu celów, ale ostrzega, że ​​niektórzy członkowie publiczni mogą w rzeczywistości nie działać. Niektóre klasy i interfejsy Microsoftu to robią, ale to raczej nieprzyjemne. Bardziej przejrzystym podejściem jest posiadanie interfejsów do różnych celów, a następnie sprawienie, by obiekty implementowały interfejsy do rzeczy, które mogą faktycznie zrobić. Jeśli jeden miał interfejs IReadableByIndex i inny interfejs IAppendable,


1

Interfejsy można również połączyć szeregowo, aby utworzyć kolejny interfejs. Ta zdolność do implementacji wielu interfejsów daje programistom korzyść polegającą na dodaniu funkcjonalności do swoich klas bez konieczności zmiany bieżącej funkcjonalności klas (Zasady SOLID)

O = „Klasy powinny być otwarte dla rozszerzenia, ale zamknięte dla modyfikacji”


1

Dla mnie zaletą / korzyścią interfejsu jest to, że jest on bardziej elastyczny niż klasa abstrakcyjna. Ponieważ możesz odziedziczyć tylko 1 klasę abstrakcyjną, ale możesz zaimplementować wiele interfejsów, zmiany w systemie, który dziedziczy klasę abstrakcyjną w wielu miejscach, stają się problematyczne. Jeśli zostanie odziedziczony w 100 miejscach, zmiana wymaga zmian na wszystkich 100. Ale za pomocą interfejsu możesz umieścić nową zmianę w nowym interfejsie i po prostu użyć tego interfejsu tam, gdzie jest potrzebny (Sekwencja interfejsu od SOLID). Ponadto wykorzystanie pamięci wydaje się być mniejsze w przypadku interfejsu, ponieważ obiekt w przykładzie interfejsu jest używany tylko raz w pamięci, pomimo tego, ile miejsc implementuje interfejs.


1

Interfejsy służą do zwiększania spójności w sposób luźno sprzężony, co odróżnia ją od klasy abstrakcyjnej, która jest ściśle powiązana. Dlatego jest ona również powszechnie definiowana jako kontrakt. zdefiniowane przez interfejs i nie ma w nim żadnych konkretnych elementów.

Podam tylko przykład obsługiwany przez poniższą grafikę.

Wyobraź sobie, że w fabryce są 3 rodzaje maszyn: maszyna prostokątna, maszyna trójkątna i maszyna wielokątna. Czasy są konkurencyjne i chcesz usprawnić szkolenie operatorów. Po prostu chcesz je przeszkolić w zakresie jednej metodyki uruchamiania i zatrzymywania maszyn, abyś mógł mają zielony przycisk start i czerwony przycisk stop. Więc teraz na 3 różnych maszynach masz spójny sposób uruchamiania i zatrzymywania 3 różnych typów maszyn. Teraz wyobraź sobie, że te maszyny są klasami i klasy muszą mieć metody start i stop, zamierzasz osiągnąć spójność między tymi klasami, które mogą być bardzo różne? Interfejs jest odpowiedzią.

wprowadź opis zdjęcia tutaj

Prosty przykład, który pomoże ci wizualizować, można zapytać, dlaczego nie użyć klasy abstrakcyjnej? Dzięki interfejsowi obiekty nie muszą być bezpośrednio powiązane ani dziedziczone, a mimo to nadal można uzyskać spójność między różnymi klasami.

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

Pozwól, że opiszę to z innej perspektywy. Stwórzmy historię zgodnie z przykładem, który pokazałem powyżej;

Program, Maszyna i IMachine są aktorami naszej historii. Program chce uruchomić, ale nie ma takiej zdolności, a Maszyna wie, jak uruchomić. Machine i IMachine są najlepszymi przyjaciółmi, ale Program nie rozmawia z Machine. Więc Program i IMachine zawierają umowę i zdecydowali, że IMachine powie Programowi, jak uruchomić wyglądającą Maszynę (jak odbłyśnik).

Program uczy się obsługiwać za pomocą IMachine.

Interfejs zapewnia komunikację i tworzenie luźno powiązanych projektów.

PS: Mam metodę konkretnej klasy jako prywatną. Moim celem tutaj jest osiągnięcie luźno sprzężonego poprzez uniemożliwienie dostępu do konkretnych właściwości i metod klasy, i pozostawienie jedynie sposobu, aby dotrzeć do nich poprzez interfejsy. (Więc wyraźnie zdefiniowałem metody interfejsów).


1

Wiem, że jestem bardzo spóźniony ... (prawie dziewięć lat), ale jeśli ktoś chce drobnych wyjaśnień, możesz to zrobić:

Krótko mówiąc, używasz interfejsu, gdy wiesz, co może zrobić obiekt lub jaką funkcję zamierzamy wdrożyć na obiekcie. Przykład Wstaw, zaktualizuj i usuń.

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

Ważna uwaga: interfejsy są ZAWSZE publiczne.

Mam nadzieję że to pomoże.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.