php - Designing an object rendering system -


i wrote code consists of set of objects implement simple interface. these objects plain dto's. need rendered. each 1 requires it's own renderer. think ok there renderer interface has 1 method render , accept result resultinterface. each result item has different pieces of data need rendered.

so happens each renderer checks receives correct type. although seems accepts implementing resultinterface doesn't. think, why bother type hinting on resultinterface.

here few examples illustrate:

<?php  interface rendererinterface {     public function render(resultinterface $result); }   class exceptionfailurerenderer implements rendererinterface {     public function render(resultinterface $result)     {         if (!$result instanceof exceptionfailure) {              throw new invalidargumentexception;         }     } }  class someotherfailurerenderer implements rendererinterface {     public function render(resultinterface $result)     {         if (!$result instanceof someotherfailure) {              throw new invalidargumentexception;         }     } }  interface resultinterface {     public function getname(); }  class exceptionfailure implements resultinterface {     public function getname()     {         return 'exception failure';     }      public function getexception()     {         return $this->exception;     } }  class someotherfailure implements resultinterface {     public function getname()     {         return 'some other failure';     }      public function getsomeotherpieceofdata()     {         return $this->importantdata;     } }  $renderers = [     exceptionfailure::class => new exceptionfailurerenderer,     someotherfailure::class => new someotherfailurerenderer ];  $output = ''; foreach ($results $result) {     $renderer = $renderers[get_class($result)];     $output  .= $renderers->render($result); } 

question

how designed better avoid calling concrete methods when renderer expects resultinterface ?

you quite right there design flaw here. having object implements interface throw exceptions when fulfilling implied contract obvious code smell.

there number of ways refactor this, here how it.

looking @ concrete rendererinterface implementations, obvious tied concrete resultinterface should explicitly declare dependency. (wtf?? see note @ bottom)

as immutable objects easier work with, , there doesnt appear decent reason reuse rendererinterface implementation (it doesnt appear expensive instantiate, or have side effects in doing so) decided alter rendererinterface making render method parameterless, , have concrete result object injected constructor:

interface rendererinterface {     public function render(); }   class exceptionfailurerenderer implements rendererinterface {     private $result;     public function __construct(exceptionfailure $result)     {         $this->result = $result;     }     public function render()     {         echo '<p>'.$this->result->getexception().'</p>';     } }  class someotherfailurerenderer implements rendererinterface {     private $result;     public function __construct(someotherfailure $result)     {         $this->result = $result;     }     public function render()     {         echo '<p>'.$this->result->getsomeotherpieceofdata().'</p>';     } } 

now concrete renderers tied concrete results require, need way correct renderer given concrete result. obvious answer factory:

interface renderfactoryinterface {     /***      * @param resultinterface $result      * @return rendererinterface      */     public function getrenderer(resultinterface $result); }  class renderfactory implements renderfactoryinterface {      //could injected constructor     private $rendermap = [         exceptionfailure::class => exceptionfailurerenderer::class,         someotherfailure::class => someotherfailurerenderer::class      ];       public function getrenderer(resultinterface $result)     {         if(!isset($this->rendermap[get_class($result)])){             //write more suitable exception class             throw new invalidargumentexception();             //or perhaps return defaultrenderer works on             //the methods defined in `resultinterface`         }         return new $this->rendermap[get_class($result)]($result);     } } 

your consuming code require collection of resultinterface objects , implementation of renderfactoryinterface work, having no knowledge of specific types of resultinterface within collection, nor knowledge of how tie resultinterface rendererinterface:

class consumer {     private $results;     private $renderfactory;      /***      * @param resultinterface[] $results php doesnt have typed collections let      * @param renderfactoryinterface $renderfactory      */     public function __construct(array $results, renderfactoryinterface $renderfactory)     {         $this->results = $results;         $this->renderfactory = $renderfactory;     }      public function dosomething()     {         foreach($this->results $result){             $this->renderfactory->getrenderer($result)->render();         }     } } 

note above using php 5, not have method declare return types, php 7 allow more specific getrenderer method should return implementation of rendererinterface. also, consumer code can typehint array, typed collections arrive sometime soon.

also, pre-emptive "typehint concrete class?? wtf thinking" example intended brief possible. of course create exceptionfailureinterface, , someotherfailureinterface, typehint those, , have concrete resultinterface classes implement correct 1 (along resultinterface)if think added flexability worth added complexity. point answer concrete rendererinterface classes require more specific contract resultinterface can offer, concrete class (as shown) or more specific interface


Comments

Popular posts from this blog

get url and add instance to a model with prefilled foreign key :django admin -

css - Make div keyboard-scrollable in jQuery Mobile? -

ruby on rails - Seeing duplicate requests handled with Unicorn -