Ostatnio próbuję uczyć się na PHP i odkrywałem, że jestem uzależniony od cech. Rozumiem koncepcję ponownego wykorzystania kodu horyzontalnego i nie chcę dziedziczyć po klasie abstrakcyjnej. Nie rozumiem tylko: jaka jest zasadnicza różnica między używaniem cech a interfejsami?
Próbowałem znaleźć porządny post na blogu lub artykuł wyjaśniający, kiedy użyć jednego lub drugiego, ale przykłady, które do tej pory znalazłem, wydają się tak podobne, że są identyczne.
Imagick
przedmioty, bez zbędnego wzdęcia potrzebnego w dawnych czasach przed cechami.Odpowiedzi:
Interfejs definiuje zestaw metod, które klasa implementująca musi wdrożyć.
Kiedy pojawia się cecha,
use
pojawiają się również implementacje metod - co nie zdarza się wInterface
.To jest największa różnica.
Od poziomego ponownego wykorzystania dla PHP RFC :
źródło
Ogłoszenie publiczne:
Pragnę oświadczyć, że moim zdaniem cechy są prawie zawsze zapachem kodu i należy ich unikać na korzyść kompozycji. Moim zdaniem pojedyncze dziedzictwo jest często nadużywane do tego stopnia, że jest anty-wzorcem, a wielokrotne dziedziczenie tylko pogarsza ten problem. W większości przypadków będziesz lepiej obsługiwany, faworyzując kompozycję zamiast dziedziczenia (zarówno pojedynczego, jak i wielokrotnego). Jeśli nadal interesują Cię cechy i ich związek z interfejsami, czytaj dalej ...
Zacznijmy od tego, że:
Aby napisać kod OO, musisz zrozumieć, że OOP naprawdę dotyczy możliwości twoich obiektów. Musisz pomyśleć o zajęciach w kategoriach tego, co mogą zrobić zamiast tego, co faktycznie robią . Jest to wyraźny kontrast z tradycyjnym programowaniem proceduralnym, w którym nacisk kładziony jest na zrobienie odrobiny kodu „zrób coś”.
Jeśli kod OOP dotyczy planowania i projektowania, interfejs jest schematem, a obiekt to w pełni zbudowany dom. Tymczasem cechy są po prostu sposobem na pomoc w budowie domu określonego w planie (interfejsie).
Interfejsy
Dlaczego więc powinniśmy używać interfejsów? Po prostu interfejsy sprawiają, że nasz kod jest mniej kruchy. Jeśli wątpisz w to stwierdzenie, poproś każdego, kto został zmuszony do zachowania starszego kodu, który nie został napisany przeciwko interfejsom.
Interfejs jest umową między programistą a jego kodem. Interfejs mówi: „Tak długo, jak będziesz postępować według moich zasad, możesz mnie zaimplementować w dowolny sposób i obiecuję, że nie złamię twojego innego kodu”.
Jako przykład weźmy pod uwagę rzeczywisty scenariusz (bez samochodów i widżetów):
Zaczynasz od napisania klasy do buforowania odpowiedzi na żądania przy użyciu APC:
Następnie w obiekcie odpowiedzi HTTP sprawdzasz trafienie w pamięci podręcznej przed wykonaniem całej pracy w celu wygenerowania rzeczywistej odpowiedzi:
To podejście działa świetnie. Ale może kilka tygodni później zdecydujesz, że chcesz użyć systemu pamięci podręcznej opartego na plikach zamiast APC. Teraz musisz zmienić kod kontrolera, ponieważ zaprogramowałeś kontroler do pracy z funkcjonalnością
ApcCacher
klasy, a nie z interfejsem, który wyraża możliwościApcCacher
klasy. Powiedzmy, że zamiast powyższego sprawiłeś, żeController
klasa polegała na konkretnymCacherInterface
zamiast na konkretnymApcCacher
:Aby to zrobić, zdefiniuj swój interfejs w następujący sposób:
Z kolei masz zarówno swoje, jak
ApcCacher
i noweFileCacher
klasy,CacherInterface
i programujesz swojąController
klasę, aby korzystała z możliwości wymaganych przez interfejs.Ten przykład (mam nadzieję) pokazuje, w jaki sposób programowanie interfejsu pozwala zmienić wewnętrzną implementację twoich klas bez obawy, że zmiany spowodują uszkodzenie innego kodu.
Cechy
Natomiast cechy to po prostu metoda ponownego użycia kodu. Interfejsy nie powinny być traktowane jako wzajemnie wykluczająca się alternatywa dla cech. W rzeczywistości tworzenie cech, które spełniają funkcje wymagane przez interfejs, jest idealnym przykładem użycia .
Tych cech należy używać tylko wtedy, gdy wiele klas ma tę samą funkcjonalność (prawdopodobnie podyktowane tym samym interfejsem). Nie ma sensu używać cechy do zapewnienia funkcjonalności dla jednej klasy: to tylko zaciemnia to, co robi klasa, a lepszy projekt przeniósłby funkcjonalność cechy do odpowiedniej klasy.
Rozważ następującą implementację cechy:
Bardziej konkretny przykład: wyobraź sobie oba
FileCacher
i swojeApcCacher
z dyskusji interfejsu używamy tej samej metody, aby ustalić, czy pozycja pamięci podręcznej jest nieaktualna i powinna zostać usunięta (oczywiście tak nie jest w rzeczywistości, ale idź z nią). Możesz napisać cechę i pozwolić obu klasom na użycie jej do wspólnego wymagania interfejsu.Ostatnie ostrzeżenie: uważaj, aby nie przesadzić z cechami. Często cechy są wykorzystywane jako narzędzie do kiepskiego projektowania, gdy wystarczą wyjątkowe implementacje klasy. Powinieneś ograniczyć cechy do spełnienia wymagań interfejsu dla najlepszego projektu kodu.
źródło
A
trait
jest w zasadzie implementacją PHPmixin
i jest zestawem metod rozszerzenia, które można dodać do dowolnej klasy poprzez dodanietrait
. Metody stają się następnie częścią implementacji tej klasy, ale bez użycia dziedziczenia .Z podręcznika PHP (moje podkreślenie):
Przykład:
Po zdefiniowaniu powyższej cechy mogę teraz wykonać następujące czynności:
W tym momencie, kiedy tworzę instancję klasy
MyClass
, ma ona dwie metody, wywoływanefoo()
ibar()
- które pochodząmyTrait
. I - zauważ, żetrait
metody -defined mają już treść metody - czegoInterface
metoda -defined nie może.Dodatkowo - PHP, podobnie jak wiele innych języków, korzysta z jednego modelu dziedziczenia - co oznacza, że klasa może pochodzić z wielu interfejsów, ale nie z wielu klas. Jednak klasa PHP może mieć wiele
trait
inkluzji - co pozwala programiście uwzględniać elementy wielokrotnego użytku - podobnie jak w przypadku wielu klas podstawowych.Kilka rzeczy do zapamiętania:
Wielopostaciowość:
We wcześniejszym przykładzie, gdzie
MyClass
rozciąga sięSomeBaseClass
,MyClass
jest instancjaSomeBaseClass
. Innymi słowy, tablica, któraSomeBaseClass[] bases
może zawierać instancjeMyClass
. Podobnie, jeśliMyClass
rozszerzonyIBaseInterface
, tablicaIBaseInterface[] bases
może zawierać instancjeMyClass
. Nie ma takiej konstrukcji polimorficznej dostępnej ztrait
- ponieważ atrait
jest zasadniczo tylko kodem, który jest kopiowany dla wygody programisty do każdej klasy, która go używa.Precedens:
Jak opisano w instrukcji:
Więc - rozważ następujący scenariusz:
Podczas tworzenia wystąpienia MyClass powyżej występują następujące zdarzenia:
Interface
IBase
Wymaga funkcji bez parametrów nazwieSomeMethod()
mają zostać przekazane.BaseClass
zapewnia implementację tej metody - zaspokajając potrzebę.trait
myTrait
Oferuje funkcję o nazwie bez parametrówSomeMethod()
, jak również, który ma pierwszeństwo nadBaseClass
-versionclass
MyClass
Zapewnia własną wersjęSomeMethod()
- który ma pierwszeństwo nadtrait
-version.Wniosek
Interface
nie może zapewnić domyślnej implementacji treści metody, natomiasttrait
puszka.Interface
jest polimorficznym , odziedziczonym konstruktem - podczas gdy atrait
nie.Interface
s może być używanych w tej samej klasie, podobnie jak wieletrait
s.źródło
mixin
- i kiedy ponownie wróciłem do otwarcia mojej odpowiedzi, zaktualizowałem ją, aby to odzwierciedlić. Dzięki za komentowanie, @BoltClock!Myślę
traits
przydatne są tworzenie klas zawierających metody, które można wykorzystać jako metody kilku różnych klas.Na przykład:
Możesz mieć i używać tej metody „błędu” w dowolnej klasie, która korzysta z tej cechy.
Podczas gdy
interfaces
możesz deklarować tylko podpis metody, ale nie kod jej funkcji. Ponadto, aby użyć interfejsu, musisz przestrzegać hierarchii, używającimplements
. Nie dotyczy to cech.Jest zupełnie inaczej!
źródło
to_integer
prawdopodobnie byłby zawarty wIntegerCast
interfejsie, ponieważ nie ma zasadniczo podobnego sposobu (inteligentnego) rzutowania klas na liczbę całkowitą.use Toolkit
ciebie mógłbyś mieć,$this->toolkit = new Toolkit();
czy też brakuje mi korzyści z samej cechy?Something
pojemniku robiszif(!$something->do_something('foo')) var_dump($something->errors);
Dla początkujących powyżej odpowiedź może być trudna. Jest to najłatwiejszy sposób na jej zrozumienie:
Cechy
więc jeśli chcesz mieć
sayHello
funkcję w innych klasach bez ponownego tworzenia całej funkcji, możesz użyć cech,Fajnie, prawda!
Nie tylko funkcje można wykorzystać dowolną cechę (funkcja, zmienne, const ...). możesz także użyć wielu cech:
use SayWorld,AnotherTraits;
Berło
tak więc interfejs różni się od cech: Musisz odtworzyć wszystko w interfejsie w zaimplementowanej klasie. interfejs nie ma implementacji. a interfejs może mieć tylko funkcje i stałą, nie może mieć zmiennych.
Mam nadzieję, że to pomoże!
źródło
Jest to dobry sposób myślenia o tym w większości przypadków, ale istnieje między nimi kilka subtelnych różnic.
Na początek
instanceof
operator nie będzie pracował z cechami (tj. Cecha nie jest prawdziwym przedmiotem), więc nie możesz nam tego zrobić, aby sprawdzić, czy klasa ma określoną cechę (lub sprawdzić, czy dwie inne niezwiązane klasy mają wspólną cechę ). Rozumie się przez to, że jest to konstrukcja do ponownego wykorzystania kodu poziomego.W PHP są teraz funkcje, które pozwolą ci uzyskać listę wszystkich cech używanych przez klasę, ale dziedziczenie cech oznacza, że będziesz musiał wykonywać rekurencyjne kontrole, aby niezawodnie sprawdzić, czy klasa w którymś momencie ma określoną cechę (istnieje przykład kod na stronach doco PHP). Ale tak, z pewnością nie jest tak prosty i czysty jak instanceof, a IMHO to funkcja, która poprawiłaby PHP.
Ponadto klasy abstrakcyjne są nadal klasami, więc nie rozwiązują problemów związanych z ponownym użyciem kodu związanych z wielokrotnym dziedziczeniem. Pamiętaj, że możesz rozszerzyć tylko jedną klasę (rzeczywistą lub abstrakcyjną), ale zaimplementować wiele interfejsów.
Przekonałem się, że cechy i interfejsy są naprawdę dobre do użycia razem w celu stworzenia pseudo wielokrotnego dziedziczenia. Na przykład:
Oznacza to, że możesz użyć instanceof, aby ustalić, czy dany obiekt Door jest Keyed, czy nie, wiesz, że otrzymasz spójny zestaw metod itp., A cały kod znajduje się w jednym miejscu we wszystkich klasach, które używają KeyedTrait.
źródło
Cechy są po prostu do ponownego użycia kodu .
Interfejs po prostu zapewnia podpis funkcji, które mają być zdefiniowane w klasie, gdzie można go używać w zależności od uznania programisty . Daje nam to prototyp dla grupy klas .
W celach informacyjnych - http://www.php.net/manual/en/language.oop5.traits.php
źródło
Zasadniczo można uznać tę cechę za zautomatyzowane „kopiowanie-wklejanie” kodu.
Używanie cech jest niebezpieczne, ponieważ nie ma sposobu, aby wiedzieć, co robi przed wykonaniem.
Jednak cechy są bardziej elastyczne ze względu na brak ograniczeń, takich jak dziedziczenie.
Cechy mogą być przydatne do wstrzyknięcia metody, która sprawdza coś w klasie, na przykład istnienie innej metody lub atrybutu. Niezły artykuł na ten temat (ale przepraszam po francusku) .
Dla osób czytających język francuski, które mogą je zdobyć, w GNU / Linux Magazine HS 54 znajduje się artykuł na ten temat.
źródło
Jeśli znasz angielski i wiesz, co to
trait
znaczy, dokładnie tak mówi nazwa. Jest to bezklasowy zestaw metod i właściwości, które dołączasz do istniejących klas, wpisującuse
.Zasadniczo można go porównać do pojedynczej zmiennej. Funkcje zamknięcia mogą
use
te zmienne spoza zakresu i w ten sposób mają wartość wewnątrz. Są potężne i można je stosować we wszystkim. To samo dzieje się z cechami, jeśli są one używane.źródło
Inne odpowiedzi świetnie spisały się wyjaśniając różnice między interfejsami i cechami. Skoncentruję się na przydatnym przykładzie ze świata rzeczywistego, w szczególności na tym, który pokazuje, że cechy mogą wykorzystywać zmienne instancji - pozwalając na dodanie zachowania do klasy przy minimalnym kodzie wzorcowym.
Ponownie, jak wspomniano przez innych, cechy dobrze łączą się z interfejsami, umożliwiając interfejsowi określenie kontraktu behawioralnego i cechę spełniającą implementację.
Dodanie możliwości publikowania / subskrybowania zdarzeń do klasy może być częstym scenariuszem w niektórych bazach kodu. Istnieją 3 popularne rozwiązania:
use
tę cechę, czyli ją zaimportować, w celu uzyskania możliwości.Jak dobrze działa każdy?
# 1 Nie działa dobrze. Tak będzie, dopóki nie zdasz sobie sprawy, że nie możesz rozszerzyć klasy podstawowej, ponieważ już rozszerzasz coś innego. Nie będę tego pokazywał, ponieważ powinno być oczywiste, jak ograniczające jest używanie takiego dziedziczenia.
# 2 i # 3 oba działają dobrze. Pokażę przykład, który podkreśla pewne różnice.
Po pierwsze, kod, który będzie taki sam między oboma przykładami:
Interfejs
I trochę kodu do zademonstrowania użycia:
Ok, teraz pokażmy, jak
Auction
różni się implementacja klasy przy użyciu cech.Po pierwsze, oto jak wyglądałby numer 2 (przy użyciu kompozycji):
Oto jak wyglądałoby # 3 (cechy):
Zauważ, że kod wewnątrz
EventEmitterTrait
jest dokładnie taki sam, jak wewnątrzEventEmitter
klasy, z wyjątkiem tego, że cecha deklarujetriggerEvent()
metodę jako chronioną. Tak więc jedyną różnicą, na którą musisz spojrzeć, jest implementacjaAuction
klasy .Różnica jest duża. Korzystając z kompozycji, otrzymujemy świetne rozwiązanie, które pozwala nam ponownie wykorzystać naszą
EventEmitter
liczbę zajęć. Ale główną wadą jest to, że mamy dużo kodu typu „płyta podstawowa”, który musimy napisać i zachować, ponieważ dla każdej metody zdefiniowanej wObservable
interfejsie musimy go zaimplementować i napisać nudny kod typu „płyta podstawowa”, który po prostu przekazuje argumenty na odpowiednią metodę w nasz komponowałEventEmitter
obiekt. Użycie cechy z tego przykładu pozwala nam tego uniknąć , pomagając nam zredukować ogólny kod i poprawić łatwość konserwacji .Może się jednak zdarzyć, że nie będziesz chciał, aby twoja
Auction
klasa zaimplementowała pełnyObservable
interfejs - być może chcesz tylko ujawnić 1 lub 2 metody, a może nawet żadną, abyś mógł zdefiniować własne podpisy metod. W takim przypadku nadal możesz preferować metodę kompozycji.Ale ta cecha jest bardzo atrakcyjna w większości scenariuszy, szczególnie jeśli interfejs ma wiele metod, co powoduje, że piszesz dużo schematu.
* Możesz właściwie zrobić jedno i drugie - zdefiniować
EventEmitter
klasę na wypadek, gdybyś chciał użyć jej kompozycyjnie, a także zdefiniowaćEventEmitterTrait
cechę, używającEventEmitter
implementacji klasy wewnątrz cechy :)źródło
Ta cecha jest taka sama jak klasa, której możemy używać do celów wielokrotnego dziedziczenia, a także do ponownego użycia kodu.
Możemy użyć cechy w klasie, a także możemy użyć wielu cech w tej samej klasie za pomocą słowa „użyj słowa kluczowego”.
Interfejs używa tego samego kodu do wielokrotnego użytku kodu
interfejs rozszerza wiele interfejsów, dzięki czemu możemy rozwiązać wiele problemów związanych z dziedziczeniem, ale kiedy implementujemy interfejs, powinniśmy stworzyć wszystkie metody wewnątrz klasy. Aby uzyskać więcej informacji, kliknij poniższy link:
http://php.net/manual/en/language.oop5.traits.php http://php.net/manual/en/language.oop5.interfaces.php
źródło
Interfejs to umowa, która mówi, że „ten obiekt jest w stanie to zrobić”, podczas gdy cecha daje temu obiektowi możliwość robienia tego.
Cecha jest zasadniczo sposobem „kopiowania i wklejania” kodu między klasami.
Spróbuj przeczytać ten artykuł. Jakie są cechy PHP?
źródło
Główna różnica polega na tym, że w przypadku interfejsów należy zdefiniować faktyczną implementację każdej metody w ramach każdej klasy, która implementuje wspomniany interfejs, dzięki czemu można mieć wiele klas implementujących ten sam interfejs, ale o różnych zachowaniach, podczas gdy cechy to tylko fragmenty kodu Klasa; Inną ważną różnicą jest to, że metody cech mogą być jedynie metodami klasowymi lub statycznymi, w przeciwieństwie do metod interfejsowych, które mogą (i zwykle są) metodami instancji.
źródło