在战略模式中设定战略

时间:2015-04-26 08:39:17

标签: php design-patterns refactoring

我可能已经实现了这个错误,因为我无法找到一种可靠的方法来设置在我的策略模式实现中使用哪种策略。我不是“静态地”写它的忠实粉丝,也许还有另一种方式。

背景故事:我已经为运输提供商完成了两(2)次实施(soap + http),以便检索Track&跟踪用户输入前端的信息。它们各自遵循一个接口,以便我知道哪些功能应该(PHP:3)可用。我缩短了下面的类名,因为这是Magento,类名很长。

流程:客户在表单中输入跟踪编号并提交。请求被发送到控制器,控制器初始化Service类的实例,设置输出via。 $ service-> setOutput('tracking / service_gls') - 请注意,tracking / service_gls只是直接映射到服务类(Magento的东西),$ service-> getDeliveryInformation($ number)被调用(我们知道这是因为存在接口),整个$ service对象返回到视图并显示数据。

我的挑战:我正在使用switch case来设置tracking / service_gls和tracking / service_otherservice,然后调用getDeliveryInformation()。这是正确的方法吗?如果有人想要连接其他运输提供商,我觉得这有点太静态而且难以维护。他们必须进入控制器并手动将另一个条目添加到开关盒中,在类中200行深处的函数中。

控制器的外观示例:

public function getDeliveryInformationAction()
{
    $id = $this->getRequest()->getParam('id', false);
    if ($id && $this->getRequest()->isAjax())
    {
        // SO note: service parameter is just two radio buttons with values "gls", "otherservice"
        $serviceType = $this->getRequest()->getParam('service', false);

        try
        {
            // SO note: same as 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 that contains the view, $output will contain the shipment data; this is returned to the ajax request.
        }
        catch (Exception_RequestError $e)
        {
            ..
        }

        // finally
        $this->getResponse()->setHeader('content-type', 'text/html', true);
        $this->getResponse()->setBody($output);
    }
}

代码已经缩短了一点,因为功能更多,但并不重要。

接口两个送货供应商模型正在实施

interface Output
{
    /* Requests delivery information for the specified tracking number */
    public function getDeliveryInformation($number);

    /**
    * Returns acceptor name
    * @return string
    */
    public function getAcceptorName();
}

服务类处理从发货模式中请求数据

class Service
{
    protected $output;


    /**
     * Sets the output model to use
     * @param string $outputType
     */
    public function setOutput($outputModel)
    {
        // SO note: same as doing new Class()
        // Magento people note: getModel() works fine tho.. ;-)
        $modelInstance = Mage::app()->getConfig()->getModelInstance($outputModel);
        $this->output = $modelInstance;
    }

    /**
     * Returns delivery information for the specified tracking number
     * @param string $number
     * @return instance of output class
     */
    public function getDeliveryInformation($number)
    {
        // SO note: This makes the shipping class request
        // information and set data internally on the object
        $this->output->getDeliveryInformation($number);
        return $this->output;
    }
}

运输类的示例;在这种情况下我有两个

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 for the specified tracking number
     * @param mixed $number
     */
    public function getDeliveryInformation($number)
    {
        $this->_getDeliveryInformation($number);
    }

    /**
     * Requests and sets information for the specified tracking number
     * @param mixed $number
     */
    private function _getDeliveryInformation($number)
    {
        // SO note: Extending from Varien_Object has magic __get, __set .. hence why there is no getData() function in this 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 the name of the current service
     * @return string
     */
    public function __toString()
    {
        return self::SERVICE_NAME;
    }
}

控制器

class AjaxController extends Mage_Core_Controller_Front_Action
{
    public function getDeliveryInformationAction()
    {
        $id = $this->getRequest()->getParam('id', false);
        if ($id && $this->getRequest()->isAjax())
        {
            // SO note: service parameter is just two radio buttons with 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 that contains the view, $output will contain the shipment data; this is returned to the ajax request.
            }
            catch (Exception_RequestError $e)
            {
                ..
            }

            // finally
            $this->getResponse()->setHeader('content-type', 'text/html', true);
            $this->getResponse()->setBody($output);
        }
    }
}

1 个答案:

答案 0 :(得分:1)

嗯,你要么用一个开关,要么用某种字符串连接来返回你需要的策略类。

使用策略模式,在运行时选择正确的策略通常通过StrategyContext模式完成:https://sourcemaking.com/design_patterns/strategy/php。这允许您隔离算法以选择正确的策略,因此它不是“在类中200行深处的某个函数中”。

至于设置运行时策略的算法,个人我是类常量的粉丝,而不是字符串操作等。由于游戏的目的是获得一个类名实例化,为什么不只是一个类常量到返回班级名称。

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

轻量级且易于维护,策略类列表在一个地方。

你当然可以使整个事物变得静止,或者使用像抽象工厂方法这样的另一种设计模式来实现同样的事情。由你决定。

无论如何在控制器中它是一个单线

class AjaxController extends Mage_Core_Controller_Front_Action
{
    public function getDeliveryInformationAction()
    {
        $id = $this->getRequest()->getParam('id', false);
        if ($id && $this->getRequest()->isAjax())
        {
            // SO note: service parameter is just two radio buttons with 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 that contains the view, $output will contain the shipment data; this is returned to the ajax request.
            }
            catch (Exception_RequestError $e)
            {
                ..
            }

            // finally
            $this->getResponse()->setHeader('content-type', 'text/html', true);
            $this->getResponse()->setBody($output);
        }
    }
}

当然你必须修改服务。我还修改了代码的上下文类。

class Service
{
    protected $output;


    /**
     * Sets the output model to use
     * @param string $outputType
     */
    public function setOutput($outputModel)
    {
        // SO note: same as doing new Class()
        // Magento people note: getModel() works fine tho.. ;-)
        $this->output = $outputModel;
    }

    /**
     * Returns delivery information for the specified tracking number
     * @param string $number
     * @return instance of output class
     */
    public function getDeliveryInformation($number)
    {
        // SO note: This makes the shipping class request
        // information and set data internally on the object
        $this->output->getDeliveryInformation($number);
        return $this->output;
    }
}