php - Setting the strategy in a Strategy Pattern -
i might have implementing wrong cannot figure out solid way of setting strategy use in implementation of strategy pattern. i'm not big fan of writing "statically", perhaps there way.
backstory: i've done 2 (2) implementations (soap + http) shipping providers in order retrieve track & trace information whatever user inputs frontend. each follow interface know functions , should (php :3) available. i've shortened class names below magento , class names long.
flow: customer inputs tracking number in form , submits. request sent controller, controller initializes instance of service class, sets output via. $service->setoutput('tracking/service_gls') - note tracking/service_gls maps directly service class (magento thing), $service->getdeliveryinformation($number) called (we know exists because of interface), entire $service object returned view , data presented.
my challenge: i'm using switch case set tracking/service_gls , tracking/service_otherservice calling getdeliveryinformation(). correct approach? feel it's bit static , hard maintain if wants connect shipping provider. have enter controller , manually add entry switch case, in function somewhere 200 lines deep in class.
example of how controller looks:
public function getdeliveryinformationaction() { $id = $this->getrequest()->getparam('id', false); if ($id && $this->getrequest()->isajax()) { // note: service parameter 2 radio buttons values "gls", "otherservice" $servicetype = $this->getrequest()->getparam('service', false); try { // note: same doing new class() $service = mage::getmodel('tracking/service'); switch ($servicetype) { case 'gls': $service->setoutput('tracking/service_gls'); break; case 'other': $service->setoutput('tracking/service_other'); break; } $shipment = $service->getdeliveryinformation($id); $output = // .. create block contains view, $output contain shipment data; returned ajax request. } catch (exception_requesterror $e) { .. } // $this->getresponse()->setheader('content-type', 'text/html', true); $this->getresponse()->setbody($output); } }
code has been shortened bit there lots more functions, not important.
interface 2 shipping provider models implementing
interface output { /* requests delivery information specified tracking number */ public function getdeliveryinformation($number); /** * returns acceptor name * @return string */ public function getacceptorname(); }
service class handles requesting data shipping models
class service { protected $output; /** * sets output model use * @param string $outputtype */ public function setoutput($outputmodel) { // note: same doing new class() // magento people note: getmodel() works fine tho.. ;-) $modelinstance = mage::app()->getconfig()->getmodelinstance($outputmodel); $this->output = $modelinstance; } /** * returns delivery information specified tracking number * @param string $number * @return instance of output class */ public function getdeliveryinformation($number) { // note: makes shipping class request // information , set data internally on object $this->output->getdeliveryinformation($number); return $this->output; } }
example of shipping class; have 2 in case
class service_gls implements output { const service_name = 'gls'; const service_url = 'http://www.gls-group.eu/276-i-portal-webservice/services/tracking/wsdl/tracking.wsdl'; protected $locale = 'da_dk'; /* class constructor */ public function __construct() { } /** * requests delivery information specified tracking number * @param mixed $number */ public function getdeliveryinformation($number) { $this->_getdeliveryinformation($number); } /** * requests , sets information specified tracking number * @param mixed $number */ private function _getdeliveryinformation($number) { // note: extending varien_object has magic __get, __set .. hence why there no getdata() function in class. if (!count($this->getdata())) { $client = new soapclient($url); $client->gettudetail($reference)); .. set data } } /** * returns acceptor name * @return string */ public function getacceptorname() { $signature = $this->getsignature(); return (isset($signature)) ? $this->getsignature() : false; } /** * returns name of current service * @return string */ public function __tostring() { return self::service_name; } }
controller
class ajaxcontroller extends mage_core_controller_front_action { public function getdeliveryinformationaction() { $id = $this->getrequest()->getparam('id', false); if ($id && $this->getrequest()->isajax()) { // note: service parameter 2 radio buttons values "gls", "otherservice" $servicetype = $this->getrequest()->getparam('service', false); try { $service = mage::getmodel('tracking/service'); switch ($servicetype) { case 'gls': $service->setoutput('tracking/service_gls'); break; case 'other': $service->setoutput('tracking/service_other'); break; } $shipment = $service->getdeliveryinformation($id); $output = // .. create block contains view, $output contain shipment data; returned ajax request. } catch (exception_requesterror $e) { .. } // $this->getresponse()->setheader('content-type', 'text/html', true); $this->getresponse()->setbody($output); } } }
well either switch or sort of string concatenation return strategy class need.
with strategy pattern, choosing correct strategy @ run time done through strategycontext pattern: https://sourcemaking.com/design_patterns/strategy/php . allows isolate algorithm choose correct strategy not "in function somewhere 200 lines deep in class." .
as algorithm setting runtime strategy, fan of class constants rather string manipulation etc. since aim of game arrive @ class name instantiate, why not class constant return class name.
class outputstrategycontext{ const service = 'tracking/service_gls'; const other = 'tracking/service_other'; private $strategy; public function __construct($servicetype) { $strategy = constant('self::' . strtoupper($servicetype)); $modelinstance = mage::app()->getconfig()->getmodelinstance($strategy); $this->strategy = $modelinstance; } public function getstrategy() { return $this->strategy; } }
lightweight , easy maintain, list of strategy classes in 1 place.
you can of course make whole thing static, or use design pattern abstract factory method acheive same thing. really.
anyway in controller one-liner
class ajaxcontroller extends mage_core_controller_front_action { public function getdeliveryinformationaction() { $id = $this->getrequest()->getparam('id', false); if ($id && $this->getrequest()->isajax()) { // note: service parameter 2 radio buttons values "gls", "otherservice" $servicetype = $this->getrequest()->getparam('service', false); try { $service = mage::getmodel('tracking/service'); $outputmodel = new outputstrategycontext($servicetype)->getstrategy(); $service->setoutput($outputmodel); $shipment = $service->getdeliveryinformation($id); $output = // .. create block contains view, $output contain shipment data; returned ajax request. } catch (exception_requesterror $e) { .. } // $this->getresponse()->setheader('content-type', 'text/html', true); $this->getresponse()->setbody($output); } } }
of course have modify service . modified context class code.
class service { protected $output; /** * sets output model use * @param string $outputtype */ public function setoutput($outputmodel) { // note: same doing new class() // magento people note: getmodel() works fine tho.. ;-) $this->output = $outputmodel; } /** * returns delivery information specified tracking number * @param string $number * @return instance of output class */ public function getdeliveryinformation($number) { // note: makes shipping class request // information , set data internally on object $this->output->getdeliveryinformation($number); return $this->output; } }
Comments
Post a Comment