Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: OOP & Interfejsy
Forum PHP.pl > Forum > PHP > Object-oriented programming
KirkoR
Witam. Czy ktoś może mi przedstawić szerzej idee interfejsów, do czego służą, kiedy i po co ich uzywać? Bo nigdzie nie mogę znaleźć konkretnych informacji.
krzysztof f.
Drugi link od mike_mech powinien wszystko wyjaśnić.

Na wszelki wypadek ;)

Interfejsy pozwalają pisać lepiej zorganizowany, czytelniejszy kod oraz oddzielać implementację od właśnie interfejsu. Założenia programowania obiektowego mówią o tym, że powinno się pisać kod bardziej ukierunkowany na interfejs, niż samą implementację. Ułatwia to późniejsze ponowne wykorzystywanie kodu i współpracę między projektantami/programistami. Programowanie (w szczególności programowanie komponentowe http://c2.com/cgi/wiki?ComponentOrientedProgramming) to nie tylko pisanie aplikacji dla końcowego użytkownika. Obecnie programiści piszą kod, który jest wykorzystywany/kupowany później przez innych programistów. Aby praca z obcymi komponentami czy bibliotekami była jak najłatwiejsza, czy też w ogóle możliwa, muszą one dostarczać czytelne API http://en.wikipedia.org/wiki/API. Programistę wykorzystującego taki komponent nie interesuję implementacja (czyli w jaki sposób działają poszczególne zadania), a jedynie interfejs (czyli jakie zadania ma możliwość wykonać).

Mówiąc prościej… interfejs w odniesieniu do obiektu to po prostu lista metod jakie obiekt posiada wraz z typami niezbędnych parametrów i informacją o zwracanej wartości. php umożliwia wymuszanie jedynie typów obiektowych jako parametrów funkcji. Gorzej jest z wartościami zwracanymi, ale ma się to zmienić http://www.php.net/~derick/meeting-notes.h...d-return-values. Póki co jedyny sposób to dobra dokumentacja kodu http://phpdoc.org/.
W taki sposób znając interfejs obiektu wiemy jak go używać. Bo zgodnie z ideą interfejsu wszystkie jego metody muszą być publiczne.

Jak sprawa ma się w praktyce?

Załóżmy, że mamy napisaną aplikację opartą o Front Controller http://www.phppatterns.com/docs/design/the...troller_and_php, który stanowi punkt wejścia do naszej aplikacji. Możemy w nim rejestrować filtry http://java.sun.com/blueprints/corej2eepat...tingFilter.html, które będą wykonywały jakieś operacje na danych wejściowych i wyjściowych. Idea polega na tym, że wykorzystując nasz kontroler w różnych przypadkach, możemy dowolnie komponować zestawy filtrów, lub też tworzyć nowe w miarę potrzeb. W tym celu tworzymy interfejs filtru, z którym będzie współpracował obiekt kontrolera.

  1. <?php
  2. interface IInterceptingFilter
  3. {
  4.  /**
  5. * Operacje wykonywane przed wywołaniem kontrolera
  6. *
  7. * @param Request Obiekt żądania
  8. * @return Request
  9. * @access public
  10. **/
  11.  public function preProcess( Request $oRequest );
  12.  
  13.  /**
  14. * Operacje wykonywane po obsłudze żądania
  15. *
  16. * @param Response Obiekt odpowiedzi
  17. * @return Response
  18. * @access public
  19. **/
  20.  public function postProcess( Response $oResponse );
  21. }
  22. ?>


Stwórzmy teraz przykładowy filtr:

  1. <?php
  2. class TimeExecutionFilter implements InterceptingFilter
  3. {
  4.  
  5.  /**#@+
  6. * @var string 
  7. * @access private 
  8. */
  9.  /**
  10. * Data rozpoczęcia skryptu (format microtime) 
  11. */
  12.  private $_sStart;
  13.  /**
  14. * Data zakończenia wykonywania skryptu (format microtime) 
  15. */
  16.  private $_sEnd;
  17.  /**#@-*/
  18.  
  19.  
  20.  public function preProcess( Request $oRequest )
  21.  {
  22. $this->_sStart = microtime();
  23. return $oRequest;
  24.  }
  25.  
  26.  public function postProcess( Response $oResponse )
  27.  {
  28. $this->_sEnd = microtime();
  29. return $oResponse->set( 'time', $this->_calculateTime( $this->_sStart, $this->_sEnd ) );
  30.  }
  31.  
  32.  /**
  33. * Przeliczenie czasu wykonania strony
  34. * @param string Czas rozpoczęcia (format microtime)
  35. * @param string Czas zakończenia (format microtime)
  36. * @return double Czas wykonania w sekundach
  37. * @access private
  38. **/
  39.  private function _calculateTime( $sStart, $sEnd )
  40.  {
  41. $aEnd = explode( ' ', $sEnd );
  42. $aStart = explode( ' ', $sStart );
  43.  
  44. $dEnd = (float) $aEnd[1] + (float) $aEnd[0];
  45. $dStart = (float) $aStart[1] + (float) $aStart[0];
  46.  
  47. return number_format( $dEnd - $dStart, 4);
  48.  }
  49. }
  50.  
  51. ?>


Z czasem rozwoju aplikacji może się okazać, że przydałby nam się jeszcze inne filtry. Nie ma problemu wystarczy, że zaimplementujemy na nowo nasz interfejs lub zlecimy to komuś innemu. Tak długo jak będziemy trzymać się interfejsu kontroler będzie potrafił sobie z nim poradzić, a my będzimy mogli go spokojnie używać. Chociażby w taki sposób:

  1. <?php
  2.  $oController = new FrontController();
  3.  $oController->addFilter( new AuthenticationFilter( 'login.php' ) );
  4.  $oController->addFilter( new OutputBufferFilter() );
  5.  $oController->addFilter( new TimeExecutionFilter() );
  6.  $oController->run();
  7.  
  8. ?>
bela
Ja filtry inaczej rozwiązałem. Nie mam 2 metod pre i post, tylko jedną: executeFilter. A niej jest $this->executeNext() co uruchamia następny filterek jaki jest w łańcuszku aż do ExecFiltra który to odpala akcje. Oczywiście jeśli będzię kod przed executeNext, zostanie to potraktowe jako pre filter, a po metodzie execNext jako post filter.
krzysztof f.
I o to chodzi. Cała idea wzorców poleg przecież na tym, że podają one sposób rozwiązania jakiegoś złożonego problemu, a implementację zostawiają programiście pozwalając na dużą swobodę. Moja implementacja filtrów z koleji wygląda mniej więcej tak:

Ociu
Czyli jesli dobrze zrozumiałem, to narzucenie podanych method w interfejsie ?

Swoją drogą, filtry zrobił bym tak:

Klasa Filter, która 'przechowuje' dany filter, potem wrzucamy do ExecuteFilter w którym jest tylko metoda np. process, która uruchamia Filter jako plugin.
hwao
Ja może krótko..

Klasy stosujemy jezeli jest zaleznosc "jest jakims\jakas", w przeciwnym wypadku nie zachodzi potrzeba dziedziczenia i stosuje sie szybsze interfejsy.
bela
Cytat(Ociu @ 2005-12-02 14:22:58)
Czyli jesli dobrze zrozumiałem, to narzucenie podanych method w interfejsie ?

Oczywiście, przecież interfejs ma za zadanie wymusić konkretne metody publiczne w implementującej klasie, w szczególnych wypadkach nie ma wcale metod np. interfejs Serializable, który pozwala na serializowanie obiekt, to bardziej z Javy chociaż ostatnio chyba też w SPL widziałem.

Cytat
Klasy stosujemy jezeli jest zaleznosc "jest jakims\jakas", w przeciwnym wypadku nie zachodzi potrzeba dziedziczenia i stosuje sie szybsze interfejsy.

Hmm, trochę nie kumam ;]
Ja stosuję dziedziczenie tam gdzie chce użyć metod klas rodziców. Czy ja wiem czy interfejsy są szybsze, po prostu nie mają ciała metod. Zresztą tylko interfejsy przydają się głównie przypisaniu aplikacji i pozwalają wykryć za wczasu, że nie ma konkretnej metody, bo polimorfizm w php praktycznie nie istnieje.
hwao
Bela - nigdy do konca nie wnikałem jak to jest w php smile.gif gdyz nie interesuja mnie to smile.gif

W C# naprzykład interfejsy sa szybsze niz dziedziczenie, pozatym badz co badz zajmuja mniej pamieci.

Jezeli potrzebujesz uzywac metod, to znaczy ze ta relacja zachodzi, ale wprowadzanie jej na hama jest poprostu dlamnie absurdem smile.gif

outputBufforPlugin jest Pluginem smile.gif wiec dziedziczenie powinno zajsc (chyba ze nasza klasa Plugin jest "czysta" i nie ma jakiegos api wykozystywanego gdzies tam)
Ociu
krzysztof f.: jeśli dobrze zrozumiałem, to twoje filtry działają tak, że każdy filter powinien mieć dwa pliki.
1. Jeden plik, który zawiera 'suchy' kod, który jest jakby mózgiem filtru.
2. Drugi plik, który będzie wykonywał całą robotę.
Cały filter leci do FilterChain, który jest zarządzany przez FilterManager, który kieruje filtry do iteratora z ArrayAccess i jesli potrzeba to wyświetla wynik. uf..
rolleyes.gif
dr_bonzo
Cytat
outputBufforPlugin jest Pluginem smile.gif wiec dziedziczenie powinno zajsc

Zgadza sie, masz bazowa klase plugin z abstrakcyjnymi metodami execute() (czy jakos tam) i je przeladowujesz.

Z JAVY (Java 2 Podstawy) cytat ze skrotami (nie chce mi sie tyle przepisywac) [moje dopiski]:
"W javie interfejs nie jest klasa ale zbiorem wymagan dotyczacych klas, ktore chca dostosowac sie do interfejsu

Zazwyczaj dostawca pewnych uslug stwierdza: Jesli twoja klasa jest dopasowana do danego interfejsu, to wykonam usluge. (...) Metoda sort klasy Arrays obiecuje posortowac obiekty tablicy, ale pod jednym warunkiem: obiekty musza nalezec do klas implementujacych interfejs Comparable [nie musza byc tej samej klasy!!!] [musza zaimplementowac jedyna metode: int compateTo( Object innyObiekt );]"

Metoda Arrays.sort() pobiera obiekt "typu" Comparable[] (interfejsy zastapily (w pewnym stopniu) wielokrotne dziedziczenie) -- czyli tablice obiektow implementujacyhc interfejs Comparable i jes sortuje.

Interfejs (np. Telewizora -- przysicki: ON/OFF, Vol+, Vol-, P+, P- ) okresla metody jakie musi zaimplementowac obiekt aby inny obiekt/user (user TV) znajacy obsluge tego interfejsu mogl sie nim poslugiwac (umiesz obsluzyc jeden telewizor, obsluzysz tez setke innych bez czytania manuala). Oczywiscie obiekt moze implementowac inne interfejsy (TV+Mikrofalowka, co co? tongue.gif), co nie przeszkadza userowi znajacy tylko jeden z nich uzuwac tego obiektu.

Inny przydlad: interfejs Zepsuwalne (ale glopie slowo) (jedyna medoda: void zepsuj()), jestes typem czlowieka ktory lubi wszystko psuc (taka abstrakcja, jakby co:)). Obiekty implementujace ten interfejs: TV, Video, Komp, MAC, PC, Sun, Impreze, Atmosfera. Dzieki temu ze implementuja ten interfejs mozesz uzyc na nich metody zepsuj(), nawet jak nie posiadaja wspolnej klasy bazowej.

ufff... DA-END
krzysztof f.
hmmm

Rozważania na temat w jakich sytuacjach stosować interfejsy a kiedy tworzyć klasy abstrakcyjne są jak najbardziej na miejscu. Nie ma to jednak nic wspólnego z prędkością czy wydajnością.

Cytat
W C# naprzykład interfejsy sa szybsze niz dziedziczenie, pozatym badz co badz zajmuja mniej pamieci.


hwao czy mógłbyś przedstawić jakieś testy wydajnościowe, które ktoś wykonywał i które potwierdzają Twoją teorię? Błędem jest stawianie interfejsów na równi z dziedziczeniem. Obydwa pojęcia oznaczają coś innego i nie są wymienne.

Kiedy potrzebujemy z jakiegoś powodu określić jedynie zestaw metod publicznych dla pisanych w przyszłości klas, nie zajmując się implementacją stosujemy interfejsy. Natomiast gdy na tym etapie pojawia się jakaś wspólna logika dla rodziny klas, którą warto jest zawrzeć w klasie bazowej stosujemy klasy abstrakcyjne. To oczywiście duże uproszczenie, a różnic jest dużo więcej. W php np. nie mamy możliwości dziedziczenia po więcej niż jednej klasie, ale możemy za to implementować więcej niż jeden interfejs, a nawet rozszerzać interfejsy po kilku innych. Ilustruje to przykładowy kod:

  1. <?php
  2. interface IA
  3. {
  4.  public function aa();
  5. }
  6.  
  7. interface IB
  8. {
  9.  public function bb();  
  10. }
  11.  
  12. interface IAB extends IA, IB
  13. {
  14.  public function ab(); 
  15. }
  16.  
  17.  
  18. class AB implements IAB
  19. {
  20. public function aa()
  21. {
  22. echo 'A_B::aa()';  
  23. }
  24.  
  25. public function bb( )
  26. {
  27. echo 'A_B::bb()';  
  28. }
  29.  
  30. public function ab( )
  31. {
  32. echo 'A_B::ab()';
  33. }
  34. }
  35.  
  36. $oAB = new AB();
  37.  
  38. echo $oAB->aa(); // A_B::aa()
  39. echo $oAB->bb(); // A_B::bb()
  40. echo $oAB->ab(); // A_B::ab()
  41.  
  42. ?>


Cytat
bo polimorfizm w php praktycznie nie istnieje.

Jak to?? Już w php4 można było stosować dziedziczenie http://www.php.net/manual/en/keyword.extends.php, które jest przecież głównym narzędziem polimorfizmu.

Cytat
krzysztof f.: jeśli dobrze zrozumiałem, to twoje filtry działają tak, że każdy filter powinien mieć dwa pliki...


Prawie : ) Każdy nowy filtr to dokładnie jedna klasa (i jeden plik) dziedzicząca po abstrakcyjnej klasie InterceptingFilter. Kod klienta operuje jedynie na obiekcie FilterManager, który ukrywa przed nim całą obsługę filtrów. Tworząc instancję menadżera filtrów podajemy w konstruktorze FilterManager::__construct() referencję do docelowego obiektu Target, który wykonuje jakąś operację poprzedzaną i zakańczaną działaniem filtrów. Obiekt Target, podobnie jak filtry, musi implementować interfejs IProcessor. Następnie używając metody FilterManager::addFilter() dodajemy zestaw potrzebnych nam filtrów i w odpowiednim momencie odpalamy przetwarzanie FilterManager::processFilter(). Zgdonie z ideą filtrów, FilterManager::processFilter()wywoła metody InterceptingFilter::preProcess() wszystkich filtrów w kolejności ich dodawania, następnie Target::execute() i metody InterceptingFilter::postProcess() w odwrotnej kolejności.
hwao
Cytat(krzysztof f. @ 2005-12-06 12:39:45)
Cytat
W C# naprzykład interfejsy sa szybsze niz dziedziczenie, pozatym badz co badz zajmuja mniej pamieci.


hwao czy mógłbyś przedstawić jakieś testy wydajnościowe, które ktoś wykonywał i które potwierdzają Twoją teorię? Błędem jest stawianie interfejsów na równi z dziedziczeniem. Obydwa pojęcia oznaczają coś innego i nie są wymienne.

Wiem ze oznaczaja co innego, aczkolwiek moga miec "podobne zastosowanie". Co do teori, to jest ona oparta na na ksiezce o c# napisanej przez jednego z tworcow tego jezyka.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.