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
}
}
struct
typów.Odpowiedzi:
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
IPizza
której możesz korzystaćIPizza
wszędzie tam, gdzie masz do czynienia z instancją o nieznanym typie pizzy. Każda instancja, której typ dziedziczy,IPizza
jest gwarantowana jako uporządkowana, ponieważ maOrder()
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,object
kompilator 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 lubdynamic
sł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ówinterface
wystarcza jednak ).źródło
foo
implementacjeIWidget
, programista widzi wezwanie, abyfoo.woozle()
przejrzeć dokumentacjęIWidget
i 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ąIWidget
interfejsu zostanie wdrożonyfoo
w sposób zgodny z tą umową. Natomiast w dynamicznym języku nie byłoby wyraźnego punktu odniesienia dla tego, cofoo.woozle()
powinno znaczyć.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ć
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:
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:
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
Prepare
automatycznie przygotuje każdą pizzę poprawnie w oparciu o jego typ, nawet jeśli kolekcja zawiera wiele rodzajów pizzy.źródło
public
. Tak więc w interfejsie powinno być po prostuvoid Prepare();
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:
Przód:
Dwa tygodnie później marketing decyduje, że potrzebujesz również Orków, ponieważ czytają o nich na Twitterze, więc musisz zrobić coś takiego:
Przód:
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:
Interfejs jest wtedy:
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:
A nasz front stałby się wtedy:
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
Front może być wtedy:
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:
Nasz interfejs może wtedy zostać wstrzyknięty (np. Kontroler API MVC) przez konstruktor (zazwyczaj):
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.
źródło
Oto wyjaśnione przykłady:
źródło
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 ):
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:
Następnie możesz użyć ich wszystkich jako
MenuItem
i nie przejmować się tym, jak obsługują każde wywołanie metody.źródło
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ć.
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()
naBuildScaffold()
(tj. Niewielka zmiana nazwy), wówczas będę musiał również zmienić klasę wywołującą (tj.Foreperson
Klasę) - 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:
Mogę zrobić coś takiego:
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):
Brak interfejsu (przykład 2):
Z interfejsem:
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ź.
źródło
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.
źródło
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ć.
źródło
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:
Nie byłoby to możliwe bez interfejsów, zamiast tego musiałbyś napisać taki kod:
źródło
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:
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.
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.
źródło
Weź pod uwagę, że nie można użyć wielokrotnego dziedziczenia w języku C #, a następnie ponownie spójrz na swoje pytanie.
źródło
Interfejs = kontrakt, używany do luźnego sprzężenia (patrz GRASP ).
źródło
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.drawRectangle
lubi.drawCube
wiedzieć, ż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
List
zmiennych 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.
źródło
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,
Po wspomnianym kodzie możesz mieć coś takiego:
W ten sposób używasz polimorfizmu i zależy Ci tylko na tym, aby Twoje obiekty reagowały na kolejność ().
źródło
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
źródło
Interfejsy służą do stosowania połączenia między różnymi klasami. na przykład masz klasę na samochód i drzewo;
chcesz dodać funkcjonalność dla obu klas. Ale każda klasa ma swoje własne sposoby na spalenie. więc po prostu robicie;
źródło
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.
źródło
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
Course
tegoICourse
interfejsu.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.
źródło
Oto interfejs dla obiektów o kształcie prostokątnym:
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
:To zwróci obszar dowolnego obiektu prostokątnego.
Zaimplementujmy klasę,
SwimmingPool
która jest prostokątna:I kolejna klasa,
House
która jest również prostokątna:Biorąc to pod uwagę, możesz wywołać tę
Area
metodę na domach lub basenach:W ten sposób Twoje klasy mogą „odziedziczyć” zachowanie (metody statyczne) z dowolnej liczby interfejsów.
źródło
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)
źródło
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 nichShapes
i mają metody do rysowaniaCircle
iTriangle
ale o konkretną realizację byłby zły pomysł, ponieważ jutro może zdecydujesz się jeszcze 2Shapes
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
Scenariusz na żywo Dzień 2
Jeśli zostaniesz poproszony o dodanie
Square
iRectangle
do niego, wszystko, co musisz zrobić, to stworzyć dla niego implantacjęclass Square: IShapes
wMain
liście i dodać do listyshapes.Add(new Square());
źródło
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:
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.
źródło
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ę.
źródło
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ć.
źródło
Najprostszym sposobem myślenia o interfejsach jest rozpoznanie, co oznacza dziedziczenie. Jeśli klasa CC dziedziczy klasę C, oznacza to, że:
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,
źródło
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”
źródło
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.
źródło
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ą.
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.
źródło
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).
źródło
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ń.
Ważna uwaga: interfejsy są ZAWSZE publiczne.
Mam nadzieję że to pomoże.
źródło