2012-06-30 15 views
8

Quiero escribir un módulo (marco específico), que envolvería y ampliaría Facebook PHP-sdk (https://github.com/facebook/php-sdk/). Mi problema es cómo organizar clases de una manera agradable.sustitución de herencia de varios niveles

Así que entrar en detalles - Facebook PHP-SDK consta de dos clases:

  • BaseFacebook - clase abstracta con todo el SDK cosas hace
  • Facebook - se extiende BaseFacebook, e implementa métodos relacionados con la persistencia-abstractos padres con el uso de sesión predeterminado

Ahora tengo algunas funciones para agregar:

  • Facebook clase sustitución, integrados con clase de sesión marco
  • métodos abreviados, que las llamadas de la API de ejecución, que utilizan en su mayoría (a través de BaseFacebook :: API()),
  • métodos de autorización, así que no tengo que volver a escribir este lógica cada vez,
  • configuración, aspirado de clases de la arquitectura, en restaurantes en vez de pasar como params
  • almacenamiento en caché, integrada con el módulo de memoria caché marco

sé que algo ha ido muy mal, porque tengo demasiada herencia eso no se ve muy normal. Envolver todo en una clase de "extensión compleja" también parece demasiado. Creo que debería tener pocas clases de trabajo juntas, pero me meto en problemas como: si la clase de caché realmente no amplía y reemplaza el método BaseFacebook :: api(), las clases de autenticación y taquigrafía no podrán usar el almacenamiento en caché.

Tal vez algún tipo de patrón estaría justo aquí? ¿Cómo organizarías estas clases y sus dependencias?

EDITAR 04.07.2012

bits de código, relacionados con el tema:

Así es como la clase base de Facebook 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 clase extiende Facebook e impone esos métodos abstractos. He sustituido con mi substitude - clase 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, entonces este se extienden más con métodos abreviados y las variables de configuración:

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 

} 

No estoy seguro, si esto no es demasiado mucho para una sola clase (autorización/métodos abreviados/configuración). Luego viene otra capa de extensión: caché:

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 

} 

Ahora esto funciona. Nueva instancia de Facebook_Cache me da toda la funcionalidad. Los métodos abreviados de Facebook_Custom utilizan el almacenamiento en caché, porque Facebook_Cache sobreescribió el método api().Pero aquí está lo que me está molestando:

  • Creo que es demasiada herencia.
  • Todo está muy bien acoplado, como mire cómo tuve que especificar 'Facebook_Custom :: api' en lugar de 'parent: api', para evitar el método de bucle api() en la extensión de la clase Facebook_Cache.
  • Desorden general y fealdad.

Así que de nuevo, esto funciona, pero solo estoy preguntando sobre los patrones/formas de hacerlo de una manera más limpia y más inteligente.

+0

favor proporcionar algunos detalles más para conseguir más cerca de la solución .. –

+0

He añadido bits de código, Sory que se tardó tanto tiempo. – Luigi

+3

Por cierto; herencia múltiple es una clase que extiende más de otra clase. Eso es algo completamente diferente. – Sherlock

Respuesta

2

características adicionales, como el almacenamiento en caché normalmente se implementan como decorador (que veo que ya se ha dicho en otro comentario). Decoradores funcionan mejor con las interfaces, así que comenzarían mediante la creación de una:

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

que sea sencillo, no añadir nada que no es necesario el exterior (como setPersistentData).Luego envuelva la clase BaseFacebook existente en su nueva interfaz:

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(); 
    } 
} 

Ahora es fácil escribir un decorador de almacenamiento en caché:

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(); 
    } 
} 

Y luego:

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

Tanto $fb y $cachingFb exponer la la misma interfaz FacebookService - para que pueda elegir si desea almacenar en caché o no, y el resto del código no cambiará en absoluto.

En cuanto a su clase Facebook_Custom, es solo un montón de métodos de ayuda en este momento; debe factorizarlo en una o más clases independientes que envuelven FacebookService y proporcionan una funcionalidad específica. Algunos casos de uso de ejemplo:

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

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

más completo y bien administrado, gracias por la publicación. –

+0

¿Qué sucede si algunos de los Servicios necesitan más funciones que api() y getUser() de BaseFacebook? La interfaz debería tener más funciones? Tal vez use _call? Pero entonces realmente no hay necesidad de interfaz, y un Servicio podría obtener un descendiente BaseFacebook u otro servicio ... – Luigi

+0

@Luigi: puede agregarlos a la interfaz si es necesario. Me mantendría alejado de los métodos mágicos como '__call' porque hacen que sea fácil romper el diseño de OO. – casablanca

0

creo que el patrón de diseño del repositorio será mejor en esta situación. aunque no soy de php pero de acuerdo con esto debería resolver su problema ...

2

De hecho, es demasiada herencia. Parece un trabajo para Facade patrón de diseño. Usa la composición en lugar de la herencia para tener más flexibilidad. Delegue cualquier método que use para apropiarse de los objetos.

Por ejemplo, si cualquiera de las clases subyacentes cambia, puede simplemente cambiar sus métodos para adaptarse a los cambios, y no tendrá que preocuparse por anular ninguno de los métodos principales.

Generalmente sí, no es una buena idea asignar responsabilidades múltiples a una clase. Aquí, la responsabilidad de la clase sería representar una API externa.

2

he hecho algo así por SDK yahoo permítanme decirlo, darle una oportunidad :)

Asumamos que Facebook es la clase en la que está utilizando el SDK para todas las llamadas a métodos fin. Puede crear una nueva clase (como lo permite su trabajo de marco) y asignar una variable de la clase a la instancia de la clase de Facebook.

Use __call() para todos los métodos de Facebook y coloque los de su clase en la clase contenedora. para todos los métodos indefinidos, el envoltorio irá a la clase de Facebook y no habrá ninguna herencia involucrada. Me funcionó. Espero que ayude :)

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 !!!! 

Editado:

que no es necesario para crear múltiples envoltura para más de uno clases siguientes se puede hacer, sólo tiene que tener cuidado a la hora llamada a un método, tienen para sufijo la instancia de retención de nombre de variable de la clase envuelta.

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

He estado pensando en esto la última noche. Supongo que esto se llama el "patrón Decorador". Esto es bueno, sin embargo, todavía tengo dudas sobre una cosa. Necesito tener pocas envolturas como esa. Como uno para el almacenamiento en caché, uno para los métodos shorhand, uno para la autorización. Ahora, ¿cómo podría usar múltiples decoradores de una manera suave? ¿Una clase separada para cargar una combinación de ellos (patrón de estrategia/fascade)? Tal vez tienes una idea? – Luigi

+0

He editado mi respuesta, espero que ayude comprobar :) –

+0

Después de editar, la API de la clase MyWrapper es completa, pero ahora FacebookCache no puede usar directamente la clase de Facebook y requerirá condiciones adicionales/colaboración para los métodos MyWrapper. Me gusta mejor tu primer ejemplo. Podría agregar un parámetro de constructor y usarlo así: '$ fb = new Facebook(); $ fbCache = nueva FacebookCache ($ fb); $ taquigrafía = nueva FacebookShort ($ fbCache); $ shorthandNoCache FacebookShort ($ fb); 'etc. Así que - acoplamiento flojo. La clase Shorhand puede usar el almacenamiento en caché sin saberlo. Aceptaré tu respuesta si no ocurre nada mejor antes de que termine el tiempo de recompensa :). Gracias. – Luigi

Cuestiones relacionadas