2012-06-30 16 views
8

Chcę napisać moduł (framework specific), który obejmowałby i rozszerzał Facebook PHP-sdk (https://github.com/facebook/php-sdk/). Moim problemem jest - jak zorganizować zajęcia w miły sposób.substytucja wielopoziomowego dziedziczenia

Tak się w szczegóły - Facebook PHP SDK składa się z dwóch klas:

  • BaseFacebook - klasa abstrakcyjna ze wszystkimi SDK rzeczy robi
  • Facebook - rozciąga BaseFacebook i wdraża nadrzędne abstrakcyjnych metod związanych oporność- z domyślnej Zastosowanie sesji

teraz mam jakąś funkcjonalność do dodania:

  • Facebook klasa substytucja, zintegrowane z klasą sesji ramy
  • metody stenograficzne, który wywołuje run API, używam głównie (poprzez BaseFacebook :: API()),
  • metody autoryzacji, więc nie mam do przerobienia tego logika za każdym razem,
  • konfiguracja, zasysane z klas ramowych insted przekazywane jako params
  • buforowanie, zintegrowany z modułem ramy cache

wiem coś poszło bardzo źle, bo mam zbyt wiele dziedziczenie to nie wygląda zbyt normalnie. Zawijanie wszystkiego w jednej klasie "złożonego rozszerzenia" również wydaje się zbyt duże. Myślę, że powinienem mieć niewiele pracujących klas, ale mam problemy, takie jak: jeśli klasa pamięci podręcznej tak naprawdę nie rozszerza i nie zastępuje metody BaseFacebook :: api() - skrócone i autentykacyjne klasy nie będą mogły korzystać z buforowania.

Może jakiś wzór będzie tutaj? Jak zorganizowałbyś te klasy i ich zależności?

EDIT 04.07.2012

fragmenty kodu, związane z tematem:

ten sposób klasa bazowa Facebooku PHP-SDK:

abstract class BaseFacebook { 

    // ... some methods 

    public function api(/* polymorphic */) 
    { 
     // ... method, that makes api calls 
    } 

    public function getUser() 
    { 
     // ... tries to get user id from session 
    } 

    // ... other methods 

    abstract protected function setPersistentData($key, $value); 

    abstract protected function getPersistentData($key, $default = false); 

    // ... few more abstract methods 

} 

Normaly Facebook klasa rozszerza to i impelements tych abstrakcyjnych metod. Wymieniłem go z moim substitude - klasa Facebook_Session:

class Facebook_Session extends BaseFacebook { 

    protected function setPersistentData($key, $value) 
    { 
     // ... method body 
    } 

    protected function getPersistentData($key, $default = false) 
    { 
     // ... method body 
    } 

    // ... implementation of other abstract functions from BaseFacebook 
} 

Ok, to ja tym bardziej z przedłużenia skrót metod i zmiennych konfiguracyjnych:

class Facebook_Custom extends Facebook_Session { 

    public function __construct() 
    { 
     // ... call parent's constructor with parameters from framework config 
    } 

    public function api_batch() 
    { 
     // ... a wrapper for parent's api() method 
     return $this->api('/?batch=' . json_encode($calls), 'POST'); 
    } 

    public function redirect_to_auth_dialog() 
    { 
     // method body 
    } 

    // ... more methods like this, for common queries/authorization 

} 

Nie jestem pewien, czy to nie jest zbyt dużo dla pojedynczej klasy (autoryzacja/skrót metod/konfiguracji). Następnie pojawia się kolejna warstwa rozszerzająca - pamięć podręczna:

class Facebook_Cache extends Facebook_Custom { 

    public function api() 
    { 
     $cache_file_identifier = $this->getUser(); 

     if(/* cache_file_identifier is not null 
       and found a valid file with cached query result */) 
     { 
      // return the result 
     } 
     else 
     { 
      try { 
       // call Facebook_Custom::api, cache and return the result 
      } catch(FacebookApiException $e) { 
       // if Access Token is expired force refreshing it 
       parent::redirect_to_auth_dialog(); 
      } 
     } 

    } 

    // .. some other stuff related to caching 

} 

Teraz to prawie działa. Nowa instancja Facebook_Cache daje mi całą funkcjonalność. Skrócone metody z Facebook_Custom korzystają z buforowania, ponieważ Facebook_Cache nadpisał metodę api().Ale oto, co mnie dręczy:

  • Myślę, że to zbyt wiele dziedziczenia.
  • To wszystko jest bardzo ściśle powiązane - jak wyglądało określenie "Facebook_Custom :: api" zamiast "parent: api", aby uniknąć pętli metody api() na rozszerzającej się klasie Facebook_Cache.
  • Ogólny bałagan i brzydota.

To znowu działa, ale ja po prostu pytam o wzory/sposoby robienia tego w bardziej przejrzysty i inteligentny sposób.

+0

Proszę podać kilka szczegółów, aby uzyskać więcej blisko rozwiązania .. –

+0

Dodałem bitów kodu, sory, że trwało to tak długo. – Luigi

+3

Przy okazji; dziedziczenie wielokrotne to jedna klasa obejmująca więcej niż jedną inną klasę. To coś zupełnie innego. – Sherlock

Odpowiedz

2

funkcje pomocnicze, takie jak buforowanie są zwykle realizowane jako dekorator (co widzę już wspomniano w innym komentarzu). Dekoratorzy pracują najlepiej z interfejsami, więc chciałbym zacząć od stworzenia jednego:

interface FacebookService { 
    public function api(); 
    public function getUser(); 
} 

Keep it simple, nie dodawać nic nie trzeba zewnętrznie (takich jak setPersistentData).Następnie owinąć istniejącą BaseFacebook klasę w nowym interfejsie:

class FacebookAdapter implements FacebookService { 
    private $fb; 

    function __construct(BaseFacebook $fb) { 
    $this->fb = $fb; 
    } 

    public function api() { 
    // retain variable arguments 
    return call_user_func_array(array($fb, 'api'), func_get_args()); 
    } 

    public function getUser() { 
    return $fb->getUser(); 
    } 
} 

Teraz łatwo napisać dekorator buforowania:

class CachingFacebookService implements FacebookService { 
    private $fb; 

    function __construct(FacebookService $fb) { 
    $this->fb = $fb; 
    } 

    public function api() { 
    // put caching logic here and maybe call $fb->api 
    } 

    public function getUser() { 
    return $fb->getUser(); 
    } 
} 

, a następnie:

$baseFb = new Facebook_Session(); 
$fb = new FacebookAdapter($baseFb); 
$cachingFb = new CachingFacebookService($fb); 

Zarówno $fb i $cachingFb wystawiać ten sam interfejs FacebookService - możesz wybrać, czy chcesz buforować czy nie, a reszta kodu w ogóle się nie zmieni.

Jeśli chodzi o klasę Facebook_Custom, jest to tylko kilka metod pomocniczych; powinieneś przeliczyć go na jedną lub więcej niezależnych klas, które zawijają FacebookService i zapewniają określoną funkcjonalność. Niektóre przypadki przykład zastosowanie:

$x = new FacebookAuthWrapper($fb); 
$x->redirect_to_auth_dialog(); 

$x = new FacebookBatchWrapper($fb); 
$x->api_batch(...); 
+0

bardziej wszechstronny i dobrze zarządzany, dzięki za wpis. –

+0

Co jeśli niektóre usługi wymagają więcej funkcji niż api() i getUser() z BaseFacebook? Interfejs powinien mieć więcej funkcji? Może użyć _call? Ale tak naprawdę nie ma potrzeby korzystania z interfejsu, a usługa może uzyskać zarówno bazę potomną BaseFacebook, jak i inną usługę ... – Luigi

+0

@Uwagi: Możesz dodać je do interfejsu, jeśli potrzebujesz. Trzymałbym się z daleka od magicznych metod, takich jak '__call', ponieważ ułatwiają one przełamanie projektu OO. – casablanca

0

Myślę, że wzór projektu repozytorium będzie lepszy w tej sytuacji. chociaż nie jestem z php, ale jak na oops powinien rozwiązać twój problem ..

2

Jest to w rzeczywistości zbyt wiele dziedziczenia. Wygląda jak praca dla wzoru projektowego Facade. Użyj kompozycji zamiast dziedziczenia, aby uzyskać większą elastyczność. Deleguj dowolne metody, których używasz do odpowiednich obiektów.

Na przykład, jeśli jedna z podstawowych klas się zmieni, możesz po prostu zmienić swoje metody, aby dostosować się do zmian, i nie będziesz musiał się martwić o przesłonięcie żadnej z metod macierzystych.

Ogólnie tak, nie jest dobrym pomysłem, aby przypisać wiele obowiązków do jednej klasy. W tym przypadku odpowiedzialność klasy będzie reprezentować zewnętrzny interfejs API.

2

Zrobiłem kilka rzeczy tak przez yahoo SDK Ujmę to, spróbować :)

Załóżmy Facebook jest klasa w SDK używasz dla wszystkich wywołań metod koniec. Możesz utworzyć nową klasę (tak jak pozwala na to twoja praca ramowa) i przypisać zmienną klasy do instancji klasy Facebook.

Użyj __call() dla wszystkich metod Facebooka i umieść swoje obiekty custome w klasie opakowania. dla wszystkich nieokreślonych metod, które otoczy, przejdzie do klasy Facebook i nie ma w ogóle żadnego dziedziczenia. To zadziałało dla mnie. Nadzieję, że to pomaga :)

Class MyWrapper 
    { 
     protected $facebook; 
     public function __construct() 
     { 
      $this->facebook = new FaceBook(); 
     } 

     public function __call($method,$args) 
     { 
      return $this->facebook->$method($args); 
     } 

     ///define Your methods ////////// 

     /////////////////////////////////// 
    } 

    $t = new MyWrap; 
    $t->api(); // Whatever !!!! 

edycja:

Nie trzeba tworzyć wielokrotne owinięcie dla więcej niż jednej klasy następujących można zrobić, po prostu trzeba dbać w momencie wywołania metody, mają do sufiksu instancji przechowującej nazwę zmiennej w klasie zawiniętej.

Class MyWrapper 
    { 
     protected $facebook; 
     protected $facebookCache; 
     public function __construct() 
     { 
      $this->facebook = new FaceBook(); 
      $this->facebookCache = new FacebookCache(); 
     } 

     public function __call($method,$args) 
     { 
      $method = explode('_',$method); 
      $instance_name = $method[0]; 
      $method_name = $method[1]; 
      return $this->$instance_name->$method_name($args); 
     } 

     ///define Your methods ////////// 

     /////////////////////////////////// 
    } 

    $t = new MyWrap; 
    $t->facebook_api(); // Whatever !!!! 
    $t->facebookCache_cache(); 
+1

Myślałem o tym ostatnim wieczorze. Przypuszczam, że nazywa się to "wzorem dekoratora". To miłe, ale wciąż mam wątpliwości co do jednej rzeczy. Muszę mieć kilka takich opakowań. Jak jeden dla buforowania, jeden dla metod shorhand, jeden dla autoryzacji. Jak mogę teraz użyć wielu dekoratorów w delikatny sposób? Oddzielna klasa do ładowania ich kombinacji (wzór strategii/fascade)? Może masz pomysł? – Luigi

+0

Edytowałem swoją odpowiedź, mam nadzieję, że to pomoże proszę sprawdzić :) –

+0

Po edycji API klasy MyWrapper jest obszerna, ale teraz FacebookCache nie może używać bezpośrednio klasy Facebooka i wymagałoby dodatkowych warunków/hardocding do współpracy z metodami MyWrapper. Lubię twój pierwszy przykład lepiej. Mógłbym dodać parametr konstruktora i użyć go w ten sposób: '$ fb = new Facebook(); $ fbCache = new FacebookCache ($ fb); $ shorthand = new FacebookShorthand ($ fbCache); $ shorthandNoCache FacebookShorthand ($ fb); 'itd. Tak więc luźne sprzężenie. Klasa Shorhand może używać buforowania, nie wiedząc o tym. Przyjmuję twoją odpowiedź, jeśli nie wydarzy się nic lepszego, zanim skończy się czas nagród :). Dzięki. – Luigi