Od jakiegoś czasu studiuję i koduję w C #. Ale nadal nie mogę zrozumieć przydatności interfejsów. Za mało przynoszą na stół. Poza dostarczeniem sygnatur funkcji nic nie robią. Jeśli pamiętam nazwy i podpis funkcji, które należy zaimplementować, nie ma takiej potrzeby. Są one po to, aby upewnić się, że wspomniane funkcje (w interfejsie) są zaimplementowane w klasie dziedziczącej.
C # to świetny język, ale czasami daje wrażenie, że najpierw Microsoft tworzy problem (nie zezwalając na wielokrotne dziedziczenie), a następnie zapewnia rozwiązanie, które jest dość żmudne.
Takie jest moje zrozumienie oparte na ograniczonym doświadczeniu w kodowaniu. Jakie jest twoje zdanie na temat interfejsów? Jak często z nich korzystasz i co sprawia, że robisz to?
źródło
Odpowiedzi:
Poprawny. Jest to wystarczająco niesamowita korzyść, aby uzasadnić tę funkcję. Jak powiedzieli inni, interfejs jest umownym obowiązkiem wdrożenia pewnych metod, właściwości i zdarzeń. Istotną zaletą języka o typie statycznym jest to, że kompilator może zweryfikować, czy umowa, na której opiera się Twój kod, jest rzeczywiście spełniona.
To powiedziawszy, interfejsy są dość słabym sposobem na przedstawienie zobowiązań umownych. Jeśli chcesz silniejszego i bardziej elastycznego sposobu reprezentowania zobowiązań umownych, zapoznaj się z funkcją Kod umów, która została dostarczona z ostatnią wersją Visual Studio.
Cieszę się, że ci się podoba.
Wszystkie złożone projekty oprogramowania są wynikiem porównywania między sobą sprzecznych funkcji i próby znalezienia „optymalnego punktu”, który daje duże korzyści przy niewielkich kosztach. Dzięki bolesnemu doświadczeniu nauczyliśmy się, że języki, które pozwalają na wielokrotne dziedziczenie w celu współużytkowania implementacji, mają stosunkowo niewielkie zalety i stosunkowo duże koszty. Zezwolenie na wielokrotne dziedziczenie tylko na interfejsach, które nie współużytkują szczegółów implementacji, daje wiele korzyści z wielokrotnego dziedziczenia bez większości kosztów.
źródło
Assume
wywołań, dla przypadków takich jak tablica lub elementy wyliczeniowe. Po dodaniu wszystkiego i zobaczeniu statycznie zweryfikowanego bałaganu, który miałem, przywróciłem kontrolę źródła, ponieważ obniżyło to jakość mojego projektu.W tym przykładzie PowerSocket nie wie nic więcej o innych obiektach. Wszystkie obiekty zależą od zasilania dostarczanego przez PowerSocket, więc implementują IPowerPlug i dzięki temu mogą się z nim połączyć.
Interfejsy są przydatne, ponieważ zapewniają kontrakty, z których obiekty mogą współpracować bez konieczności wzajemnego poznawania się.
źródło
Celem interfejsów nie jest przypomnienie sobie, jaką metodę wdrożyć, tutaj jest zdefiniowanie umowy . W przykładzie Foreach P.Brian.Mackey (który okazuje się błędny , ale nas to nie obchodzi), IEnumerable definiuje umowę między foreach a dowolną wymienną rzeczą. Mówi: „Kimkolwiek jesteś, dopóki trzymasz się umowy (implementuj IEnumerable), obiecuję ci, że będę iterował wszystkie twoje elementy”. I to świetnie (dla języka niedynamicznego).
Dzięki interfejsom można uzyskać bardzo niskie sprzężenie między dwiema klasami.
źródło
Interfejsy są najlepszym sposobem na utrzymanie dobrze oddzielonych konstrukcji.
Podczas pisania testów przekonasz się, że konkretne klasy nie będą działać w twoim środowisku testowym.
Przykład: Chcesz przetestować klasę zależną od klasy usługi dostępu do danych . Jeśli ta klasa rozmawia z usługą internetową lub bazą danych - test jednostkowy nie uruchomi się w twoim środowisku testowym (a ponadto zamienił się w test integracyjny).
Rozwiązanie? Użyj interfejsu dla usługi dostępu do danych i udaj ten interfejs, aby przetestować swoją klasę jako jednostkę.
Z drugiej strony, WPF i Silverlight wcale nie grają z interfejsami, jeśli chodzi o wiązanie. To dość paskudna zmarszczka.
źródło
Interfejsy są kręgosłupem (statycznego) polimorfizmu! Interfejs jest najważniejszy. Dziedziczenie nie działałoby bez interfejsów, ponieważ podklasy w zasadzie dziedziczą już zaimplementowany interfejs rodzica.
Dość często. Wszystko, co musi być podłączane, to interfejs w moich aplikacjach. Często masz inne, niezwiązane ze sobą klasy, które muszą zapewnić takie samo zachowanie. Nie można rozwiązać takich problemów z dziedziczeniem.
Potrzebujesz różnych algorytmów do wykonywania operacji na tych samych danych? Użyj interfejsu ( patrz wzór strategii )!
Czy chcesz używać różnych implementacji listy? Kod przeciwko interfejsowi, a osoba dzwoniąca nie musi się martwić implementacją!
Uznano za dobrą praktykę (nie tylko w OOP) w zakresie kodowania interfejsów od wieków, z jednego tylko powodu: łatwo jest zmienić implementację, gdy zdasz sobie sprawę, że nie pasuje ona do twoich potrzeb. Jest to dość kłopotliwe, jeśli próbujesz to osiągnąć tylko przy wielokrotnym dziedziczeniu lub sprowadza się do tworzenia pustych klas w celu zapewnienia niezbędnego interfejsu.
źródło
Prawdopodobnie użyłeś
foreach
i okazało się, że jest to bardzo przydatne narzędzie do iteracji. Czy wiesz, że do działania wymagany jest interfejs, IEnumerable ?To z pewnością konkretny przypadek przemawiający za użytecznością interfejsu.
źródło
Interfejsy służą do kodowania obiektów, podobnie jak wtyczka do domowych przewodów. Czy przylutowałbyś radio bezpośrednio do okablowania domu? Co powiesz na odkurzacz? Oczywiście nie. Wtyczka i gniazdo, w które się wpasowuje, tworzą „interfejs” między okablowaniem domu a urządzeniem, które wymaga zasilania z niego. Okablowanie domu nie musi wiedzieć nic o urządzeniu poza tym, że wykorzystuje ono trzy bolcową uziemioną wtyczkę i wymaga zasilania elektrycznego przy 120 VAC <= 15 A. I odwrotnie, urządzenie nie wymaga tajemnej wiedzy na temat okablowania domu, poza tym, że ma jedno lub więcej gniazd z trzema bolcami dogodnie usytuowanych, zapewniających napięcie 120 VAC <= 15 A.
Interfejsy pełnią bardzo podobną funkcję w kodzie. Obiekt może zadeklarować, że określona zmienna, parametr lub typ zwracany jest typu interfejsu. Interfejsu nie można utworzyć bezpośrednio za pomocą
new
słowa kluczowego, ale mój obiekt może otrzymać lub znaleźć implementację tego interfejsu, z którą będzie musiał współpracować. Kiedy obiekt ma swoją zależność, nie musi dokładnie wiedzieć, czym jest ta zależność, musi tylko wiedzieć, że może wywoływać metody X, Y i Z zależności. Implementacje interfejsu nie muszą wiedzieć, w jaki sposób będą używane, muszą tylko wiedzieć, że od metod X, Y i Z można oczekiwać określonych podpisów.Tak więc, wyodrębniając wiele obiektów za tym samym interfejsem, zapewniasz wspólny zestaw funkcji każdemu konsumentowi obiektów tego interfejsu. Nie musisz wiedzieć, że obiekt to, na przykład, Lista, Słownik, LinkedList, Or uporządkowana lista lub cokolwiek innego. Ponieważ wiesz, że wszystkie są elementami IEnumerable, możesz korzystać z metod IEnumerable, aby przejść przez każdy element w tych kolekcjach pojedynczo. Nie musisz wiedzieć, że klasa wyjściowa to ConsoleWriter, FileWriter, NetworkStreamWriter, a nawet MulticastWriter, który przyjmuje inne typy programów piszących; wszystko, co musisz wiedzieć, to to, że wszyscy oni są IWriterami (lub czymkolwiek), a zatem mają metodę „Write”, do której możesz przekazać ciąg znaków, a ten ciąg zostanie wyprowadzony.
źródło
Chociaż programista (przynajmniej na początku) wyraźnie ma przyjemność mieć wielokrotne dziedziczenie, jest to prawie trywialne pominięcie i nie należy (w większości przypadków) polegać na wielokrotnym dziedziczeniu. Przyczyny tego są złożone, ale jeśli naprawdę chcesz się o tym dowiedzieć, skorzystaj z dwóch najpopularniejszych (według indeksu TIOBE ) języków programowania, które go obsługują: C ++ i Python (odpowiednio 3 i 8).
W Pythonie obsługiwane jest wielokrotne dziedziczenie, jednak programiści są prawie powszechnie niezrozumiani i stwierdzenie, że wiesz, jak to działa, oznacza przeczytanie i zrozumienie tego artykułu na temat: Kolejność rozwiązywania metod . Coś innego, co zdarza się w Pythonie, to interfejsy w pewnym sensie wprowadzone do języka - Zope.Interfaces.
W przypadku C ++, google „diamentowa hierarchia C ++” i zobacz brzydotę, która ma cię objąć. Profesjonaliści z C ++ wiedzą, jak korzystać z wielokrotnego dziedziczenia. Wszyscy inni zwykle bawią się, nie wiedząc, jakie będą wyniki. Inną rzeczą, która pokazuje, jak przydatne są interfejsy, jest fakt, że w wielu przypadkach klasa może wymagać całkowitego zastąpienia zachowania swojego rodzica. W takich przypadkach implementacja nadrzędna jest niepotrzebna i tylko obciąża klasę potomną pamięcią dla prywatnych zmiennych rodzica, co może nie mieć znaczenia w wieku C #, ale ma znaczenie, gdy programujesz osadzone. Jeśli korzystasz z interfejsu, problem nie istnieje.
Podsumowując, interfejsy są, moim zdaniem, istotną częścią OOP, ponieważ egzekwują umowę. Wielokrotne dziedziczenie jest przydatne w ograniczonych przypadkach i zwykle tylko dla facetów, którzy wiedzą, jak go używać. Tak więc, jeśli jesteś początkującym, jesteś traktowany przez brak wielokrotnego dziedziczenia - daje to lepszą szansę, aby nie popełnić błędu .
Historycznie pomysł interfejsu jest zakorzeniony znacznie wcześniej niż specyfikacje projektowe Microsoft C #. Większość ludzi uważa C # za uaktualnienie w stosunku do Javy (w większości sensów) i zgaduje, skąd C # ma swoje interfejsy - Java. Protokół jest starszym słowem dla tej samej koncepcji i jest znacznie starszy niż .NET.
Aktualizacja: Teraz widzę, że mogłem odpowiedzieć na inne pytanie - dlaczego interfejsy zamiast wielokrotnego dziedziczenia, ale wydawało się, że to odpowiedź, której szukałeś. Poza tym język OO powinien mieć przynajmniej jeden z dwóch, a pozostałe odpowiedzi obejmowały twoje pierwotne pytanie.
źródło
Trudno mi wyobrazić sobie czysty, obiektowy kod C # bez użycia interfejsów. Używasz ich, ilekroć chcesz wymusić dostępność określonej funkcjonalności bez zmuszania klas do dziedziczenia po określonej klasie bazowej, a to pozwala twojemu kodowi mieć odpowiedni poziom (niskiego) sprzężenia.
Nie zgadzam się, że wielokrotne dziedziczenie jest lepsze niż posiadanie interfejsów, nawet zanim zaczniemy argumentować, że wielokrotne dziedziczenie wiąże się z własnym zestawem bólów. Interfejsy to podstawowe narzędzie umożliwiające polimorfizm i ponowne wykorzystanie kodu. Czego więcej potrzeba?
źródło
Osobiście uwielbiam klasę abstrakcyjną i używam jej bardziej niż interfejsu. Główną różnicą jest integracja z interfejsami .NET, takimi jak IDisposable, IEnumerable i tak dalej ... oraz z interfejsem COM. Ponadto interfejs wymaga nieco mniejszego wysiłku w pisaniu niż klasa abstrakcyjna, a klasa może implementować więcej niż jeden interfejs, podczas gdy może dziedziczyć tylko z jednej klasy.
To powiedziawszy, uważam, że większość rzeczy, do których używałbym interfejsu, są lepiej obsługiwane przez klasę abstrakcyjną. Funkcje czysto wirtualne - funkcje abstrakcyjne - pozwalają zmusić implementator do zdefiniowania funkcji podobnej do tego, w jaki sposób interfejs zmusza implementatora do zdefiniowania wszystkich jego elementów.
Jednak zwykle używasz interfejsu, gdy nie chcesz narzucać określonego projektu superklasie, podczas gdy używałbyś klasy abstrakcyjnej, aby mieć projekt wielokrotnego użytku, który jest już w większości wdrożony.
Używałem interfejsów do pisania środowisk wtyczek przy użyciu przestrzeni nazw System.ComponentModel. Przydają się całkiem.
źródło
Mogę powiedzieć, że się do tego odnoszą. Kiedy po raz pierwszy zacząłem uczyć się o OO i C #, ja również nie otrzymałem interfejsów. W porządku. Musimy tylko znaleźć coś, co sprawi, że docenisz wygodę interfejsów.
Pozwól mi spróbować dwóch podejść. I wybacz mi uogólnienia.
Spróbuj 1
Powiedz, że jesteś rodzimym językiem angielskim. Wybierasz się do innego kraju, w którym angielski nie jest językiem ojczystym. Potrzebujesz pomocy. Potrzebujesz kogoś, kto może ci pomóc.
Czy pytasz: „Hej, czy urodziłeś się w Stanach Zjednoczonych?” To jest dziedzictwo.
Czy pytasz: „Hej, mówisz po angielsku”? To jest interfejs.
Jeśli zależy ci na tym, co robi, możesz polegać na interfejsach. Jeśli zależy ci na tym, co jest, polegasz na dziedzictwie.
Można polegać na dziedziczeniu. Jeśli potrzebujesz kogoś, kto mówi po angielsku, lubi herbatę i lubi piłkę nożną, lepiej jest poprosić o Brytyjczyka. :)
Spróbuj 2
Ok, spróbujmy innego przykładu.
Korzystasz z różnych baz danych i musisz zaimplementować klasy abstrakcyjne, aby z nimi pracować. Prześlesz swoją klasę do jakiejś klasy od dostawcy DB.
Wielokrotne dziedziczenie, mówisz? Wypróbuj to w powyższym przypadku. Nie możesz Kompilator nie będzie wiedział, którą metodę Connect próbujesz wywołać.
Teraz jest coś, z czym możemy współpracować - przynajmniej w języku C # - gdzie możemy jawnie implementować interfejsy.
Wniosek
Przykłady nie są najlepsze, ale myślę, że ma to sens.
Interfejsy „dostaniesz” tylko wtedy, gdy poczujesz ich potrzebę. Dopóki nie pomyślisz, że nie są dla ciebie.
źródło
Istnieją 2 główne powody:
źródło
Korzystanie z interfejsów pomaga systemowi pozostać oddzielonym od systemu, a tym samym łatwiej jest go refaktoryzować, zmieniać i ponownie wdrażać. Jest to bardzo podstawowa koncepcja obiektowej ortodoksji i po raz pierwszy się o niej dowiedziałem, gdy guru C ++ stworzyli „czyste klasy abstrakcyjne”, które są zupełnie równoważne interfejsom.
źródło
Same interfejsy nie są bardzo przydatne. Ale kiedy są implementowane przez konkretne klasy, widać, że daje to elastyczność, aby mieć jedną lub więcej implementacji. Zaletą jest to, że obiekt korzystający z interfejsu nie musi wiedzieć, jak idą szczegóły rzeczywistej implementacji - to się nazywa enkapsulacja.
źródło
Są one najczęściej używane do ponownego użycia kodu. Jeśli kodujesz do interfejsu, możesz użyć innej klasy, która dziedziczy z tego interfejsu i nie wszystko zepsuć.
Są również bardzo przydatne w usługach internetowych, w których chcesz poinformować klienta o tym, co robi klasa (aby mogli go wykorzystać), ale nie chcesz podawać rzeczywistego kodu.
źródło
Jako młody programista / programista, dopiero ucząc się języka C #, możesz nie widzieć przydatności interfejsu, ponieważ możesz pisać kody za pomocą swoich klas, a kod działa dobrze, ale w prawdziwym scenariuszu budowanie skalowalnej, niezawodnej i łatwej w utrzymaniu aplikacji wymaga użycia niektóre architektoniczne i wzorce, które można uczynić możliwym tylko za pomocą interfejsu, na przykład wstrzyknięcie zależności.
źródło
Realizacja w świecie rzeczywistym:
Możesz rzutować obiekt jako typ interfejsu:
Możesz utworzyć listę interfejsu
Za pomocą tych obiektów można uzyskać dostęp do dowolnej metody lub właściwości interfejsu. W ten sposób możesz zdefiniować interfejs dla swojej części programu. I zbuduj wokół niego logikę. Wtedy ktoś inny może wdrożyć Twój interfejs w swoich obiektach biznesowych. Jeśli BO zmieni się, mogą zmienić logikę komponentów interfejsu i nie wymagają zmiany logiki dla twojego elementu.
źródło
Interfejsy zapewniają modułowość w stylu wtyczek, zapewniając mechanizm umożliwiający klasom zrozumienie (i zasubskrybowanie) określonych rodzajów komunikatów dostarczanych przez system. Opracuję.
W aplikacji decydujesz, że za każdym razem, gdy formularz jest ładowany lub ponownie ładowany, chcesz wyczyścić wszystkie rzeczy, które hostuje. Definiujesz
IClear
interfejs, który implementujeClear
. Ponadto decydujesz, że za każdym razem, gdy użytkownik naciśnie przycisk Zapisz, formularz powinien próbować zachować swój stan. Tak więc wszystko, co przestrzega,ISave
otrzymuje komunikat o zachowaniu swojego stanu. Oczywiście, praktycznie mówiąc, większość interfejsów obsługuje kilka komunikatów.To, co wyróżnia interfejsy, to to, że wspólne zachowanie można osiągnąć bez dziedziczenia. Klasa, która implementuje dany interfejs, po prostu rozumie, jak się zachować po wydaniu polecenia (komunikat polecenia) lub jak odpowiedzieć na zapytanie (komunikat zapytania). Zasadniczo klasy w Twojej aplikacji rozumieją komunikaty dostarczane przez aplikację. Ułatwia to budowę modułowego systemu, do którego można podłączyć rzeczy.
W większości języków istnieją mechanizmy (takie jak LINQ ) do wysyłania zapytań o rzeczy, których dotyczy interfejs. Pomoże to zwykle wyeliminować logikę warunkową, ponieważ nie będziesz musiał mówić odmiennych rzeczy (które nie są konieczne, wywodzących się z tego samego łańcucha dziedziczenia), jak się zachować podobnie (zgodnie z konkretnym komunikatem). Zamiast tego zbierasz wszystko, co rozumie określoną wiadomość (przestrzega interfejsu) i publikujesz wiadomość.
Na przykład możesz zastąpić ...
...z:
Co faktycznie brzmi bardzo podobnie:
W ten sposób możemy programowo uniknąć mówienia wszystkim, aby się wyczyściło. A kiedy w przyszłości dodawane są usuwalne elementy, po prostu odpowiadają bez dodatkowego kodu.
źródło
Oto pseudokod:
Ostatnie klasy mogą być zupełnie innymi implementacjami.
Jeśli nie jest możliwe wielokrotne dziedziczenie, dziedziczenie narzuca implementację klasy nadrzędnej, czyniąc rzeczy bardziej sztywnymi. Z drugiej strony programowanie w interfejsach może pozwolić na wyjątkową elastyczność kodu lub frameworka. Jeśli kiedykolwiek natkniesz się na przypadek, w którym chciałbyś zamienić klasy w łańcuchu spadkowym, zrozumiesz dlaczego.
Na przykład framework, który zapewnia Reader pierwotnie przeznaczony do odczytu danych z dysku, może zostać ponownie zaimplementowany, aby zrobić coś o tym samym charakterze, ale w zupełnie inny sposób. Na przykład interpretuj kod Morse'a.
źródło