2013-07-05 5 views
5

Mam zestaw klas, które mają nawyk wielokrotnego wywoływania z tymi samymi argumentami. Te metody zazwyczaj uruchamiają żądania bazy danych i budują tablice obiektów i tym podobne, a więc aby wykluczyć to duplikowanie, stworzyłem kilka metod buforowania w celu optymalizacji. Są one używane tak:Najlepszy sposób zaimplementowania wzorca dekoratora do buforowania wyników metod w PHP

Przed buforowanie stosowane:

public function method($arg1, $arg2) { 
$result = doWork(); 
return $result; 
} 

Po buforowanie stosowane:

public function method($arg1, $arg2, $useCached=true) { 
if ($useCached) {return $this->tryCache();} 
$result = doWork(); 
return $this->cache($result); 
} 

Niestety mam teraz pozostało nieco pracochłonne zadanie ręcznie dodając to do wszystkich metody - uważam, że jest to przypadek użycia wzoru dekoratora, ale nie mogę wymyślić, jak zaimplementować go w prostszy sposób w PHP dla tego przypadku.

Jaki jest najlepszy sposób, aby to zrobić, miejmy nadzieję, że albo wszystkie metody w którejś z tych klas automatycznie to zrobić, czy po prostu muszę dodać jedną linię w metodzie itp?

Sprawdziłem sposoby na zastąpienie oświadczenia zwrotu i takie, ale naprawdę nie widzę niczego.

Dzięki!

Odpowiedz

10

Jeśli nie potrzebują Typ bezpieczeństwa, można użyć rodzajowe Dekorator cache:

class Cached 
{ 
    public function __construct($instance, $cacheDir = null) 
    { 
     $this->instance = $instance; 
     $this->cacheDir = $cacheDir === null ? sys_get_temp_dir() : $cacheDir; 
    } 

    public function defineCachingForMethod($method, $timeToLive) 
    { 
     $this->methods[$method] = $timeToLive; 
    } 

    public function __call($method, $args) 
    { 
     if ($this->hasActiveCacheForMethod($method, $args)) { 
      return $this->getCachedMethodCall($method, $args); 
     } else { 
      return $this->cacheAndReturnMethodCall($method, $args); 
     } 
    } 

    // … followed by private methods implementing the caching 

Można by następnie owinąć instancji, który wymaga buforowania na ten dekorator tak:

$cachedInstance = new Cached(new Instance); 
$cachedInstance->defineCachingForMethod('foo', 3600); 

oczywiście $cachedInstance nie ma metody foo(). Sztuką jest tutaj utilize the magic __call method to intercept all calls to inaccessible or non-existing methods i przekazanie ich do udekorowanej instancji. W ten sposób eksponujemy cały publiczny interfejs API udekorowanej instancji za pośrednictwem dekoratora.

Jak widać, metoda __call zawiera również kod sprawdzający, czy dla tej metody istnieje buforowanie zdefiniowane. Jeśli tak, zwróci buforowane wywołanie metody. Jeśli nie, wywoła instancję i zwróci pamięć podręczną.

Alternatywnie można przekazać dedykowany element CacheBackend do programu Decorator zamiast implementować buforowanie w samym dekoratorze. Dekorator działałby wtedy jedynie jako pośrednik między udekorowanym wystrojem a zapleczem.

To ogólne podejście polega na tym, że Twój dekorator pamięci podręcznej nie będzie miał typu dekorowanej instancji.Kiedy twój kod zużywający spodziewa się wystąpienia typu Wystąpienie, dostaniesz błędy.


Jeśli potrzebujesz typu bezpieczny dekoratorów, trzeba użyć „klasyczne” podejście:

  1. Załóż interfejsu API urządzone instancji publicznej. Możesz to zrobić ręcznie lub, jeśli to dużo pracy, użyj mojego Interface Distiller) Zmień typ wskazań dla każdej metody, oczekując, że dekorowana instancja interfejsu będzie implementować je za pomocą Urządzonej instancji.
  2. Oddaj Dekorator wdrożyć go i przekazać żadnych metod do zdobionej przykład
  3. modyfikować wszystkie metody, które wymagają buforowania
  4. Powtórz dla wszystkich klas, które chcą korzystać z dekorator

w pigułce

class CachedInstance implements InstanceInterface 
{ 
    public function __construct($instance, $cachingBackend) 
    { 
     // assign to properties 
    } 

    public function foo() 
    { 
     // check cachingBackend whether we need to delegate call to $instance 
    } 
} 

Wada: wada: polega na tym, że jest to więcej pracy. Musisz to zrobić dla każdej klasy, która ma używać buforowania. Będziesz także musiał umieścić sprawdzanie w backendach pamięci podręcznej w każdej funkcji (duplikacji kodu), a także delegować wszelkie wywołania, które nie wymagają buforowania do udekorowanej instancji (nużące i podatne na błędy).

+0

Łamie zasadę zastępowania Liskov, spójrz na projekt Proxy Manager, może pomaga, pomógł mi – decebal

+0

@decebal Podejście używające '__call' oczywiście, ale wyraźnie wspomniałem, że nie jest to bezpieczny typ, co sugeruje. "Klasyczny" dekorator nie narusza LSP, ponieważ implementuje ten sam interfejs. – Gordon

1

Użyj magicznej metody __call.

class Cachable { 
    private $Cache = array(); 
    public function Method1(){ 
     return gmstrftime('%Y-%m-%d %H:%M:%S GMT'); 
    } 
    public function __call($Method, array $Arguments){ 
     // Only 'Cached' or '_Cached' trailing methods are accepted 
     if(!preg_match('~^(.+)_?Cached?$~i', $Method, $Matches)){ 
      trigger_error('Illegal Cached method.', E_USER_WARNING); 
      return null; 
     } 
     // The non 'Cached' or '_Cached' trailing method must exist 
     $NotCachedMethod = $Matches[1]; 
     if(!method_exists($this, $NotCachedMethod)){ 
      trigger_error('Cached method not found.', E_USER_WARNING); 
      return null; 
     } 
     // Rebuild if cache does not exist or is too old (5+ minutes) 
     $ArgumentsHash = md5(serialize($Arguments)); // Each Arguments product different output 
     if(
      !isset($this->Cache[$NotCachedMethod]) 
      or !isset($this->Cache[$NotCachedMethod][$ArgumentsHash]) 
      or ((time() - $this->Cache[$NotCachedMethod][$ArgumentsHash]['Updated']) > (5 * 60)) 
     ){ 
      // Rebuild the Cached Result 
      $NotCachedResult = call_user_func_array(array($this, $NotCachedMethod), $Arguments); 
      // Store the Cache again 
      $this->Cache[$NotCachedMethod][$ArgumentsHash] = array(
       'Method' => $NotCachedMethod, 
       'Result' => $NotCachedResult, 
       'Updated' => time(), 
      ); 
     } 
     // Deliver the Cached result 
     return $this->Cache[$NotCachedMethod][$ArgumentsHash]['Result']; 
    } 
} 
$Cache = new Cachable(); 
var_dump($Cache->Method1()); 
var_dump($Cache->Method1Cached()); // or $Cache->Method1_Cached() 
sleep(5); 
var_dump($Cache->Method1()); 
var_dump($Cache->Method1Cached()); // or $Cache->Method1_Cached() 

ta jest stosowana przy użyciu pamięci wewnętrznej, ale można użyć DB dla tego i tworzyć własne krótkotrwałe przechowywanie. Wystarczy dołączyć _Cached lub Cached do dowolnej istniejącej metody. Oczywiście możesz zmienić długość życia i więcej.

To tylko dowód koncepcji. Jest dużo miejsca dla poprawy :)

+0

to narusza zasadę substytucji Liskov, ale jest to dobry przykład, sprawia, że ​​jestem ciekawa, jak zaimplementować apc i memcache? – decebal

0

Oto fragment z artykułu around the subject of caching in php

/** 
* Caching aspect 
*/ 
class CachingAspect implements Aspect 
{ 
    private $cache = null; 

    public function __construct(Memcache $cache) 
    { 
     $this->cache = $cache; 
    } 

/** 
* This advice intercepts the execution of cacheable methods 
* 
* The logic is pretty simple: we look for the value in the cache and if we have a cache miss 
* we then invoke original method and store its result in the cache. 
* 
* @param MethodInvocation $invocation Invocation 
* 
* @Around("@annotation(Annotation\Cacheable)") 
*/ 
public function aroundCacheable(MethodInvocation $invocation) 
{ 
    $obj = $invocation->getThis(); 
    $class = is_object($obj) ? get_class($obj) : $obj; 
    $key = $class . ':' . $invocation->getMethod()->name; 

    $result = $this->cache->get($key); 
    if ($result === false) { 
     $result = $invocation->proceed(); 
     $this->cache->set($key, $result); 
    } 

    return $result; 
    } 
} 

większy sens dla mnie, ponieważ dostarcza w solidną sposób realizacji. Nie jestem wielkim fanem wprowadzania tego samego z adnotacjami, wolałbym coś prostszego.

Powiązane problemy