在策略模式中设置策略

Setting the strategy in a Strategy Pattern

我可能实施这个错误,因为我无法找到一种可靠的方法来设置在我实施战略模式时使用哪种战略。我不太喜欢写它"statically",也许还有另一种方式。

背景故事: 我已经对货运提供商进行了两 (2) 次实施(soap + http),以便检索用户输入前端的跟踪信息。它们每个都遵循一个界面,以便我知道哪些功能是并且应该 (PHP :3) 可用。我缩短了下面的 class 名称,因为这是 Magento 并且 class 名称很长。

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

我的挑战: 我正在使用 switch case 来设置 tracking/service_gls 和 tracking/service_otherservice,然后调用 getDeliveryInformation()。这是正确的方法吗?如果有人想连接另一个运输提供商,我觉得它有点过于静态且难以维护。他们将不得不进入控制器并手动向 switch case 添加另一个条目,在 class.

中某处 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 处理来自运输模型的请求数据

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;在这种情况下我有两个

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

好吧,您可以通过开关或某种字符串连接来实现 return 您需要的策略 class。

使用策略模式,在 运行 时间选择正确的策略通常是通过 StrategyContext 模式完成的:https://sourcemaking.com/design_patterns/strategy/php。这允许您隔离算法以选择正确的策略,因此它不是 "in a function somewhere 200 lines deep in the class." .

至于设置 运行 时间策略的算法,我个人更喜欢 class 常量而不是字符串操作等。因为游戏的目标是达到 class 要实例化的名称,为什么不只是一个 class 常量到 return class 名称。

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

轻巧易维护,策略列表classes在一处

你当然可以把整个事情做成静态的,或者使用另一种设计模式,比如抽象工厂方法来实现同样的事情。真的取决于你。

无论如何在控制器中它是单行的

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。

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