Co to znaczy „programować do interfejsu”?


813

Widziałem to kilka razy i nie jestem pewien, co to znaczy. Kiedy i dlaczego miałbyś to zrobić?

Wiem, co robią interfejsy, ale fakt, że nie mam jasności co do tego, sprawia, że ​​myślę, że nie używam ich poprawnie.

Czy tak jest, jeśli miałbyś zrobić:

IInterface classRef = new ObjectWhatever()

Możesz użyć dowolnej klasy, która implementuje IInterface? Kiedy musisz to zrobić? Jedyne, o czym myślę, to posiadanie metody i brak pewności, jaki obiekt zostanie przekazany, z wyjątkiem implementacji IInterface. Nie mogę myśleć, jak często trzeba to robić.

Jak można napisać metodę, która przyjmuje obiekt implementujący interfejs? Czy to jest możliwe?


3
Jeśli pamiętasz, a Twój program musi być optymalny, tuż przed kompilacją możesz zamienić deklarację interfejsu na rzeczywistą implementację. Ponieważ korzystanie z interfejsu dodaje poziom pośredni, który daje efekt wydajności. Dystrybucja kodu zaprogramowanego na interfejsy ...
Ande Turner,

18
@Ande Turner: to kiepska rada. 1). „Twój program musi być optymalny” nie jest dobrym powodem do zamiany interfejsów! Potem mówisz „Dystrybuuj swój kod zaprogramowany na interfejsy ...”, więc radzisz, aby biorąc pod uwagę ten wymóg (1), następnie zwolnisz kod nieoptymalny?!?
Mitch Wheat

74
Większość odpowiedzi tutaj nie jest w porządku. To wcale nie oznacza ani nie sugeruje „użyj słowa kluczowego interfejsu”. Interfejs to specyfikacja sposobu korzystania z czegoś - równoznacznego z umową (sprawdź to). Oddzielne jest od tego wdrożenie, czyli sposób, w jaki umowa jest realizowana. Programuj tylko w oparciu o gwarancje metody / typu, aby zmiana metody / typu w taki sposób, aby nadal była zgodna z umową, nie spowodowała uszkodzenia kodu przy jej użyciu.
jyoungdev

2
@ apollodude217, która jest najlepszą odpowiedzią na całej stronie. Przynajmniej dla pytania w tytule, ponieważ są tu co najmniej 3 całkiem różne pytania ...
Andrew Spencer

4
Podstawowym problemem związanym z takimi pytaniami jest to, że zakłada się, że „programowanie do interfejsu” oznacza „zawinięcie wszystkiego w abstrakcyjny interfejs”, co jest głupie, jeśli weźmie się pod uwagę, że pojęcie to wyprzedza koncepcję abstrakcyjnych interfejsów w stylu Java.
Jonathan Allen,

Odpowiedzi:


1633

Oto kilka wspaniałych odpowiedzi na te pytania, które zawierają wiele różnych szczegółów na temat interfejsów i luźno sprzężonego kodu, odwrócenia kontroli i tak dalej. Istnieje kilka dość poważnych dyskusji, więc chciałbym skorzystać z okazji, aby trochę rozłożyć na części, aby zrozumieć, dlaczego interfejs jest użyteczny.

Kiedy po raz pierwszy zacząłem być narażony na interfejsy, ja również byłem zdezorientowany co do ich znaczenia. Nie rozumiem, dlaczego ich potrzebujesz. Jeśli używamy języka takiego jak Java lub C #, mamy już dziedziczenie, a ja widziałem interfejsy jako słabszą formę dziedziczenia i pomyślałem: „po co zawracać sobie głowę”? W pewnym sensie miałem rację, interfejsy można traktować jako słabą formę dziedziczenia, ale poza tym w końcu zrozumiałem ich użycie jako konstrukcję językową, myśląc o nich jako o sposobie klasyfikowania wspólnych cech lub zachowań, które przejawiały potencjalnie wiele niepowiązanych klas obiektów.

Na przykład - powiedzmy, że masz grę SIM i masz następujące klasy:

class HouseFly inherits Insect {
    void FlyAroundYourHead(){}
    void LandOnThings(){}
}

class Telemarketer inherits Person {
    void CallDuringDinner(){}
    void ContinueTalkingWhenYouSayNo(){}
}

Oczywiście te dwa obiekty nie mają ze sobą nic wspólnego pod względem dziedziczenia bezpośredniego. Ale można powiedzieć, że oba są denerwujące.

Powiedzmy, że nasza gra musi mieć jakąś przypadkową rzecz, która denerwuje gracza podczas zjedzenia obiadu. Może to być HouseFlyaTelemarketer lub oba - ale jak zezwolić na jedno i drugie za pomocą jednej funkcji? I w jaki sposób prosisz każdy inny typ obiektu, aby „robił irytującą rzecz” w ten sam sposób?

Kluczem do zrozumienia jest to, że zarówno „a”, jak Telemarketeri „ HouseFlymają wspólne, luźno interpretowane zachowanie, nawet jeśli nie są one podobne pod względem modelowania. Stwórzmy interfejs, który można wdrożyć:

interface IPest {
    void BeAnnoying();
}

class HouseFly inherits Insect implements IPest {
    void FlyAroundYourHead(){}
    void LandOnThings(){}

    void BeAnnoying() {
        FlyAroundYourHead();
        LandOnThings();
    }
}

class Telemarketer inherits Person implements IPest {
    void CallDuringDinner(){}
    void ContinueTalkingWhenYouSayNo(){}

    void BeAnnoying() {
        CallDuringDinner();
        ContinueTalkingWhenYouSayNo();
    }
}

Mamy teraz dwie klasy, z których każda może być denerwująca na swój sposób. I nie muszą wywodzić się z tej samej klasy bazowej i dzielić wspólne nieodłączne cechy - po prostu muszą spełnić umowę IPest- ta umowa jest prosta. Musisz tylko BeAnnoying. W tym względzie możemy modelować następujące elementy:

class DiningRoom {

    DiningRoom(Person[] diningPeople, IPest[] pests) { ... }

    void ServeDinner() {
        when diningPeople are eating,

        foreach pest in pests
        pest.BeAnnoying();
    }
}

Tutaj mamy jadalnię, która akceptuje wiele gości i szkodników - zwróć uwagę na użycie interfejsu. Oznacza to, że w naszym małym świecie członkiem peststablicy może być Telemarketerobiekt lub HouseFlyobiekt.

ServeDinnerMetoda jest wywoływana, gdy kolacja serwowana jest i nasi ludzie w jadalni mają jeść. W naszej małej grze nasze szkodniki wykonują swoją pracę - każdy szkodnik jest instruowany, aby był denerwujący za pośrednictwem IPestinterfejsu. W ten sposób możemy z łatwością mieć jedno Telemarketersi drugie HouseFlysirytujące na każdy z ich własnych sposobów - dbamy tylko o to, że mamy w DiningRoomobiekcie coś, co jest szkodnikiem, tak naprawdę nie dbamy o to, co to jest i nie mogą mieć nic w sobie wspólne z innymi.

Ten bardzo wymyślny przykład pseudokodu (który przeciągał się o wiele dłużej, niż się spodziewałem) ma po prostu zilustrować coś, co w końcu zapaliło mnie pod kątem, kiedy możemy użyć interfejsu. Z góry przepraszam za głupotę tego przykładu, ale mam nadzieję, że to pomoże w twoim zrozumieniu. I, oczywiście, inne opublikowane odpowiedzi, które tutaj otrzymałeś, naprawdę obejmują gamę zastosowań interfejsów w dzisiejszych wzorcach projektowych i metodologiach programistycznych.


3
Inną rzeczą do rozważenia jest to, że w niektórych przypadkach przydatne może być posiadanie interfejsu dla rzeczy, które „mogą” być denerwujące i mieć różnorodne obiekty zaimplementowane BeAnnoyingjako brak możliwości; interfejs ten może istnieć zamiast interfejsu lub jako dodatek do rzeczy, które są denerwujące (jeśli istnieją oba interfejsy, interfejs „rzeczy, które denerwujące” powinien odziedziczyć po interfejsie „rzeczy, które mogą być denerwujące”). Wadą korzystania z takich interfejsów jest to, że implementacje mogą być obciążone wdrażaniem „irytującej” liczby metod pośredniczących. Zaletą jest to, że ...
supercat

4
Metody nie mają na celu reprezentowania metod abstrakcyjnych - ich implementacja nie ma znaczenia dla pytania, które koncentrowało się na interfejsach.
Peter Meyer

33
Zachowania enkapsulujące, takie jak IPest, są znane jako wzorzec strategii na wypadek, gdyby ktoś był zainteresowany kontynuowaniem dalszych materiałów na ten temat ...
nckbrz

9
Co ciekawe, nie zaznaczasz, że ponieważ obiekty w IPest[]odwołaniach IPest można wywoływać, BeAnnoying()ponieważ mają one tę metodę, podczas gdy nie można wywoływać innych metod bez rzutowania. Jednak dla każdego obiektu BeAnnoying()zostanie wywołana osobna metoda.
D. Ben Knoble

4
Bardzo dobre wytłumaczenie ... Po prostu trzeba to powiedzieć tutaj: Nigdy nie słyszałem o interfejsach będących jakiś luźny mechanizm dziedziczenia, lecz wiem dziedziczenia wykorzystywane jako biedny mechanizm definiowania interfejsów (na przykład w regularnym Pythona rób to cały czas).
Carlos H Romano,

282

Konkretnym przykładem, który podałem uczniom, jest to, że powinni pisać

List myList = new ArrayList(); // programming to the List interface

zamiast

ArrayList myList = new ArrayList(); // this is bad

Wyglądają dokładnie tak samo w krótkim programie, ale jeśli użyjesz myList100 razy w swoim programie, możesz zacząć widzieć różnicę. Pierwsza deklaracja zapewnia, że ​​wywołujesz tylko metody myListzdefiniowane przez Listinterfejs (więc nie ma ArrayListokreślonych metod). Jeśli tak zaprogramowałeś interfejs, możesz później zdecydować, że naprawdę potrzebujesz

List myList = new TreeList();

i musisz zmienić kod tylko w tym jednym miejscu. Wiesz już, że reszta kodu nie robi nic, co zostanie zepsute przez zmianę implementacji, ponieważ zaprogramowałeś interfejs .

Korzyści są jeszcze bardziej oczywiste (myślę), gdy mówimy o parametrach metody i zwracanych wartościach. Weźmy na przykład:

public ArrayList doSomething(HashMap map);

Ta deklaracja metody wiąże cię z dwoma konkretnymi implementacjami ( ArrayListi HashMap). Jak tylko ta metoda zostanie wywołana z innego kodu, wszelkie zmiany tych typów prawdopodobnie oznaczają, że będziesz musiał również zmienić kod wywołujący. Lepiej byłoby zaprogramować interfejsy.

public List doSomething(Map map);

Teraz nie ma znaczenia, jaki rodzaj Listzwrotu powrócisz lub jaki parametr Mapzostanie przekazany jako parametr. Zmiany, które wprowadzasz w doSomethingmetodzie, nie zmuszają cię do zmiany kodu wywołującego.


Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
Yvette,

Bardzo jasne wytłumaczenie. Bardzo pomocny
samuel luswata,

Mam pytanie o powód, dla którego wspomniałeś: „Pierwsza deklaracja zapewnia, że ​​wywołujesz tylko metody na mojej liście, które są zdefiniowane przez interfejs List (więc nie ma metod specyficznych dla ArrayList). Jeśli tak zaprogramowałeś interfejs, to później może zdecydować, że naprawdę potrzebujesz List myList = new TreeList (); musisz tylko zmienić kod w tym jednym miejscu. " Może źle zrozumiałem, zastanawiam się, dlaczego musisz zmienić ArrayList na TreeList, jeśli chcesz „upewnić się, że wywołujesz metody tylko na myList”?
user3014901,

1
@ user3014901 Istnieje wiele powodów, dla których możesz chcieć zmienić typ używanej listy. Można na przykład uzyskać lepszą wydajność wyszukiwania. Chodzi o to, że jeśli programujesz do interfejsu List, łatwiej jest później zmienić kod na inną implementację.
Bill the Lizard

73

Programowanie interfejsu mówi: „Potrzebuję tej funkcjonalności i nie dbam o to, skąd ona pochodzi”.

Rozważyć (w Javie), ten Listinterfejs kontra ArrayListi LinkedListkonkretnych zajęć. Jeśli zależy mi tylko na tym, że mam strukturę danych zawierającą wiele elementów danych, do których powinienem uzyskać dostęp poprzez iterację, wybrałbym List(i to w 99% przypadków). Jeśli wiem, że potrzebuję wstawiania / usuwania w czasie stałym z dowolnego końca listy, mogę wybrać LinkedListkonkretną implementację (lub, co bardziej prawdopodobne, użyć interfejsu kolejki ). Jeśli wiem, że potrzebuję dostępu losowego według indeksu, wybrałbym ArrayListkonkretną klasę.


1
całkowicie się zgadzają, tj. niezależność między tym, co jest zrobione, a tym, jak się to robi. Dzieląc system na niezależne komponenty, otrzymujesz system, który jest prosty i wielokrotnego użytku (patrz Simple Made Easy autorstwa faceta, który stworzył Clojure)
beluchin

38

Korzystanie z interfejsów jest kluczowym czynnikiem ułatwiającym testowanie kodu, a także usuwanie niepotrzebnych połączeń między klasami. Tworząc interfejs definiujący operacje na klasie, umożliwiasz klasom, które chcą korzystać z tej funkcji, możliwość korzystania z niej bez bezpośredniego uzależnienia od klasy implementującej. Jeśli później zdecydujesz się zmienić i zastosować inną implementację, wystarczy zmienić tylko część kodu, w której implementacja jest tworzona. Reszta kodu nie musi się zmieniać, ponieważ zależy od interfejsu, a nie od klasy implementującej.

Jest to bardzo przydatne w tworzeniu testów jednostkowych. W testowanej klasie zależy to od interfejsu i wstrzykuje instancję interfejsu do klasy (lub fabryki, która pozwala mu budować instancje interfejsu w razie potrzeby) za pośrednictwem konstruktora lub ustawiacza właściwości. Klasa używa podanego (lub utworzonego) interfejsu w swoich metodach. Kiedy idziesz pisać testy, możesz kpić lub sfałszować interfejs i zapewnić interfejs, który odpowiada danymi skonfigurowanymi w teście jednostkowym. Możesz to zrobić, ponieważ testowana klasa zajmuje się tylko interfejsem, a nie konkretną implementacją. Zrobią to wszystkie klasy implementujące interfejs, w tym twoja próbna lub fałszywa klasa.

EDYCJA: Poniżej znajduje się link do artykułu, w którym Erich Gamma omawia swój cytat: „Program do interfejsu, a nie implementacja”.

http://www.artima.com/lejava/articles/designprinciples.html


3
Przeczytaj jeszcze raz ten wywiad: Gamma oczywiście mówiła o koncepcji interfejsu OO, a nie o JAVA lub specjalnym rodzaju klasy C # (ISomething). Problem w tym, że większość ludzi mówiło o tym słowie kluczowym, więc mamy teraz wiele niepotrzebnych interfejsów (ISomething).
Sylvain Rodrigue

Bardzo dobry wywiad. Zachowaj ostrożność dla przyszłych czytelników, w wywiadzie są cztery strony. Prawie zamknąłem przeglądarkę, zanim ją zobaczę.
Ad Infinitum

37

Programowanie interfejsu nie ma absolutnie nic wspólnego z abstrakcyjnymi interfejsami, jak widzimy w Javie lub .NET. To nawet nie jest koncepcja OOP.

Oznacza to, że nie należy się bawić w elementy wewnętrzne obiektu lub struktury danych. Użyj interfejsu programu abstrakcyjnego lub interfejsu API do interakcji z danymi. W Javie lub C # oznacza to używanie publicznych właściwości i metod zamiast surowego dostępu do pola. Dla C oznacza to używanie funkcji zamiast surowych wskaźników.

EDYCJA: A w przypadku baz danych oznacza to używanie widoków i procedur przechowywanych zamiast bezpośredniego dostępu do tabeli.


5
Najlepsza odpowiedź. Gamma daje podobne wyjaśnienie tutaj: artima.com/lejava/articles/designprinciples.html (patrz strona 2). Odwołuje się do koncepcji OO, ale masz rację: jest ona większa.
Sylvain Rodrigue

36

Powinieneś spojrzeć na Odwrócenie kontroli:

W takim scenariuszu nie napisałbyś tego:

IInterface classRef = new ObjectWhatever();

Napisałbyś coś takiego:

IInterface classRef = container.Resolve<IInterface>();

Spowoduje to przejście do konfiguracji opartej na regułach w containerobiekcie i skonstruowanie dla Ciebie rzeczywistego obiektu, którym może być ObjectWhokolwiek. Ważną rzeczą jest to, że możesz zastąpić tę regułę czymś, co używa zupełnie innego typu obiektu, a Twój kod nadal będzie działał.

Jeśli zostawimy IoC poza tabelą, możesz napisać kod, który będzie wiedział, że może komunikować się z obiektem, który robi coś konkretnego , ale nie z jakiego typu obiektu i jak to robi.

Przydałoby się to przy przekazywaniu parametrów.

Jeśli chodzi o twoje nawiasowe pytanie „Jak można napisać metodę, która przyjmuje obiekt, który implementuje interfejs? Czy to możliwe?”, W języku C # wystarczy użyć typu interfejsu dla typu parametru, jak poniżej:

public void DoSomethingToAnObject(IInterface whatever) { ... }

To podłącza się bezpośrednio do „rozmowy z obiektem, który robi coś konkretnego”. Zdefiniowana powyżej metoda wie, czego oczekiwać od obiektu, że implementuje wszystko w IInterface, ale nie dba o to, jaki jest typ obiektu, tylko przestrzega kontraktu, jakim jest interfejs.

Na przykład, prawdopodobnie znasz kalkulatory i prawdopodobnie używałeś sporo w swoich czasach, ale przez większość czasu są one różne. Ty z drugiej strony wiesz, jak powinien działać standardowy kalkulator, więc możesz korzystać z nich wszystkich, nawet jeśli nie możesz korzystać z określonych funkcji, które ma każdy kalkulator, których nie ma żaden inny.

To jest piękno interfejsów. Możesz napisać fragment kodu, który wie, że otrzyma obiekty, od których może oczekiwać określonego zachowania. Nie przejmuje się jednym hukiem, jaki to obiekt, tylko że obsługuje potrzebne zachowanie.

Dam ci konkretny przykład.

Mamy niestandardowy system tłumaczenia formularzy Windows. Ten system zapętla formanty w formularzu i tłumaczy tekst w każdym z nich. System wie, jak obsługiwać podstawowe elementy sterujące, takie jak właściwość-type-of-control-that-has-a-Text i podobne podstawowe rzeczy, ale w przypadku wszystkiego podstawowego jest on niewystarczający.

Ponieważ kontrole dziedziczą po predefiniowanych klasach, nad którymi nie mamy kontroli, moglibyśmy zrobić jedną z trzech rzeczy:

  1. Zbuduj wsparcie dla naszego systemu tłumaczeń, aby wykryć konkretnie rodzaj kontroli, z którym współpracuje, i przetłumacz odpowiednie bity (koszmar konserwacji)
  2. Wbuduj obsługę w klasy podstawowe (niemożliwe, ponieważ wszystkie formanty dziedziczą po różnych predefiniowanych klasach)
  3. Dodaj obsługę interfejsu

Więc zrobiliśmy nr. 3. Wszystkie nasze kontrolki implementują ILocalizable, który jest interfejsem, który daje nam jedną metodę, możliwość przetłumaczenia „samego” na pojemnik tekstu / reguł tłumaczenia. W związku z tym formularz nie musi wiedzieć, jaki rodzaj kontroli znalazł, tylko że implementuje konkretny interfejs i wie, że istnieje metoda, w której można go wywołać w celu zlokalizowania kontroli.


31
Po co wspominać o IoC na samym początku, ponieważ spowodowałoby to dodatkowe zamieszanie.
Kevin Le - Khnle

1
Zgadzam się, powiedziałbym, że programowanie przeciwko interfejsom to tylko technika, dzięki której IoC jest łatwiejszy i bardziej niezawodny.
terjetyl

28

Kod interfejsu Nie Implementacja nie ma nic wspólnego z Javą ani jej konstrukcją interfejsu.

Ta koncepcja została wyeksponowana w książkach Patterns / Gang of Four, ale najprawdopodobniej była wcześniej na długo. Koncepcja z pewnością istniała na długo przed powstaniem Javy.

Konstrukcja interfejsu Java została stworzona, aby pomóc w tym pomyśle (między innymi), a ludzie zbyt skupili się na konstrukcji jako centrum znaczenia, a nie pierwotnych zamiarach. Jest to jednak powód, dla którego mamy publiczne i prywatne metody i atrybuty w Javie, C ++, C # itp.

Oznacza to po prostu interakcję z publicznym interfejsem obiektu lub systemu. Nie martw się ani nawet nie przewiduj, w jaki sposób robi to, co robi wewnętrznie. Nie martw się o to, jak to jest realizowane. W kodzie zorientowanym obiektowo właśnie dlatego mamy publiczne / prywatne metody / atrybuty. Mamy zamiar korzystać z metod publicznych, ponieważ metody prywatne są dostępne tylko do użytku wewnętrznego w klasie. Składają się na implementację klasy i mogą być zmieniane zgodnie z wymaganiami bez zmiany interfejsu publicznego. Załóżmy, że jeśli chodzi o funkcjonalność, metoda na klasie wykona tę samą operację z takim samym oczekiwanym rezultatem za każdym razem, gdy wywołasz ją z tymi samymi parametrami. Pozwala autorowi zmienić sposób działania klasy, jej implementację, bez przerywania interakcji z nią.

I możesz programować do interfejsu, a nie implementacji bez użycia konstrukcji interfejsu. Możesz zaprogramować interfejs, a nie implementację w C ++, który nie ma konstrukcji interfejsu. Możesz zintegrować dwa potężne systemy korporacyjne o wiele solidniej, pod warunkiem, że współdziałają one za pośrednictwem publicznych interfejsów (kontraktów), zamiast wywoływać metody na obiektach wewnętrznych systemów. Interfejsy powinny zawsze reagować w ten sam oczekiwany sposób, biorąc pod uwagę te same parametry wejściowe; jeśli zaimplementowane do interfejsu, a nie implementacji. Koncepcja działa w wielu miejscach.

Pomiń myśl, że interfejsy Java mają cokolwiek wspólnego z koncepcją „Program do interfejsu, a nie implementacja”. Mogą one pomóc zastosować koncepcję, ale są one nie pojęcie.


1
Pierwsze zdanie mówi wszystko. To powinna być zaakceptowana odpowiedź.
madumlao

14

Wygląda na to, że rozumiesz, jak działają interfejsy, ale nie masz pewności, kiedy ich używać i jakie oferują one zalety. Oto kilka przykładów, kiedy interfejs miałby sens:

// if I want to add search capabilities to my application and support multiple search
// engines such as Google, Yahoo, Live, etc.

interface ISearchProvider
{
    string Search(string keywords);
}

potem mógłbym utworzyć GoogleSearchProvider, YahooSearchProvider, LiveSearchProvider itp.

// if I want to support multiple downloads using different protocols
// HTTP, HTTPS, FTP, FTPS, etc.
interface IUrlDownload
{
    void Download(string url)
}

// how about an image loader for different kinds of images JPG, GIF, PNG, etc.
interface IImageLoader
{
    Bitmap LoadImage(string filename)
}

następnie utwórz JpegImageLoader, GifImageLoader, PngImageLoader itp.

Większość dodatków i systemów wtyczek działa bez interfejsów.

Innym popularnym zastosowaniem jest wzorzec repozytorium. Powiedz, że chcę załadować listę kodów pocztowych z różnych źródeł

interface IZipCodeRepository
{
    IList<ZipCode> GetZipCodes(string state);
}

następnie mógłbym utworzyć XMLZipCodeRepository, SQLZipCodeRepository, CSVZipCodeRepository itp. Dla moich aplikacji internetowych często tworzę repozytoria XML wcześnie, aby móc coś uruchomić i uruchomić, zanim baza danych SQL będzie gotowa. Gdy baza danych jest gotowa, piszę SQLRepository, aby zastąpić wersję XML. Reszta mojego kodu pozostaje niezmieniona, ponieważ działa wyłącznie poza interfejsami.

Metody mogą akceptować interfejsy, takie jak:

PrintZipCodes(IZipCodeRepository zipCodeRepository, string state)
{
    foreach (ZipCode zipCode in zipCodeRepository.GetZipCodes(state))
    {
        Console.WriteLine(zipCode.ToString());
    }
}

10

To sprawia, że ​​twój kod jest znacznie bardziej rozszerzalny i łatwiejszy w utrzymaniu, gdy masz zestawy podobnych klas. Jestem młodszym programistą, więc nie jestem ekspertem, ale właśnie skończyłem projekt, który wymagał czegoś podobnego.

Pracuję na oprogramowaniu po stronie klienta, które komunikuje się z serwerem z urządzeniem medycznym. Pracujemy nad nową wersją tego urządzenia, która zawiera nowe komponenty, które klient musi czasem skonfigurować. Istnieją dwa rodzaje nowych komponentów i są one różne, ale są również bardzo podobne. Zasadniczo musiałem utworzyć dwie formularze konfiguracji, dwie listy klas, dwa wszystkiego.

Zdecydowałem, że najlepiej będzie utworzyć abstrakcyjną klasę podstawową dla każdego typu kontrolnego, która zawierałaby prawie całą prawdziwą logikę, a następnie wyprowadzić typy, aby zająć się różnicami między tymi dwoma składnikami. Jednak klasy podstawowe nie byłyby w stanie wykonywać operacji na tych komponentach, gdybym musiał cały czas martwić się typami (no cóż, mogłyby to mieć, ale w każdej metodzie byłaby instrukcja „if” lub przełącznik) .

Zdefiniowałem prosty interfejs dla tych komponentów i wszystkie klasy podstawowe rozmawiają z tym interfejsem. Teraz, kiedy coś zmieniam, prawie „po prostu działa” wszędzie i nie mam duplikacji kodu.


9

Istnieje wiele wyjaśnień, ale dla uproszczenia. Weźmy na przykład a List. Można zaimplementować listę jako:

  1. Tablica wewnętrzna
  2. Połączona lista
  3. Inne wdrożenia

Budując do interfejsu, powiedz a List. Podajesz tylko definicję Listy lub co Listw rzeczywistości oznacza.

Możesz użyć dowolnego rodzaju wewnętrznej implementacji, powiedz arrayimplementację. Ale załóżmy, że chcesz zmienić implementację z jakiegoś powodu, powiedz błąd lub wydajność. Następnie wystarczy zmienić deklarację List<String> ls = new ArrayList<String>()na List<String> ls = new LinkedList<String>().

Nigdzie indziej w kodzie nie będziesz musiał niczego zmieniać; Ponieważ wszystko inne zostało zbudowane na podstawie definicji List.


8

Jeśli programujesz w Javie, JDBC jest dobrym przykładem. JDBC definiuje zestaw interfejsów, ale nic nie mówi o implementacji. Twoje aplikacje mogą być napisane w oparciu o ten zestaw interfejsów. Teoretycznie wybierasz sterownik JDBC, a aplikacja po prostu działa. Jeśli odkryjesz, że istnieje szybszy lub „lepszy” lub tańszy sterownik JDBC lub z jakiegokolwiek innego powodu, możesz ponownie teoretycznie ponownie skonfigurować plik właściwości i bez konieczności wprowadzania zmian w aplikacji, aplikacja nadal działałaby.


Jest to przydatne nie tylko w przypadku, gdy dostępny będzie lepszy sterownik, umożliwia całkowitą zmianę dostawców baz danych.
Ken Liu,

3
JDBC jest tak zły, że wymaga wymiany. Znajdź inny przykład.
Joshua

JDBC jest zły, ale nie ma żadnego powodu, aby mieć związek z implementacją interfejsu lub poziomem abstrakcji. Aby zilustrować omawianą koncepcję, jest ona idealna.
Erwin Smout

8

Programowanie interfejsów jest niesamowite, promuje luźne sprzężenie. Jak wspomniano @lassevk, Inversion of Control jest świetnym zastosowaniem tego.

Ponadto spójrz na zasady SOLID . oto seria filmów

Przechodzi przez mocno zakodowany (silnie sprzężony przykład), a następnie przegląda interfejsy, w końcu przechodzi do narzędzia IoC / DI (NInject)


7

Jestem spóźniony na to pytanie, ale chcę tutaj wspomnieć, że wiersz „Program do interfejsu, a nie implementacja” miał dobrą dyskusję w książce Wzory projektowe GoF (Gang of Four).

Stwierdzono na str. 18:

Zaprogramuj interfejs, a nie implementację

Nie deklaruj zmiennych jako instancji konkretnych klas. Zamiast tego zatwierdzaj tylko interfejs zdefiniowany przez klasę abstrakcyjną. Przekonasz się, że jest to wspólny motyw wzorców projektowych w tej książce.

a ponadto zaczęło się od:

Istnieją dwie zalety manipulowania obiektami wyłącznie pod względem interfejsu zdefiniowanego przez klasy abstrakcyjne:

  1. Klienci pozostają nieświadomi określonych typów obiektów, których używają, o ile obiekty przylegają do interfejsu, którego oczekują klienci.
  2. Klienci pozostają nieświadomi klas, które implementują te obiekty. Klienci wiedzą tylko o klasach abstrakcyjnych definiujących interfejs.

Innymi słowy, nie pisz tego swoim klasom, aby zawierała quack()metodę dla kaczek, a następnie bark()metodę dla psów, ponieważ są one zbyt specyficzne dla konkretnej implementacji klasy (lub podklasy). Zamiast tego napisz metodę, używając nazw, które są na tyle ogólne, że można ich używać w klasie podstawowej, takich jak giveSound()lub move(), aby można było ich używać w przypadku kaczek, psów, a nawet samochodów, a wtedy klient twoich klas może po prostu powiedzieć, .giveSound()a nie zastanawiając się, czy użyć, quack()czy bark()nawet określić typ, przed wydaniem poprawnej wiadomości, która zostanie wysłana do obiektu.


6

Oprócz już wybranej odpowiedzi (i różnych postów informacyjnych tutaj), zdecydowanie polecam pobranie kopii Wzorów pierwszego projektu . Jest to bardzo łatwa lektura i bezpośrednio odpowie na twoje pytanie, wyjaśni, dlaczego jest to ważne i pokaże wiele wzorców programowania, których możesz użyć, aby skorzystać z tej zasady (i innych).


5

Aby dodać do istniejących postów, czasami kodowanie interfejsów pomaga w dużych projektach, gdy programiści pracują jednocześnie nad oddzielnymi komponentami. Wystarczy zdefiniować interfejsy z góry i napisać do nich kod, podczas gdy inni programiści piszą kod do wdrażanego interfejsu.


4

Jest również dobry do testowania jednostkowego, możesz wstrzyknąć własne klasy (które spełniają wymagania interfejsu) do klasy, która jest od niego zależna


4

Korzystne może być programowanie interfejsów, nawet jeśli nie jesteśmy zależni od abstrakcji.

Programowanie interfejsów zmusza nas do użycia odpowiedniego kontekstowo podzbioru obiektu . To pomaga, ponieważ:

  1. uniemożliwia nam robienie kontekstowo nieodpowiednich rzeczy oraz
  2. pozwala nam bezpiecznie zmienić implementację w przyszłości.

Na przykład, rozważmy Personklasy implementującej Friendi Employeeinterfejs.

class Person implements AbstractEmployee, AbstractFriend {
}

W kontekście urodzin danej osoby programujemy Friendinterfejs, aby zapobiec traktowaniu tej osoby jak kogoś Employee.

function party() {
    const friend: Friend = new Person("Kathryn");
    friend.HaveFun();
}

W kontekście pracy osoby programujemy Employeeinterfejs, aby zapobiec rozmyciu granic miejsca pracy.

function workplace() {
    const employee: Employee = new Person("Kathryn");
    employee.DoWork();
}

Świetny. Zachowaliśmy się odpowiednio w różnych kontekstach, a nasze oprogramowanie działa dobrze.

W dalekiej przyszłości, jeśli nasza firma zmieni się na pracę z psami, możemy dość łatwo zmienić oprogramowanie. Najpierw tworzymy Dogklasę, która implementuje zarówno Friendi Employee. Następnie bezpiecznie zmieniamy new Person()na new Dog(). Nawet jeśli obie funkcje mają tysiące wierszy kodu, ta prosta edycja będzie działać, ponieważ wiemy, że następujące są prawdziwe:

  1. Funkcja partyużywa tylko Friendpodzbioru Person.
  2. Funkcja workplaceużywa tylko Employeepodzbioru Person.
  3. Klasa Dogimplementuje zarówno interfejsy, jak Friendi Employeeinterfejsy.

Z drugiej strony, gdyby którykolwiek partylub workplacemiałby się programować przeciwko Person, istniałoby ryzyko, że oba będą miały Personkod -specyficzny. Zmiana z Personna Dogwymagałaby od nas przeczesania kodu w celu wytępienia dowolnego Personkodu specyficznego dla tegoDog .

Morał : programowanie interfejsów pomaga naszemu kodowi zachowywać się odpowiednio i być gotowym na zmiany. Przygotowuje również nasz kod do zależności od abstrakcji, co daje jeszcze więcej korzyści.


1
Zakładając, że nie masz zbyt szerokich interfejsów.
Casey

4

Jeśli piszę nową klasę, Swimmeraby dodać funkcjonalność swim()i muszę użyć obiektu klasy powiedz Dog, a ta Dogklasa implementuje interfejs, Animalktóry deklaruje swim().

Na górze hierarchii ( Animal) jest bardzo abstrakcyjna, a na dole ( Dog) bardzo konkretna. Sposób, w jaki myślę o „programowaniu interfejsów”, polega na tym, że pisząc Swimmerklasę, chcę pisać mój kod na interfejsie znajdującym się tak daleko od hierarchii, która w tym przypadku jestAnimal obiektem. Interfejs jest wolny od szczegółów implementacyjnych, przez co kod jest luźno powiązany.

Szczegóły implementacji można zmieniać z czasem, jednak nie wpłynie to na pozostały kod, ponieważ interakcja odbywa się wyłącznie z interfejsem, a nie z implementacją. Nie obchodzi cię, jak wygląda implementacja ... wiesz tylko, że będzie klasa, która implementuje interfejs.


3

Tak więc, aby to zrobić poprawnie, zaletą interfejsu jest to, że mogę oddzielić wywołanie metody od dowolnej konkretnej klasy. Zamiast tego tworzy instancję interfejsu, w której implementacja jest podana z dowolnej klasy, którą wybiorę, która implementuje ten interfejs. Dzięki temu mogę mieć wiele klas, które mają podobną, ale nieco inną funkcjonalność, aw niektórych przypadkach (przypadki związane z intencją interfejsu) nie dbam o to, który to obiekt.

Na przykład mógłbym mieć interfejs ruchu. Metoda, która sprawia, że ​​coś „porusza się” i dowolny obiekt (Osoba, Samochód, Kot), który implementuje interfejs ruchu, może zostać przekazana i nakazana ruch. Bez tej metody wszyscy znają rodzaj klasy.


3

Wyobraź sobie, że masz produkt o nazwie „Zebra”, który można rozszerzyć za pomocą wtyczek. Znajduje wtyczki, wyszukując biblioteki DLL w jakimś katalogu. Ładuje wszystkie te biblioteki DLL i używa refleksji, aby znaleźć wszystkie implementujące klasyIZebraPlugin , a następnie wywołuje metody tego interfejsu w celu komunikacji z wtyczkami.

To czyni go całkowicie niezależnym od jakiejkolwiek konkretnej klasy wtyczek - nie ma znaczenia, jakie to klasy. Dba tylko o to, aby spełniały specyfikację interfejsu.

Interfejsy są sposobem definiowania takich punktów rozszerzalności. Kod komunikujący się z interfejsem jest luźniej sprzężony - w rzeczywistości nie jest w ogóle sprzężony z żadnym innym konkretnym kodem. Może współpracować z wtyczkami napisanymi wiele lat później przez osoby, które nigdy nie spotkały oryginalnego programisty.

Zamiast tego możesz użyć klasy podstawowej z funkcjami wirtualnymi - wszystkie wtyczki zostałyby wyprowadzone z klasy podstawowej. Jest to jednak znacznie bardziej ograniczające, ponieważ klasa może mieć tylko jedną klasę bazową, podczas gdy może implementować dowolną liczbę interfejsów.


3

Objaśnienie C ++.

Pomyśl o interfejsie jako metodach publicznych swoich klas.

Następnie możesz utworzyć szablon, który „zależy” od tych publicznych metod w celu wykonania własnej funkcji (sprawia, że ​​wywołania funkcji są zdefiniowane w interfejsie publicznym klas). Powiedzmy, że ten szablon jest kontenerem, takim jak klasa Vector, a interfejs, od którego zależy, jest algorytmem wyszukiwania.

Każda klasa algorytmów, która definiuje funkcje / interfejs Vector, do którego wywołuje Vector, spełnia „kontrakt” (jak ktoś wyjaśnił w oryginalnej odpowiedzi). Algorytmy nie muszą nawet należeć do tej samej klasy podstawowej; jedynym wymaganiem jest zdefiniowanie funkcji / metod, od których Vector zależy (interfejs), w twoim algorytmie.

Chodzi o to, że można podać dowolny inny algorytm / klasę wyszukiwania, o ile zapewnia on interfejs, od którego Vector jest zależny (wyszukiwanie bąbelkowe, wyszukiwanie sekwencyjne, szybkie wyszukiwanie).

Możesz także zaprojektować inne kontenery (listy, kolejki), które wykorzystywałyby ten sam algorytm wyszukiwania, co Vector, dzięki temu, że spełniają interfejs / kontrakt, od którego zależą twoje algorytmy wyszukiwania.

To oszczędza czas (zasada OOP „ponowne użycie kodu”), ponieważ możesz napisać algorytm raz za razem, specyficzny dla każdego nowego obiektu, który tworzysz, bez nadmiernego komplikowania problemu z przerośniętym drzewem dziedziczenia.

Co do „pominięcia” sposobu działania; big-time (przynajmniej w C ++), ponieważ tak działa większość frameworku Standardowej Biblioteki TEMPLATE.

Oczywiście przy stosowaniu klas dziedziczenia i klas abstrakcyjnych zmienia się metodologia programowania interfejsu; ale zasada jest taka sama, twoje publiczne funkcje / metody są interfejsem klas.

To ogromny temat i jedna z podstawowych zasad wzorców projektowych.


3

W Javie te konkretne klasy implementują interfejs CharSequence:

CharBuffer, String, StringBuffer, StringBuilder

Te konkretne klasy nie mają wspólnej klasy nadrzędnej innej niż Object, więc nie ma nic, co by je łączyło, poza faktem, że każda z nich ma coś wspólnego z tablicami znaków, reprezentującymi takie lub manipulującymi nimi. Na przykład znaków String nie można zmienić po utworzeniu instancji obiektu String, natomiast można edytować znaki StringBuffer lub StringBuilder.

Jednak każda z tych klas jest w stanie odpowiednio zaimplementować metody interfejsu CharSequence:

char charAt(int index)
int length()
CharSequence subSequence(int start, int end)
String toString()

W niektórych przypadkach klasy bibliotek klas Java, które kiedyś akceptowały String, zostały zmienione, aby teraz akceptowały interfejs CharSequence. Jeśli więc masz instancję StringBuilder, zamiast wyodrębnić obiekt String (co oznacza utworzenie instancji nowej instancji obiektu), zamiast tego możesz po prostu przekazać samą StringBuilder, ponieważ implementuje on interfejs CharSequence.

Interfejs Appendable wdrażany przez niektóre klasy ma tę samą korzyść w każdej sytuacji, w której znaki mogą być dołączane do instancji podstawowej instancji konkretnego obiektu klasy. Wszystkie te konkretne klasy implementują interfejs Appendable:

BufferedWriter, CharArrayWriter, CharBuffer, FileWriter, FilterWriter, LogStream, OutputStreamWriter, PipedWriter, PrintStream, PrintWriter, StringBuffer, StringBuilder, StringWriter, Writer


Szkoda, że ​​interfejsy CharSequencesą tak anemiczne. Chciałbym, aby Java i .NET zezwoliły interfejsom na domyślną implementację, aby ludzie nie ograniczali interfejsów wyłącznie w celu zminimalizowania kodu podstawowego. Biorąc pod uwagę każdą prawidłową CharSequenceimplementację, można emulować większość funkcji Stringprzy użyciu tylko powyższych czterech metod, ale wiele implementacji może wykonywać te funkcje znacznie wydajniej na inne sposoby. Niestety, nawet jeśli określona implementacja CharSequencezawiera wszystko w jednym char[]i może wykonać wiele ...
supercat

... operacje jak indexOfszybko, nie ma mowy, żeby osoba dzwoniąca, która nie zna konkretnej implementacji, CharSequencemogła o to poprosić, zamiast musieć charAtsprawdzać każdą indywidualną postać.
supercat

3

Krótka historia: Listonosz proszony jest o powrót do domu po domu i otrzymanie okładek zawierających (listy, dokumenty, czeki, karty podarunkowe, podanie, list miłosny) z adresem podanym na doręczeniu.

Załóżmy, że nie ma osłony i poproś listonosza, aby poszedł do domu po domu i odebrał wszystkie rzeczy i dostarczył innym ludziom, listonosz może się pomylić.

Lepiej więc zawiń go w osłonę (w naszej historii jest to interfejs), wtedy wykona swoją pracę dobrze.

Teraz zadaniem listonosza jest przyjmowanie i dostarczanie tylko okładek (nie przejmowałby się tym, co jest w okładce).

Utwórz typ interface niefaktyczny, ale zaimplementuj go z typem faktycznym.

Tworzenie do interfejsu oznacza, że ​​otrzymujesz komponenty dopasowują się do reszty kodu

Dam ci przykład.

masz interfejs AirPlane, jak poniżej.

interface Airplane{
    parkPlane();
    servicePlane();
}

Załóżmy, że masz metody w swojej klasie samolotów typu Controller

parkPlane(Airplane plane)

i

servicePlane(Airplane plane)

zaimplementowane w twoim programie. Nie spowoduje to przerwania twojego kodu. To znaczy, nie musi się zmieniać, dopóki akceptuje argumenty jakoAirPlane .

Ponieważ będzie akceptować każdego samolotu pomimo rzeczywistego typu, flyer, highflyr,fighter , itd.

Ponadto w kolekcji:

List<Airplane> plane; // Zabierze wszystkie twoje samoloty.

Poniższy przykład wyjaśni twoje zrozumienie.


Masz samolot myśliwski, który go implementuje, więc

public class Fighter implements Airplane {

    public void  parkPlane(){
        // Specific implementations for fighter plane to park
    }
    public void  servicePlane(){
        // Specific implementatoins for fighter plane to service.
    }
}

To samo dotyczy HighFlyer i innych klases:

public class HighFlyer implements Airplane {

    public void  parkPlane(){
        // Specific implementations for HighFlyer plane to park
    }

    public void  servicePlane(){
        // specific implementatoins for HighFlyer plane to service.
    }
}

Teraz pomyśl, że twoje klasy kontrolerów używają AirPlanekilka razy,

Załóżmy, że twoja klasa Controller to ControlPlane jak poniżej,

public Class ControlPlane{ 
 AirPlane plane;
 // so much method with AirPlane reference are used here...
}

Tutaj pojawia się magia, ponieważ możesz tworzyć AirPlaneinstancje nowego typu tyle, ile chcesz i nie zmieniasz kodu ControlPlaneklasy.

Możesz dodać instancję ...

JumboJetPlane // implementing AirPlane interface.
AirBus        // implementing AirPlane interface.

Możesz także usunąć instancje wcześniej utworzonych typów.


2

Interfejs jest jak kontrakt, w którym klasa implementacji ma implementować metody zapisane w kontrakcie (interfejsie). Ponieważ Java nie zapewnia wielokrotnego dziedziczenia, „programowanie do interfejsu” jest dobrym sposobem na uzyskanie wielokrotnego dziedziczenia.

Jeśli masz klasę A, która już rozszerza niektóre inne klasy B, ale chcesz, aby klasa A również postępowała zgodnie z pewnymi wytycznymi lub realizowała określoną umowę, możesz to zrobić, stosując strategię „programowania do interfejsu”.


2

P: - ... „Czy możesz użyć dowolnej klasy, która implementuje interfejs?”
Odp .: - Tak.

P: - ... „Kiedy miałbyś to zrobić?”
Odp .: - Za każdym razem, gdy potrzebujesz klasy, która implementuje interfejs (y).

Uwaga: nie mogliśmy utworzyć instancji interfejsu niezaimplementowanego przez klasę - prawda.

  • Dlaczego?
  • Ponieważ interfejs ma tylko prototypy metod, a nie definicje (tylko nazwy funkcji, a nie ich logika)

AnIntf anInst = new Aclass();
// moglibyśmy to zrobić tylko, jeśli Aclass implementuje AnIntf.
// anInst będzie miał odniesienie Aclass.


Uwaga: Teraz mogliśmy zrozumieć, co się stanie, jeśli Bclass i Cclass zaimplementują ten sam Dintf.

Dintf bInst = new Bclass();  
// now we could call all Dintf functions implemented (defined) in Bclass.

Dintf cInst = new Cclass();  
// now we could call all Dintf functions implemented (defined) in Cclass.

Co mamy: Te same prototypy interfejsów (nazwy funkcji w interfejsie) i wywołują różne implementacje.

Bibliografia: Prototypy - wikipedia


1

Program do interfejsu pozwala płynnie zmieniać implementację umowy zdefiniowanej przez interfejs. Umożliwia luźne powiązanie między kontraktem a konkretnymi implementacjami.

IInterface classRef = new ObjectWhatever()

Możesz użyć dowolnej klasy, która implementuje interfejs II? Kiedy musisz to zrobić?

Spójrz na to pytanie SE na dobry przykład.

Dlaczego interfejs klasy Java powinien być preferowany?

czy użycie interfejsu wpływa na wydajność?

jeśli tak to ile?

Tak. Będzie miał niewielki narzut wydajności w ciągu kilku sekund. Ale jeśli Twoja aplikacja wymaga dynamicznej zmiany implementacji interfejsu, nie martw się o wpływ na wydajność.

jak możesz tego uniknąć bez konieczności utrzymywania dwóch bitów kodu?

Nie próbuj unikać wielu implementacji interfejsu, jeśli Twoja aplikacja ich potrzebuje. W przypadku braku ścisłego powiązania interfejsu z jedną konkretną implementacją może być konieczne wdrożenie poprawki w celu zmiany jednej implementacji na inną implementację.

Jeden dobry przykład zastosowania: wdrożenie wzorca strategii:

Przykład wzorca strategii w świecie rzeczywistym


1

program do interfejsu to termin z książki GOF. nie powiedziałbym bezpośrednio, że ma to związek z interfejsem Java, ale raczej prawdziwymi interfejsami. aby osiągnąć czystą separację warstw, musisz na przykład utworzyć separację między systemami: Załóżmy, że masz konkretną bazę danych, której chcesz użyć, nigdy nie „programowałbyś bazy danych”, zamiast tego „programowałbyś interfejs pamięci”. Podobnie nigdy nie „programowałbyś się do usługi sieci Web”, ale programowałbyś się w „interfejsie klienta”. dzięki temu możesz łatwo wymieniać rzeczy.

uważam, że te zasady pomogą mi:

1 . używamy interfejsu Java, gdy mamy wiele typów obiektów. jeśli mam tylko jeden obiekt, nie widzę sensu. jeśli są co najmniej dwie konkretne implementacje jakiegoś pomysłu, wtedy użyłbym interfejsu Java.

2 . jeśli, jak powiedziałem powyżej, chcesz przenieść oddzielenie od systemu zewnętrznego (system pamięci masowej) do własnego systemu (lokalna baza danych), użyj również interfejsu.

zwróć uwagę, że istnieją dwa sposoby rozważenia, kiedy z nich korzystać. mam nadzieję że to pomoże.


0

Widzę też wiele dobrych i wyjaśniających odpowiedzi, więc chcę tu przedstawić swój punkt widzenia, w tym dodatkowe informacje, które zauważyłem podczas korzystania z tej metody.

Testów jednostkowych

Przez ostatnie dwa lata napisałem projekt hobby i nie napisałem dla niego testów jednostkowych. Po napisaniu około 50 000 wierszy odkryłem, że naprawdę konieczne będzie napisanie testów jednostkowych. Nie korzystałem z interfejsów (lub bardzo oszczędnie) ... i kiedy wykonałem swój pierwszy test jednostkowy, okazało się, że jest to skomplikowane. Dlaczego?

Ponieważ musiałem wykonać wiele instancji klas, używanych do wprowadzania danych jako zmiennych klas i / lub parametrów. Testy wyglądają więc bardziej jak testy integracyjne (konieczność stworzenia kompletnego „frameworka” klas, ponieważ wszystko było powiązane).

Strach przed interfejsami Więc postanowiłem użyć interfejsów. Obawiałem się, że musiałem zaimplementować całą funkcjonalność wszędzie (we wszystkich używanych klasach) wiele razy. W pewnym sensie jest to jednak prawdą, ale dzięki dziedziczeniu można je znacznie zmniejszyć.

Połączenie interfejsów i dziedziczenia Dowiedziałem się, że kombinacja jest bardzo dobra do użycia. Daję bardzo prosty przykład.

public interface IPricable
{
    int Price { get; }
}

public interface ICar : IPricable

public abstract class Article
{
    public int Price { get { return ... } }
}

public class Car : Article, ICar
{
    // Price does not need to be defined here
}

W ten sposób kopiowanie kodu nie jest konieczne, przy jednoczesnym korzystaniu z samochodu jako interfejsu (ICar).


0

Zacznijmy od kilku definicji:

Interfejs n. Zestaw wszystkich podpisów zdefiniowanych przez operacje obiektu nazywa się interfejsem do obiektu

Wpisz n. Szczególny interfejs

Prostym przykładem interfejs , jak zdefiniowano powyżej, są wszystkie sposoby obiektu PDO, takie jak query(), commit(), close()itd., Jako całości, a nie oddzielnie. Te metody, tj. Interfejs definiują pełny zestaw komunikatów, żądań, które mogą być wysyłane do obiektu.

Typu , jak określono powyżej jest szczególnie interfejsu. Użyję-up wykonany interfejs kształt wykazać: draw(), getArea(), getPerimeter()etc ..

Jeśli obiekt jest typu danych mamy na myśli, że przyjmuje wiadomości / wnioski interfejsu bazy danych query(), commit()itd .. Obiekty mogą być różnego rodzaju. Możesz mieć obiekt bazy danych typu kształtu, o ile implementuje on interfejs, w takim przypadku byłoby to podtypowanie .

Wiele obiektów może mieć wiele różnych interfejsów / typów i implementować ten interfejs w różny sposób. To pozwala nam zastępować obiekty, pozwalając nam wybrać, którego z nich użyć. Znany również jako polimorfizm.

Klient będzie wiedział tylko o interfejsie, a nie o implementacji.

Tak więc w istocie do programowania interfejsu wiązałoby co jakiś rodzaj klasy abstrakcyjnej takie jak Shapez interfejsem tylko określony tj draw(), getCoordinates(), getArea()etc .. A potem mają różne klasy konkretne wdrożenia tych interfejsów, takich jak koło, kwadrat klasy klasy, klasy Triangle. Stąd program do interfejsu, a nie implementacja.


0

„Program do interfejsu” oznacza, że ​​nie zapewniasz poprawnego kodu, co oznacza, że ​​Twój kod powinien zostać rozszerzony bez zerwania z poprzednią funkcjonalnością. Tylko rozszerzenia, a nie edycja poprzedniego kodu.

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.