Skąd będę wiedział, kiedy utworzyć interfejs?


196

Jestem na etapie uczenia się programowania, w którym czuję, że muszę dowiedzieć się więcej o interfejsach.

Często o nich czytam, ale wygląda na to, że ich nie rozumiem.

Czytałem przykłady: klasa bazowa zwierząt, z interfejsem IAnimal do takich rzeczy jak „Spacer”, „Uruchom”, „GetLegs” itp. - Ale nigdy nad tym nie pracowałem i czułem, że „Hej, powinienem użyć interfejsu tutaj!"

czego mi brakuje? Dlaczego tak trudno mi to pojąć! Jestem po prostu zastraszony faktem, że nigdy nie zdaję sobie sprawy z konkretnej potrzeby - głównie z powodu jakiegoś brakującego aspektu ich zrozumienia! To sprawia, że ​​mam wrażenie, że brakuje mi czegoś na topie, jeśli chodzi o bycie programistą! Jeśli ktoś miałby takie doświadczenie i przełom, byłbym wdzięczny za kilka wskazówek, jak zrozumieć tę koncepcję. Dziękuję Ci.


Odpowiedzi:


151

rozwiązuje ten konkretny problem:

masz a, b, c, d 4 różnych typów. w całym kodzie masz coś takiego:

a.Process();
b.Process();
c.Process();
d.Process();

dlaczego nie ma ich wdrożyć IProcessable, a następnie zrobić

List<IProcessable> list;

foreach(IProcessable p in list)
    p.Process();

skaluje się to znacznie lepiej, jeśli dodasz, powiedzmy, 50 typów klas, które wszystkie robią to samo.


Kolejny konkretny problem:

Czy kiedykolwiek spojrzałeś na System.Linq.Enumerable? Definiuje mnóstwo metod rozszerzenia, które działają na każdym typie, który implementuje IEnumerable. Ponieważ wszystko, co implementuje IEnumerable, w zasadzie mówi „popieram iterację w nieuporządkowanym wzorcu typu foreach”, możesz zdefiniować złożone zachowania (Count, Max, Where, Select, itp.) Dla dowolnego typu enumerable.


2
To pomaga. Jaka jest zaleta posiadania interfejsu, a nie tylko tego, że wszystkie typy mają implementację dla metody Process ()?
user53885

Nie byłoby możliwe użycie tej samej zmiennej p, chyba że wszystkie byłyby podklasami tego samego typu podstawowego lub nie zaimplementowały interfejsu.
Karl

Nie musisz rzucać, w przeciwieństwie do 50 różnych klas za pomocą metody Process. C # nie używa „pisania kaczego”, więc tylko dlatego, że A ma Process (), a B ma Process (), nie oznacza, że ​​istnieje jakikolwiek ogólny sposób wywoływania. Potrzebujesz do tego interfejsu.
user7116

dobrze. Właśnie zmieniłem „var” na „IProcessable”, aby przykład miał więcej sensu.
Jimmy

2
@Rogerio: Próbowałem być ogólny. Nie chodzi o to, że „kiedy masz rzeczy, które mają funkcję Process ()”, „jest”, gdy masz rzeczy, które mają wspólny zestaw metod ”. przykład można łatwo zmienić naforeach(IMyCompanyWidgetFrobber a in list) a.Frob(widget, context);
Jimmy

133

Bardzo podoba mi się odpowiedź Jimmy'ego, ale czuję, że muszę coś do niej dodać. Kluczem do wszystkiego jest „zdolny” w IProcess zdolny. Wskazuje na zdolność (lub właściwość, ale oznacza „wewnętrzną jakość”, a nie w sensie właściwości C #) obiektu, który implementuje interfejs. IAnimal prawdopodobnie nie jest dobrym przykładem interfejsu, ale IWalkable może być dobrym interfejsem, jeśli twój system ma wiele rzeczy, które mogą chodzić. Możesz mieć klasy pochodzące od zwierząt, takie jak pies, krowa, ryba, wąż. Pierwsze dwa prawdopodobnie zaimplementują IWalkable, pozostałe dwa nie chodzą, więc nie. Teraz pytasz „dlaczego nie mieć kolejnej nadklasy, WalkingAnimal, z której wywodzą się Pies i Krowa?”. Odpowiedź brzmi, gdy masz coś całkowicie poza drzewem dziedzictwa, które również może chodzić, na przykład robot. Robot zaimplementuje IWalkable, ale prawdopodobnie nie będzie pochodził od Animal. Jeśli chcesz listę rzeczy, które mogą chodzić,

Teraz zastąp IWalkable czymś bardziej programowym, takim jak IPersistable, a analogia stanie się znacznie bliższa temu, co zobaczysz w prawdziwym programie.


9
Podoba mi się to, co wymyśliłeś - „Wskazuje możliwości”. Interfejsy są naprawdę potrzebne, gdy trzeba zdefiniować zdolność, ponieważ podstawy muszą trzymać się swojej klasy „Base”.
Ramiz Uddin

71

Użyj interfejsów, gdy implementacje tej samej funkcjonalności będą się różnić.

Użyj klasy abstrakcyjnej / podstawowej, jeśli chcesz udostępnić wspólną konkretną implementację.


8
Pierwszy nazywa się polimorfizmem. Drugi to olej z węży - chyba że podklasa jest klasą podstawową (nie ma naruszenia zasady substytucji Liskowa ), powinieneś preferować kompozycję zamiast dziedziczenia.
Arnis Lapsa,

@ArnisLapsa Nie do końca rozumiem, co rozumiesz przez „chyba, że ​​podklasa jest klasą podstawową”. Kiedy podklasa nie będzie „klasą podstawową”? (jak w issłowie kluczowym)
Marc.2377,

32

Pomyśl o interfejsie jak o umowie. To sposób na powiedzenie: „Te klasy powinny przestrzegać tych zasad”.

Tak więc w przykładzie IAnimal można powiedzieć: „MUSZĄ być w stanie wywoływać bieg, spacer itp. Na klasach, które implementują IAnimal”.

Dlaczego to jest przydatne? Możesz zbudować funkcję, która polega na tym, że musisz mieć możliwość wywoływania funkcji Run and Walk, na przykład na obiekcie. Możesz mieć następujące:

public void RunThenWalk(Monkey m) {
    m.Run();
    m.Walk();
}

public void RunThenWalk(Dog d) {
    d.Run();
    d.Walk();
}

... i powtórz to dla wszystkich obiektów, które znasz, mogą biegać i chodzić. Jednak w interfejsie IAnimal możesz zdefiniować funkcję jeden raz w następujący sposób:

public void RunThenWalk(IAnimal a) {
    a.Run();
    a.Walk();
}

Programując w oparciu o interfejs, zasadniczo ufasz klasom, że zrealizują zamiar interfejsu. W naszym przykładzie myśl brzmi: „Nie dbam o to, jak biegają i chodzą, dopóki biegają i chodzą. Mój RunThenWalk będzie ważny, dopóki spełnią tę umowę. Działa doskonale, nie wiedząc o niczym innym klasa."

W tym pokrewnym pytaniu jest również dobra dyskusja .


1
Nie zapomnij o niezależności wdrożenia. Interfejs pozwala nie dbać o to, jak zaimplementowana jest podstawowa funkcja, tylko że robi to, co mówi Interfejs.
Matthew Brubaker

18

Nie martw się tak bardzo. Wielu programistów rzadko będzie musiało napisać interfejs. Często będziesz korzystać z interfejsów dostępnych w ramach .NET , ale jeśli nie poczujesz potrzeby pisania w najbliższym czasie, nie ma w tym nic zaskakującego.

Przykład, który zawsze komuś daję, to jeśli masz klasę żaglówki i klasę żmii. Dziedziczą odpowiednio klasę łodzi i klasę samochodów. Teraz powiedz, że musisz przejść przez wszystkie te obiekty i wywołać ich Drive()metodę. Chociaż możesz napisać kod podobny do następującego:

if(myObject is Boat)
    ((Boat)myObject).Drive()
else
    if (myObject is Car)
        ((Car)myObject).Drive()

O wiele łatwiej byłoby napisać:

((IDrivable)myObject).Drive()

16

Jimmy ma rację, gdy chcesz być w stanie używać jednej zmiennej dla wielu typów, ale wszystkie te typy implementują tę samą metodę za pomocą deklaracji interfejsu. Następnie możesz nazwać je główną metodą na zmiennej typu interfejs.

Istnieje jednak drugi powód, aby używać interfejsów. Gdy architekt projektu jest inną osobą niż programista implementacji lub istnieje kilka programistów implementacji i jeden kierownik projektu. Osoba odpowiedzialna może napisać całą gamę interfejsów i sprawdzić, czy system współpracuje, a następnie pozostawić programistom wypełnienie interfejsów klasami implementacji. Jest to najlepszy sposób, aby zapewnić, że wiele osób pisze kompatybilne klasy i mogą to robić równolegle.


15

Lubię analogię armii.

Sierżant nie dba o to, czy jesteś programistą , muzykiem lub prawnikiem .
Jesteś traktowany jak żołnierz .

uml

Jest to łatwiejsze dla sierżanta nie zajmować się szczegółami konkretnych osób, przy których pracuje z,
traktują wszystkich jak abstrakcjami żołnierz (... i karania ich, gdy nie działają jak te).

Zdolność osób do zachowywania się jak żołnierze nazywa się polimorfizmem.

Interfejsy to konstrukcje oprogramowania, które pomagają osiągnąć polimorfizm.

Potrzeba abstrakcyjnych szczegółów w celu osiągnięcia prostoty jest odpowiedzią na Twoje pytanie.

Polimorfizm , który etymologicznie oznacza „wiele form”, to zdolność do traktowania obiektu dowolnej podklasy klasy podstawowej, tak jakby był obiektem klasy podstawowej. Klasa podstawowa ma zatem wiele form: samą klasę podstawową i dowolną z jej podklas.

(..) Ułatwia to pisanie kodu i zrozumienie przez innych. Umożliwia także rozszerzanie kodu, ponieważ inne podklasy mogłyby zostać dodane później do rodziny typów, a obiekty tych nowych podklas będą również działać z istniejącym kodem.


14

Z mojego doświadczenia wynika, że ​​siła napędowa do tworzenia interfejsów nie pojawiła się, dopóki nie zacząłem przeprowadzać testów jednostkowych przy użyciu frameworku. Stało się zupełnie jasne, że korzystanie z interfejsów znacznie ułatwi drwiny (ponieważ ramy zależą od metod wirtualnych). Kiedy zacząłem, zobaczyłem wartość abstrakcji interfejsu mojej klasy od implementacji. Nawet jeśli nie utworzę rzeczywistego interfejsu, staram się teraz uczynić moje metody wirtualnymi (zapewniając niejawny interfejs, który można zastąpić).

Jest wiele innych powodów, dla których odkryłem, że wzmacniam dobrą praktykę refaktoryzacji do interfejsów, ale testowanie / kpowanie z jednostek było tym, co zapewniło początkowy „aha moment” praktycznego doświadczenia.

EDYCJA : Aby to wyjaśnić, przy testowaniu jednostkowym i kpinach zawsze mam dwie implementacje - rzeczywistą, konkretną implementację i alternatywną próbną implementację używaną w testowaniu. Gdy masz dwie implementacje, wartość interfejsu staje się oczywista - radzi sobie z tym pod względem interfejsu, abyś mógł w dowolnym momencie zastąpić implementację. W tym przypadku zastępuję go fałszywym interfejsem. Wiem, że mogę to zrobić bez faktycznego interfejsu, jeśli moja klasa jest poprawnie zbudowana, ale użycie faktycznego interfejsu wzmacnia to i czyni go czystszym (czytelniejszym dla czytelnika). Bez tego impulsu nie sądzę, że doceniłbym wartość interfejsów, ponieważ większość moich klas ma tylko jedną konkretną implementację.


Dobra rzecz z niewłaściwych powodów. W twoim przypadku - w efekcie powstają tak zwane „interfejsy nagłówków”, a dodatkowe złożoności przeważają osiągniętą prostotę.
Arnis Lapsa,

@Arnis - testowanie jednostkowe jest „złym powodem”, łatwe kpienie z klas w celu usunięcia zależności w testach jest „złym powodem”. Przepraszam, ale się nie zgadzam.
tvanfosson,

1
testy powinny pośrednio wpływać na projekt poprzez dostarczanie informacji zwrotnych, jeśli kod jest lub nie jest testowalny. samo dodawanie punktów rozszerzalności w celu poprawienia testowalności jest jak oszustwo. Myślę, że Mark Seeman podsumowuje to najlepiej bit.ly/esi8Wp
Arnis Lapsa

1
@Arnis - więc w ogóle używasz próbnych testów? Jeśli nie, w jaki sposób usunąć zależność od zależności. Czy w ogóle używasz DI? Testy jednostkowe skłoniły mnie do używania kpiny i DI; drwiny i DI udowodniły wartość dobrej praktyki używania interfejsów do definiowania kontraktów w sposób, którego nigdy nie byłoby możliwe zrozumienie akademickie. Z powodu przyjęcia TDD mój kod jest znacznie mniej sprzężony niż byłby w innym przypadku. Myślę, że to dobrze.
tvanfosson,

samo stwierdzenie, że rozkład, który nie występuje wzdłuż tak zwanych naturalnych stawów, prowadzi do niskiej kohezji i niepotrzebnej złożoności.
Arnis Lapsa,

10

Niektóre nieprogramowe przykłady, które mogą pomóc ci zobaczyć właściwe zastosowania interfejsów w programowaniu.

Istnieje interfejs między urządzeniami elektrycznymi a siecią elektryczną - jest to zestaw konwencji dotyczących kształtu wtyczek i gniazd oraz napięć / prądów w poprzek nich. Jeśli chcesz wdrożyć nowe urządzenie elektryczne, o ile wtyczka będzie zgodna z zasadami, będzie mogła uzyskać usługi z sieci. To sprawia, że rozszerzalność jest bardzo łatwa i usuwa lub obniża koszty koordynacji : nie musisz powiadamiać dostawcy energii elektrycznej o tym, jak działa twoje nowe urządzenie, i dojść do osobnej umowy dotyczącej sposobu podłączenia nowego urządzenia do sieci.

Kraje mają standardowe skrajnie szyn. Pozwala to na podział pracy między firmy inżynieryjne, które stawiają szyny, oraz firmy inżynierskie, które budują pociągi, aby jeździć na tych szynach, a także umożliwia przedsiębiorstwom kolejowym wymianę i modernizację pociągów bez ponownej zmiany całego systemu.

Usługę, którą firma przedstawia klientowi, można opisać jako interfejs: dobrze zdefiniowany interfejs podkreśla usługę i ukrywa środki . Po umieszczeniu listu w skrzynce pocztowej oczekujesz, że system pocztowy dostarczy list w określonym czasie, ale nie masz żadnych oczekiwań co do sposobu dostarczenia listu: nie musisz tego wiedzieć , a usługa pocztowa może elastycznie wybierz sposób dostawy, który najlepiej spełnia wymagania i obecne okoliczności. Wyjątkiem jest możliwość wyboru przez klientów poczty lotniczej - nie jest to interfejs, który zaprojektowałby nowoczesny programista, ponieważ ujawnia zbyt wiele implementacji.

Przykłady z natury: Nie przepadam za przykładami eats (), makesSound (), move () itp. Opisują zachowanie, co jest poprawne, ale nie opisują interakcji i sposobu ich włączania . Oczywiste przykłady interfejsów, które umożliwiają interakcje w przyrodzie, dotyczą reprodukcji, na przykład kwiat stanowi pewien interfejs dla pszczoły, dzięki czemu może mieć miejsce zapylenie.


5

Jest całkowicie możliwe, aby przejść całe życie jako programista .net i nigdy nie pisać własnych interfejsów. W końcu przetrwaliśmy bez nich dobrze przez dziesięciolecia, a nasze języki wciąż były kompletne w Turingu.

Nie mogę ci powiedzieć, dlaczego potrzebujesz interfejsów, ale mogę dać ci listę miejsc, w których używamy ich w naszym bieżącym projekcie:

  1. W naszym modelu wtyczek ładujemy wtyczki według interfejsu i udostępniamy ten interfejs pisarzom wtyczek, aby były zgodne.

  2. W naszym systemie przesyłania wiadomości między maszynami wszystkie klasy komunikatów implementują określony interfejs i są „rozpakowywane” za pomocą interfejsu.

  3. Nasz system zarządzania konfiguracją definiuje interfejs używany do ustawiania i pobierania ustawień konfiguracji.

  4. Mamy jeden interfejs, którego używamy, aby uniknąć nieprzyjemnego problemu z okrągłym odniesieniem. (Nie rób tego, jeśli nie musisz.)

Sądzę, że jeśli istnieje reguła, należy używać interfejsów, aby zgrupować kilka klas w relacji is-a, ale nie chcesz zapewnić żadnej implementacji w klasie podstawowej.


5

Przykład kodu (połączenie Andrzeja z dodatkowymi moimi w interfejsach what-is-the-the-the-of-interfaces ), który również pokazuje, dlaczego interfejs zamiast klasy abstrakcyjnej dla języków bez obsługi wielokrotnego dziedziczenia (c # i Jawa):

interface ILogger
{
    void Log();
}
class FileLogger : ILogger
{
    public void Log() { }
}
class DataBaseLogger : ILogger
{
    public void Log() { }
}
public class MySpecialLogger : SpecialLoggerBase, ILogger
{
    public void Log() { }
}

Zauważ, że FileLogger i DataBaseLogger nie potrzebują interfejsu (może to być abstrakcyjna klasa bazowa Logger). Uważaj jednak, że musisz użyć zewnętrznego programu rejestrującego, który zmusza Cię do korzystania z klasy bazowej (powiedzmy, że ujawnia chronione metody, których musisz użyć). Ponieważ język nie obsługuje wielokrotnego dziedziczenia, nie będzie można używać abstrakcyjnego podejścia do klasy podstawowej.

Podsumowując: użyj interfejsu, jeśli to możliwe, aby uzyskać dodatkową elastyczność w kodzie. Twoja implementacja jest mniej związana, więc lepiej dostosowuje się do zmian.


4

Używałem interfejsów od czasu do czasu i oto moje najnowsze użycie (nazwy zostały uogólnione):

Mam kilka niestandardowych elementów sterujących w WinForm, które muszą zapisywać dane w obiekcie biznesowym. Jednym podejściem jest wywołanie każdej kontroli osobno:

myBusinessObject.Save(controlA.Data);
myBusinessObject.Save(controlB.Data);
myBusinessObject.Save(controlC.Data);

Problem z tą implementacją polega na tym, że za każdym razem, gdy dodam kontrolkę, muszę przejść do mojej metody „Zapisz dane” i dodać nową kontrolkę.

Zmieniłem formanty, aby wdrożyć interfejs ISaveable, który ma metodę SaveToBusinessObject (...), więc teraz moja metoda „Save Data” po prostu iteruje przez formanty, a jeśli znajdzie taką, która jest ISaveable, wywoła SaveToBusinessObject. Teraz, gdy potrzebna jest nowa kontrola, wszystko, co ktoś musi zrobić, to zaimplementować ISaveable w tym obiekcie (i nigdy nie dotykać innej klasy).

foreach(Control c in Controls)
{
  ISaveable s = c as ISaveable;

  if( s != null )
      s.SaveToBusinessObject(myBusinessObject);
}

Często niezrealizowaną korzyścią dla interfejsów jest lokalizacja modyfikacji. Po zdefiniowaniu rzadko zmieniasz ogólny przepływ aplikacji, ale często wprowadzasz zmiany na poziomie szczegółów. Gdy zachowujesz szczegóły w określonych obiektach, zmiana w ProcessA nie wpłynie na zmianę w ProcessB. (Klasy podstawowe również dają tę korzyść.)

EDYCJA: Kolejną korzyścią jest specyfika działań. Tak jak w moim przykładzie, wszystko, co chcę zrobić, to zapisać dane; Nie dbam o to, jaki to rodzaj kontroli lub czy może zrobić cokolwiek innego - chcę tylko wiedzieć, czy mogę zapisać dane w formancie. Sprawia, że ​​mój kod zapisu jest całkiem jasny - nie ma żadnych sprawdzeń, czy jest to tekst, numer, boolean czy cokolwiek innego, ponieważ formant niestandardowy obsługuje to wszystko.


4

Powinieneś zdefiniować interfejs, kiedy będziesz musiał wymusić zachowanie dla swojej klasy.

Zachowanie zwierzęcia może obejmować chodzenie, jedzenie, bieganie itp. Dlatego definiujesz je jako interfejsy.

Innym praktycznym przykładem jest interfejs ActionListener (lub Runnable). Wdrożysz je, gdy chcesz śledzić określone zdarzenie. Dlatego musisz podać implementację actionPerformed(Event e)metody w swojej klasie (lub podklasie). Podobnie w przypadku interfejsu Runnable udostępniasz implementację public void run()metody.

Można również zaimplementować te interfejsy dla dowolnej liczby klas.

Innym przykładem użycia interfejsów (w Javie) jest implementacja wielokrotnego dziedziczenia oferowanego w C ++.


3
Proszę, niech Bóg sprawi, aby przestali mówić takie rzeczy, jak wielokrotne dziedzictwo w odniesieniu do interfejsów. Ty NIE dziedziczyć interfejs w klasie. Ci REALIZACJI go.
Andrei Rînea

4

Załóżmy, że chcesz modelować irytacje, które mogą się zdarzyć, gdy próbujesz iść spać.

Model przed interfejsami

wprowadź opis zdjęcia tutaj

class Mosquito {
    void flyAroundYourHead(){}
}

class Neighbour{
    void startScreaming(){}
}

class LampJustOutsideYourWindow(){
    void shineJustThroughYourWindow() {}
}

Jak wyraźnie widzisz, wiele „rzeczy” może być denerwujących, kiedy próbujesz spać.

Wykorzystanie klas bez interfejsów

Ale jeśli chodzi o korzystanie z tych klas, mamy problem. Nie mają ze sobą nic wspólnego. Każdą metodę musisz wywołać osobno.

class TestAnnoyingThings{
    void testAnnoyingThinks(Mosquito mosquito, Neighbour neighbour, LampJustOutsideYourWindow lamp){
         if(mosquito != null){
             mosquito.flyAroundYourHead();
         }
         if(neighbour!= null){
             neighbour.startScreaming();
         }
         if(lamp!= null){
             lamp.shineJustThroughYourWindow();
         }
    }
}

Model z interfejsami

Aby rozwiązać ten problem, możemy wprowadzić iteracjęwprowadź opis zdjęcia tutaj

interface Annoying{
   public void annoy();

}

I zaimplementuj to w klasach

class Mosquito implements Annoying {
    void flyAroundYourHead(){}

    void annoy(){
        flyAroundYourHead();
    }
}

class Neighbour implements Annoying{
    void startScreaming(){}

    void annoy(){
        startScreaming();
    }
}

class LampJustOutsideYourWindow implements Annoying{
    void shineJustThroughYourWindow() {}

    void annoy(){
        shineJustThroughYourWindow();
    }
}

Użycie z interfejsami

Co znacznie ułatwi korzystanie z tych klas

class TestAnnoyingThings{
    void testAnnoyingThinks(Annoying annoying){
        annoying.annoy();
    }
}

Ok, ale nie trzeba Neighbouri LampJustOutsideYourWindowteż trzeba wdrożyć Annoying?
Stardust

Tak, dziękuję za zwrócenie na to uwagi. Dokonałem edycji za pomocą tej zmiany
Marcin Szymczak

2

Najłatwiejszym przykładem jest coś takiego jak procesory płatności (Paypal, PDS itp.).

Załóżmy, że tworzysz interfejs IPaymentProcessor z metodami ProcessACH i ProcessCreditCard.

Możesz teraz wdrożyć konkretną implementację Paypal. Wywołanie tych metod wywołuje określone funkcje PayPal.

Jeśli później zdecydujesz, że musisz zmienić dostawcę, możesz to zrobić. Po prostu utwórz kolejną konkretną implementację dla nowego dostawcy. Ponieważ wszystko, z czym jesteś związany, to interfejs (umowa), możesz zamienić, którego używa Twoja aplikacja, nie zmieniając kodu, który go zużywa.


2

Pozwala także na przeprowadzanie próbnych testów jednostkowych (.Net). Jeśli twoja klasa korzysta z interfejsu, możesz wyśmiewać obiekt w testach jednostkowych i łatwo testować logikę (bez faktycznego odwiedzania bazy danych lub usługi internetowej itp.).

http://www.nmock.org/


2

Podczas przeglądania zestawów .NET Framework i drążenia w dół do klas podstawowych dla dowolnego ze standardowych obiektów, zauważysz wiele interfejsów (członków o nazwie ISomeName).

Interfejsy są w zasadzie do implementacji frameworków, dużych lub małych. Tak samo myślałem o interfejsach, dopóki nie chciałem napisać własnego frameworka. Odkryłem również, że zrozumienie interfejsów pomogło mi szybciej nauczyć się ram. W momencie, gdy chcesz napisać bardziej eleganckie rozwiązanie do wszystkiego, przekonasz się, że interfejs ma sens. To jak metoda pozwalająca klasie założyć odpowiednie ubrania do pracy. Co ważniejsze, interfejsy pozwalają systemom na znacznie bardziej samo-dokumentowanie, ponieważ złożone obiekty stają się mniej złożone, gdy klasa implementuje interfejsy, co pomaga kategoryzować jego funkcjonalność.

Klasy implementują interfejsy, gdy chcą mieć możliwość bezpośredniego lub pośredniego uczestnictwa w strukturze. Na przykład, IDisposable to wspólny interfejs, który zapewnia podpis metody dla popularnej i użytecznej metody Dispose (). W środowisku wszystko, co ty lub inny programista powinniście wiedzieć o klasie, to to, że jeśli implementuje ona IDisposable, to wiesz, że ((IDisposable) myObject) .Dispose () można wywołać w celu wyczyszczenia.

PRZYKŁAD KLASYCZNY: bez implementacji interfejsu IDisposable nie można używać konstrukcji słowa kluczowego „using ()” w języku C #, ponieważ wymaga to, aby każdy obiekt określony jako parametr mógł być niejawnie rzutowany na IDisposable.

PRZYKŁAD KOMPLEKSOWY: Bardziej złożonym przykładem byłaby klasa System.ComponentModel.Component. Ta klasa implementuje zarówno IDisposable, jak i IComponent. Większość, jeśli nie wszystkie, obiekty .NET, z którymi jest powiązany projektant wizualny, implementują IComponent, dzięki czemu IDE będzie mogło wchodzić w interakcje z tym komponentem.

WNIOSEK: Gdy zapoznasz się z .NET Framework, pierwszą rzeczą, którą zrobisz, gdy spotkasz nową klasę w Przeglądarce obiektów lub w narzędziu .NET Reflector (darmowym) ( http://www.red-gate.com / products / reflector / ) ma sprawdzić, która klasa dziedziczy, a także interfejsy, które implementuje. .NET Reflector jest jeszcze lepszy niż Przeglądarka obiektów, ponieważ pozwala także zobaczyć klasy pochodne. To pozwala ci dowiedzieć się o wszystkich obiektach, które wywodzą się z konkretnej klasy, a tym samym potencjalnie dowiedzieć się o funkcjonalności frameworku, o której istnieniu nie wiedziałeś. Jest to szczególnie istotne, gdy do .NET Framework zostaną dodane nowe lub nowe przestrzenie nazw.


2

Rozważ, że tworzysz strzelankę FPS. Gracz ma do wyboru wiele pistoletów.

Możemy mieć interfejs Gundefiniujący funkcjęshoot() .

Potrzebujemy różnych podklas Gunklasy mianowicie ShotGun Sniperi tak dalej.

ShotGun implements Gun{
    public void shoot(){
       \\shotgun implementation of shoot.
    } 
}

Sniper implements Gun{
    public void shoot(){
       \\sniper implementation of shoot.
    } 
}

Klasa strzelanek

Strzelec ma wszystkie pistolety w swojej zbroi. Stwórzmy to, Listaby je przedstawić.

List<Gun> listOfGuns = new ArrayList<Gun>();

Strzelec zmienia swoją broń, w razie potrzeby, korzystając z funkcji switchGun()

public void switchGun(){
    //code to cycle through the guns from the list of guns.
    currentGun = //the next gun in the list.
}

Możemy ustawić bieżącą broń, używając powyższej funkcji i po prostu wywołać shoot()funkcję, gdy fire()zostanie wywołana.

public void fire(){
    currentGun.shoot();
}

Zachowanie funkcji fotografowania będzie się różnić w zależności od różnych implementacji Gun interfejsu.

Wniosek

Utwórz interfejs, gdy funkcja klasy jest zależna od funkcji innej klasy , która podlega zmianie swojego zachowania, w zależności od instancji (obiektu) zaimplementowanej klasy.

na przykład fire()funkcja z Shooterklasy oczekuje, że pistolety ( Sniper, ShotGun) wykonają tę shoot()funkcję. Więc jeśli zmienimy broń i strzelimy.

shooter.switchGun();
shooter.fire();

Zmieniliśmy zachowanie fire()funkcji.


1

Aby rozwinąć to, co powiedział Larsenal. Interfejs to umowa, której muszą przestrzegać wszystkie klasy implementacyjne. Z tego powodu możesz skorzystać z techniki zwanej programowaniem do umowy. Dzięki temu oprogramowanie może stać się niezależne od implementacji.


1

Interfejsy są zwykle używane, gdy chcesz zdefiniować zachowanie, które mogą wykazywać obiekty.

Dobrym przykładem tego w świecie .NET jest interfejs IDisposable , który jest używany we wszystkich klasach Microsoft korzystających z zasobów systemowych, które należy ręcznie zwolnić. Wymaga, aby klasa implementująca go miała metodę Dispose ().

(Metoda Dispose () jest także wywoływana przy użyciu konstruktu języka VB.NET i C # , który działa tylko naIDisposable s)

Należy pamiętać, że można sprawdzić, czy obiekt implementuje określony interfejs, używając konstrukcji takich jak TypeOf ... Is(VB.NET), is(C #), instanceof(Java) itp.


1

Ponieważ kilka osób prawdopodobnie już odpowiedziało, interfejsy mogą służyć do wymuszania określonych zachowań między klasami, które nie będą wdrażać tych zachowań w ten sam sposób. Tak więc, implementując interfejs, mówisz, że twoja klasa ma takie zachowanie interfejsu. Interfejs IAnimal nie byłby typowym interfejsem, ponieważ klasy Dog, Cat, Bird itp. Są typami zwierząt i prawdopodobnie powinny go rozszerzać, co jest przypadkiem dziedziczenia. Zamiast tego interfejs byłby bardziej podobny do zachowania zwierząt w tym przypadku, na przykład IRunnable, IFlyable, ITrainable itp.

Interfejsy są dobre dla wielu rzeczy, jedną z kluczowych rzeczy jest podłączalność. Na przykład, zadeklarowanie metody z parametrem List pozwoli na przekazanie wszystkiego, co implementuje interfejs List, umożliwiając programistom usunięcie i podłączenie innej listy w późniejszym czasie bez konieczności przepisywania ton kodu.

Możliwe, że nigdy nie użyjesz interfejsów, ale jeśli projektujesz projekt od zera, zwłaszcza pewnego rodzaju framework, prawdopodobnie będziesz chciał się z nimi zapoznać.

Poleciłbym przeczytać rozdział o interfejsach w Java Design autorstwa Coad, Mayfield i Kern. Wyjaśniają to nieco lepiej niż przeciętny tekst wprowadzający. Jeśli nie używasz Javy, możesz po prostu przeczytać początek rozdziału, który jest po prostu głównie pojęciami.


1

Jak każda technika programowania, która zwiększa elastyczność twojego systemu, interfejsy również zwiększają poziom złożoności. Często są świetne i można ich używać wszędzie (możesz stworzyć interfejs dla wszystkich swoich klas) - ale dzięki temu stworzyłbyś bardziej złożony system, który byłby trudniejszy w utrzymaniu.

Jest tutaj jak zwykle kompromis: elastyczność nad łatwością w utrzymaniu. Który jest ważniejszy? Nie ma odpowiedzi - to zależy od projektu. Pamiętaj jednak, że każde oprogramowanie będzie musiało być konserwowane ...

Moja rada: nie używaj interfejsów, dopóki ich naprawdę nie potrzebujesz. (W Visual Studio możesz wyodrębnić interfejs z istniejącej klasy w ciągu 2 sekund - więc nie spiesz się.)

Powiedziawszy to, kiedy trzeba stworzyć interfejs?

Robię to, gdy refaktoryzuję metodę, która nagle musi przetworzyć dwie lub więcej podobnych klas. Następnie tworzę interfejs, przypisuję ten interfejs do dwóch (lub więcej) podobnych klas i zmieniam typ parametru metody (zamieniam typ klasy na typ interfejsu).

I to działa: o)

Jeden wyjątek: kiedy mam wyśmiewać obiekty, interfejs jest znacznie łatwiejszy w użyciu. Dlatego często tworzę interfejs właśnie do tego.

PS: kiedy piszę „interfejs”, mam na myśli: „interfejs dowolnej klasy bazowej”, w tym klasy czystego interfejsu. Zauważ, że klasy abstrakcyjne są często lepszym rozwiązaniem niż czyste interfejsy, ponieważ można do nich dodać logikę.

Pozdrawiam, Sylvain.


1

Interfejsy staną się widoczne, gdy zostaniesz programistą biblioteki (kimś, kto koduje inne kodery). Większość z nas zaczyna jako twórcy aplikacji , w których używamy istniejących interfejsów API i bibliotek programistycznych.

W tym samym wierszu, w którym interfejsy są umową , nikt jeszcze nie wspomniał, że interfejsy są doskonałym sposobem na ustabilizowanie niektórych części kodu . Jest to szczególnie przydatne, gdy jest to projekt zespołowy (lub gdy tworzysz kod używany przez innych programistów). Oto konkretny scenariusz:

Gdy tworzysz kod w zespole , inni prawdopodobnie będą używać kodu, który piszesz. Będą najbardziej zadowoleni, gdy kodują do (stabilnych) interfejsów, a będziesz szczęśliwy, gdy będziesz mieć swobodę zmiany swoich implementacji (ukrytych za interfejsem) bez łamania kodu zespołu. Jest to wariant ukrywania informacji (interfejsy są publiczne, implementacje są ukryte przed programistami klienta). Przeczytaj więcej o chronionych odmianach .

Zobacz także podobne pytanie dotyczące kodowania interfejsu .


1

Interfejs ma tak wiele celów.

  1. Użyj w zachowaniu polimorficznym. Gdzie chcesz wywoływać określone metody klasy podrzędnej z interfejsem mającym odniesienie do klasy podrzędnej.

  2. Zawarcie umowy z klasami na wdrożenie wszystkich metod tam, gdzie jest to konieczne, podobnie jak najczęściej w przypadku obiektów COM, gdzie klasa otoki jest generowana w bibliotece DLL, która dziedziczy interfejs; metody te są wywoływane za kulisami i wystarczy je zaimplementować, ale z taką samą strukturą, jak zdefiniowano w bibliotece DLL COM, którą można poznać tylko poprzez interfejs, który one ujawniają.

  3. Aby zmniejszyć zużycie pamięci, ładując określone metody w klasie. Na przykład, jeśli masz trzy obiekty biznesowe i są one zaimplementowane w jednej klasie, możesz użyć trzech interfejsów.

Np. IUser, IOrder, IOrderItem

public interface IUser()
{

void AddUser(string name ,string fname);

}

// Same for IOrder and IOrderItem
//


public class  BusinessLayer: IUser, IOrder, IOrderItem

{    
    public void AddUser(string name ,string fname)
    {
        // Do stuffs here.
    }

    // All methods from all interfaces must be implemented.

}

Jeśli chcesz tylko dodać użytkownika, wykonaj następujące czynności:

IUser user = new (IUser)BusinessLayer();

// It will load  all methods into memory which are declared in the IUser interface.

user.AddUser();
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.