Interfejs czy klasa abstrakcyjna: którego użyć?

327

Proszę wyjaśnić, kiedy powinienem używać PHP, interfacea kiedy powinienem abstract class?

Jak mogę zmienić swoje abstract classkonto na interface?

Darryl Hein
źródło

Odpowiedzi:

458

Użyj interfejsu, gdy chcesz zmusić programistów pracujących w twoim systemie (włączając ciebie) do wdrożenia określonej liczby metod w klasach, które będą budować.

Użyj klasy abstrakcyjnej, jeśli chcesz zmusić programistów pracujących w twoim systemie (włączając ciebie) do zaimplementowania określonej liczby metod i chcesz podać podstawowe metody, które pomogą im rozwinąć klasy potomne.

Inną rzeczą, o której należy pamiętać, jest to, że klasy klientów mogą rozszerzać tylko jedną klasę abstrakcyjną, podczas gdy mogą implementować wiele interfejsów. Jeśli więc definiujesz swoje umowy dotyczące zachowania w klasach abstrakcyjnych, oznacza to, że każda klasa potomna może być zgodna tylko z jedną umową. Czasami jest to dobra rzecz, gdy chcesz zmusić programistów do korzystania z określonej ścieżki. Innym razem byłoby źle. Wyobraź sobie, że interfejsy PHP Countable i Iterator były klasami abstrakcyjnymi zamiast interfejsów.

Jednym z powszechnych podejść, gdy nie masz pewności, którą drogą wybrać (jak wspomniano poniżej, jest utworzenie interfejsu, a następnie zaimplementowanie tego interfejsu przez klasę abstrakcyjną.

Alan Storm
źródło
12
Przez cały dzień starałem się zrozumieć zwyczaje abstracti interfacezajęcia, twój post wyjaśnił wszystko.
Wielkie
4
Kolejną zaletą klas abstrakcyjnych jest możliwość definiowania abstrakcyjnych metod chronionych . Nie zawsze przydatne, ale mogą się przydać w niektórych architekturach.
netcoder
Dlatego w wielu przypadkach powinniśmy używać klasy abstrakcyjnej ze względu na elastyczność - taki jest mój wniosek :)
ymakux
3
@vococuga: niekoniecznie, jak zauważył Alan, tylko jedno streszczenie może zostać rozszerzone. Ja osobiście nie podoba mi się, że streszczenie implementuje pomysł interfejsu, ponieważ przyczynia się do zaciemnienia kodu i jest mniej bezpośredni, IMO.
Prefiks
171

Różnice między an Abstract Classa Interface:

Klasy abstrakcyjne

Klasa abstrakcyjna może zapewnić pewną funkcjonalność, a resztę pozostawić dla klasy pochodnej .

  • Klasa pochodna może, ale nie musi, zastępować konkretne funkcje zdefiniowane w klasie bazowej.

  • Klasa potomna wywodząca się z klasy abstrakcyjnej powinna być logicznie powiązana.

Berło

Interfejs nie może zawierać żadnej funkcjonalności . Zawiera tylko definicje metod.

  • Klasa pochodna MUSI zapewnić kod dla wszystkich metod zdefiniowanych w interfejsie .

  • Całkowicie różne i niepowiązane klasy można logicznie pogrupować za pomocą interfejsu.

kn3l
źródło
1
Czy możesz podać prawdziwy przykład z życia, aby to zademonstrować?
RN Kushwaha
1
Jaka jest różnica między abstract class X implements Yi class X implements Y?
Webinan,
3
@Webinan W abstract class X implements Yoświadczasz, że zbiorcza funkcjonalność X powinna być zaimplementowana w klasie pochodnej oraz że zarówno klasa abstrakcyjna, jak i pochodna muszą zawierać funkcje zdefiniowane w Y, podczas gdy class X implements Yimplikuje to tylko, że klasa X musi zawierać funkcje zdefiniowane w Y. Jeśli twój interfejs Y nie jest przeznaczony do implementacji przez żadną inną klasę niż XI, tak naprawdę pominąłby definiowanie Y jako interfejs i implementowałby funkcje w Y jako publiczną / chronioną / prywatną funkcję abstrakcyjną, aby upewnić się, że są one zaimplementowane w klasie pochodnej.
Calle Bergström
1
Interfejsy mogą nie tylko zawierać definicję metod, ale mogą także zawierać stałe
Thielicious,
Podobało mi się twoje porównanie. Więc chciałem coś dodać. Interfejsy mogą mieć stałe klasy po wyjęciu z pudełka, podczas gdy klasa abstrakcyjna nie.
Noman Ibrahim,
128

Dlaczego warto korzystać z klas abstrakcyjnych? Oto prosty przykład. Powiedzmy, że mamy następujący kod:

<?php 

class Fruit {
    private $color;

    public function eat() {
        // chew
    }

    public function setColor($c) {
        $this->color = $c;
    }
}

class Apple extends Fruit {
    public function eat() {
        // chew until core
    }
}

class Orange extends Fruit {
    public function eat() {
        // peeling
        // chew
    }
}

Teraz daję ci jabłko, a ty jesz. Jak to smakuje? Smakuje jak jabłko.

<?php 
$apple = new Apple();
$apple->eat();

// Now I give you a fruit.
$fruit = new Fruit();
$fruit->eat();

Jak to smakuje? Cóż, to nie ma większego sensu, więc nie powinieneś być w stanie tego zrobić. Uzyskuje się to poprzez streszczenie klasy Fruit oraz metodę jedzenia wewnątrz niej.

<?php 
abstract class Fruit {
    private $color;

    abstract public function eat(){}

    public function setColor($c) {
        $this->color = $c;
    }
}
?>

Klasa abstrakcyjna jest jak interfejs, ale można zdefiniować metody w klasie abstrakcyjnej, podczas gdy w interfejsie wszystkie są abstrakcyjne. Klasy abstrakcyjne mogą mieć zarówno metody puste, jak i pracujące / konkretne. W interfejsach funkcje tam zdefiniowane nie mogą mieć ciała. W klasach abstrakcyjnych potrafią.

Przykład ze świata rzeczywistego:

<?php 
abstract class person {

    public $LastName;
    public $FirstName;
    public $BirthDate;

    abstract protected function write_info();
}

final class employee extends person{

    public $EmployeeNumber;
    public $DateHired;

    public function write_info(){
        //sql codes here
        echo "Writing ". $this->LastName . "'s info to emloyee dbase table <br>";   
    }
}

final class student extends person{

    public $StudentNumber;
    public $CourseName;

    public function write_info(){
        //sql codes here
        echo "Writing ". $this->LastName . "'s info to student dbase table <br>";
    }
}

///----------
$personA = new employee;
$personB = new student;

$personA->FirstName="Joe";
$personA->LastName="Sbody";

$personB->FirstName="Ben";
$personB->LastName="Dover";

$personA->write_info();
// Writing Sbody's info to emloyee dbase table
$personB->write_info();
// Writing Dover's info to student dbase table 
Vineesh Kalarickal
źródło
2
Cześć, ta odpowiedź prawdopodobnie spotkała się z negatywnym przyjęciem ze względu na sposób jej sformatowania. Byłoby dobrze, gdyby nie był to duży blok kodu (cztery spacje tworzą coś w blok kodu, cofnij tekst, aby go wyjąć z bloku), a jeśli byłby gdzieś skopiowany (wygląda na to) uznanie ich byłoby grzeczne.
Camilo Martin
9
Kocham cię za przykład owocu, człowieku! Odkąd zacząłem uczyć się php, to przykłady wyjaśniają bardzo dzięki
Raheel
23
+1 What does that taste like? Well, it doesn't make much sense, so you shouldn't be able to do that.Teraz znam streszczenie!
Webinan,
Co robi finalsłowo kluczowe? Świetny post, dzięki.
Gus,
1
@VineeshKalarickal To, czego nie rozumiem w przykładzie Person, to różnica między: 1) użyciem abstrakcyjnej klasy Person (jak w przykładzie); 2) pisanie Osoby jako klasy standardowej i zmuszanie Pracownika i Ucznia do zastąpienia metody write_info ().
Ferex,
66

Najlepszą praktyką jest użycie interfejsu do określenia kontraktu i klasy abstrakcyjnej jako tylko jednej jego realizacji. Ta klasa abstrakcyjna może wypełnić wiele schematu, dzięki czemu możesz utworzyć implementację, po prostu nadpisując to, czego potrzebujesz lub czego chcesz, bez zmuszania Cię do użycia konkretnej implementacji.

Cletus
źródło
37

Żeby wrzucić to do miksu, ale jak Cletus wspomniał o użyciu interfejsu w połączeniu z klasą abstrakcyjną, często używam interfejsu do wyjaśnienia mojego myślenia projektowego.

Na przykład:

<?php
class parser implements parserDecoratorPattern {
    //...
}

W ten sposób każdy, kto czyta mój kod (i kto wie, co to jest wzorzec dekoratora), będzie od razu wiedział a) w jaki sposób buduję mój parser oraz b) będzie mógł zobaczyć, jakie metody są stosowane do implementacji wzorca dekoratora.

Poza tym mogę być tutaj nie będący programistą Java / C ++ / etc, ale typy danych mogą się tutaj bawić. Twoje obiekty są pewnego rodzaju, a kiedy je rozprowadzasz, ma to znaczenie programowe. Przeniesienie elementów objętych kontraktem do interfejsu dyktuje tylko typy zwracane przez metody, ale nie typ podstawowy klasy, która je implementuje.

Jest późno i nie mogę wymyślić lepszego przykładu kodu psudo, ale oto:

<?php
interface TelevisionControls {};
class Remote implements TelevisionControls {};
class Spouse implements TelevisionControls {};
Spouse spouse = new Spouse();
Remote remote = new Remote();
isSameType = (bool)(remote == spouse)
Austen Hoogen
źródło
1
Cóż za wspaniały przykład! ;)
Joel Murphy
@ matt2000 Nie seksistowski ORAZ teraz działa również w przypadku małżeństw osób tej samej płci. Świetna edycja. :)
Austen Hoogen,
4
To niesamowity przykład! Jednak nie sądzę, że można utworzyć instancję klasy abstrakcyjnej, ale raczej klasę, która
Arielle Nguyen
2
nie zabiłby, żeby sudo-kod przynajmniej wyglądał jak dany język. ktoś powinien tam włożyć trochę dolarów.
Raz walczyłem z niedźwiedziem.
2
Chociaż ten przykład jest zabawny, nie możemy bezpośrednio utworzyć instancji klasy abstrakcyjnej, dokonaj zmian w tym przykładzie, aby ludzie nie uznali jej za prawidłowe użycie w PHP.
saji89,
16

Główną różnicą jest to, że klasa abstrakcyjna może zawierać domyślną implementację, podczas gdy interfejs nie.

Interfejs to umowa o zachowaniu bez jakiegokolwiek wdrożenia.

Mitch Pszenica
źródło
16

Chciałbym również dodać tutaj, że tylko dlatego, że jakikolwiek inny język OO ma jakieś interfejsy i abstrakcja, nie oznacza, że ​​mają takie samo znaczenie i cel jak w PHP. Używanie abstrakcji / interfejsów jest nieco inne, podczas gdy interfejsy w PHP nie mają w rzeczywistości żadnej funkcji. Są one używane jedynie ze względów semantycznych i związanych ze schematem. Chodzi o to, aby projekt był jak najbardziej elastyczny, możliwy do rozszerzenia i bezpieczny dla przyszłych rozszerzeń, niezależnie od tego, czy programista ma później zupełnie inny plan użytkowania, czy nie.

Jeśli Twój angielski nie jest językiem ojczystym, możesz sprawdzić, czym tak naprawdę jest abstrakcja i interfejsy. I szukaj też synonimów.

A to może ci pomóc jako metaforę:

BERŁO

Powiedzmy, że piec nowy rodzaj ciasta z truskawkami i wymyśliłeś przepis opisujący składniki i etapy. Tylko Ty wiesz, dlaczego smakuje tak dobrze, a Twoi goście to lubią. Następnie decydujesz się opublikować swój przepis, aby inne osoby również mogły spróbować tego ciasta.

Chodzi o to, że

- zrobić to dobrze
- zachować ostrożność
- zapobiegać sytuacjom, które mogłyby się popsuć (np. zbyt dużo truskawek lub czegoś takiego)
- ułatwić ludziom, którzy to wypróbowali
- powiedzieć ci, jak długo trzeba robić (np. mieszanie) )
- aby powiedzieć, które rzeczy MOŻESZ zrobić, ale NIE MUSISZ

Właśnie to opisuje interfejsy. Jest to przewodnik, zestaw instrukcji, które przestrzegają treści przepisu. Tak samo, jakbyś tworzył projekt w PHP i chcesz podać kod na GitHub lub ze znajomymi lub czymkolwiek innym. Interfejs to, co ludzie mogą robić, a czego nie. Reguły, które ją trzymają - jeśli nie zastosujesz się do niej, cała konstrukcja zostanie zepsuta.


ABSTRAKCJA

Aby kontynuować tę metaforę tutaj ... wyobraź sobie, że tym razem jesteś gościem jedzącym to ciasto. Następnie próbujesz teraz tego ciasta, korzystając z przepisu. Ale chcesz dodać nowe składniki lub zmienić / pominąć kroki opisane w przepisie. Co dalej? Zaplanuj inną wersję tego ciasta. Tym razem z czarnymi jagodami, a nie słomkowymi i kremem waniliowym ... pyszne.

To właśnie można rozważyć rozszerzenie oryginalnego ciasta. Zasadniczo wykonujesz jej abstrakcję, tworząc nowy przepis, ponieważ jest on inny. Ma kilka nowych kroków i inne składniki. Jednak wersja z czarnymi jagodami ma pewne części, które przejęłaś od oryginału - są to podstawowe kroki, które musi mieć każdy rodzaj tego ciasta. Podobnie jak składniki mleka - właśnie to ma każda klasa pochodna.

Teraz chcesz wymienić składniki i kroki, MUSZĄ one zostać zdefiniowane w nowej wersji tego ciasta. Są to abstrakcyjne metody, które należy zdefiniować dla nowego ciasta, ponieważ powinien on zawierać owoce, ale które? Tym razem bierzesz czarne jagody. Gotowy.

Proszę, rozszerzyłeś ciasto, podążyłeś za interfejsem i wyodrębniłeś z niego kroki i składniki.

Thielicious
źródło
1
To było moje ulubione porównanie wszystkiego, co dotyczy PHP. To naprawdę miało sens. Dziękuję Ci!
cbloss793
13

Aby dodać do niektórych i tak doskonałych odpowiedzi:

  • Klasy abstrakcyjne pozwalają na pewien stopień implementacji, interfejsy są czystymi szablonami. Interfejs może jedynie definiować funkcjonalność , nigdy nie może go wdrożyć.

  • Każda klasa, która implementuje interfejs, zobowiązuje się do wdrożenia wszystkich zdefiniowanych przez siebie metod, w przeciwnym razie musi zostać zadeklarowana jako abstrakcyjna.

  • Interfejsy mogą pomóc w zarządzaniu faktem, że podobnie jak Java, PHP nie obsługuje wielokrotnego dziedziczenia. Klasa PHP może rozszerzyć tylko jednego rodzica. Możesz jednak złożyć obietnicę klasy zaimplementowania dowolnej liczby interfejsów.

  • type: dla każdego implementowanego interfejsu klasa przyjmuje odpowiedni typ. Ponieważ każda klasa może implementować interfejs (lub więcej interfejsów), interfejsy skutecznie łączą typy, które w innym przypadku nie są ze sobą powiązane.

  • klasa może zarówno rozszerzać nadklasę, jak i implementować dowolną liczbę interfejsów:

    class SubClass extends ParentClass implements Interface1, Interface2 {
        // ...
    }

Proszę wyjaśnić, kiedy powinienem użyć interfejsu, a kiedy powinienem użyć klasy abstrakcyjnej?

Użyj interfejsu, jeśli potrzebujesz tylko szablonu bez implementacji, a chcesz mieć pewność, że każda klasa, która implementuje ten interfejs, będzie miała takie same metody jak każda inna klasa, która go implementuje (przynajmniej).

Użyj klasy abstrakcyjnej, jeśli chcesz utworzyć podstawę dla innych obiektów (klasa częściowo zbudowana). Klasa rozszerzająca klasę abstrakcyjną będzie używać niektórych właściwości lub metod zdefiniowanych / zaimplementowanych:

<?php
// interface
class X implements Y { } // this is saying that "X" agrees to speak language "Y" with your code.

// abstract class
class X extends Y { } // this is saying that "X" is going to complete the partial class "Y".
?>

Jak mogę zmienić swoją klasę abstrakcyjną na interfejs?

Oto uproszczony przypadek / przykład. Wyjmij wszystkie szczegóły implementacji. Na przykład zmień klasę abstrakcyjną z:

abstract class ClassToBuildUpon {
    public function doSomething() {
          echo 'Did something.';
    }
}

do:

interface ClassToBuildUpon {
    public function doSomething();
}
bg17aw
źródło
12

Z filozoficznego punktu widzenia:

  • Klasa abstrakcyjna reprezentuje relację „to”. Powiedzmy, że mam owoce, no cóż, miałbym klasę abstrakcyjną Fruit, która dzieli wspólną odpowiedzialność i wspólne zachowanie.

  • Interfejs reprezentuje relację „należy zrobić”. Interfejs, moim zdaniem (który jest zdaniem młodszego programisty), powinien być nazwany przez akcję lub coś zbliżonego do akcji (Przepraszam, nie mogę znaleźć słowa, nie jestem rodzimym językiem angielskim) powiedzmy IEatable. Wiesz, że można go zjeść, ale nie wiesz, co jesz.

Z punktu widzenia kodowania:

  • Jeśli twoje obiekty mają zduplikowany kod, oznacza to, że mają one wspólne zachowanie, co oznacza, że ​​możesz potrzebować abstrakcyjnej klasy do ponownego użycia kodu, czego nie można zrobić z interfejsem.

  • Kolejna różnica polega na tym, że obiekt może implementować tyle interfejsów, ile potrzebujesz, ale możesz mieć tylko jedną klasę abstrakcyjną z powodu „problemu z diamentem” (sprawdź tutaj, aby dowiedzieć się, dlaczego! Http://en.wikipedia.org/wiki/ Multiple_inheritance # The_diamond_problem )

Prawdopodobnie zapominam o niektórych punktach, ale mam nadzieję, że to wyjaśni.

PS: Odpowiedź „należy” / „powinna zrobić” pochodzi od odpowiedzi Viveka Vermaniego. Nie chciałem kraść jego odpowiedzi, tylko po to, aby ponownie użyć terminów, ponieważ je lubiłem!

IEatBagels
źródło
2
Wierzę, że słowo, którego szukasz, jest jadalne.
Travis Weston
1
Właściwie uważam, że to „czasownik”, „słowo
czynu
7

Różnice techniczne między klasą abstrakcyjną a interfejsem są już dokładnie wymienione w innych odpowiedziach. Chcę dodać wyjaśnienie wyboru między klasą a interfejsem podczas pisania kodu ze względu na programowanie obiektowe.

Klasa powinna reprezentować byt, podczas gdy interfejs powinien reprezentować zachowanie.

Weźmy przykład. Monitor komputerowy jest bytem i powinien być reprezentowany jako klasa.

class Monitor{
    private int monitorNo;
}

Został zaprojektowany w celu zapewnienia interfejsu wyświetlacza, więc funkcjonalność powinna być zdefiniowana przez interfejs.

interface Display{
    void display();
}

Jest wiele innych rzeczy do rozważenia, jak wyjaśniono w innych odpowiedziach, ale jest to najbardziej podstawowa rzecz, którą większość ludzi ignoruje podczas kodowania.

Anurag Sharma
źródło
2
PHP nie definiuje typów zwrotów, a OP otagował to pytaniePHP
Purefan,
1

Chciałem tylko dodać przykład, kiedy możesz potrzebować obu. Obecnie piszę procedurę obsługi plików powiązaną z modelem bazy danych w rozwiązaniu ERP ogólnego zastosowania.

  • Mam wiele klas abstrakcyjnych, które obsługują standardowe operacje crud, a także niektóre specjalne funkcje, takie jak konwersja i streaming dla różnych kategorii plików.
  • Interfejs dostępu do plików określa wspólny zestaw metod potrzebnych do pobrania, przechowywania i usunięcia pliku.

W ten sposób otrzymuję wiele szablonów dla różnych plików i wspólny zestaw metod interfejsu z wyraźnym rozróżnieniem. Interfejs podaje poprawną analogię do metod dostępu zamiast tych, które byłyby w przypadku podstawowej klasy abstrakcyjnej.

W dalszej kolejności, gdy będę tworzyć adaptery dla różnych usług przechowywania plików, ta implementacja pozwoli na użycie interfejsu gdzie indziej w zupełnie innych kontekstach.

Umair Ahmed
źródło