5

我需要创建一个策略模式,其中用户从二十或三十个独特策略对象的列表中选择四个策略。策略列表将随着项目的成熟而扩展,用户可以随时更改他们选择的策略。

我打算将他们选择的策略名称存储为字符串,然后使用类似这样的方法来加载与他们选择的字符串对应的策略类。

class StrategyManager { // simplified for the example
    public $selectedStrategies = array();
    public function __construct($userStrategies) {
        $this->selectedStrategies = array(
            'first'  => new $userStrategies['first'],
            'second' => new $userStrategies['second'],
            'third'  => new $userStrategies['third'],
            'fourth' => new $userStrategies['fourth']
        );
    }

    public function do_first() {
        $this->selectedStrategies['first']->execute();
    }

    public function do_second() {
        $this->selectedStrategies['second']->execute();
    }

    public function do_third() {
        $this->selectedStrategies['third']->execute();
    }

    public function do_fourth() {
        $this->selectedStrategies['fourth']->execute();
    }
}

我试图避免一个大的 switch 语句。我担心的是,这似乎有点Stringly Typed。有没有更好的方法来实现这个目标而不使用条件或大型 switch 语句?

BTW:用户在选择四种策略时没有输入字符串。我需要维护一个字符串列表以在选择框中呈现给用户,并在添加新策略对象时将新字符串添加到列表中。

解释
ircmaxell对我正在尝试做的事情表示了一些困惑。在上面的例子中,用户从一个列表中选择了四个策略,并将它们作为字符串数组传递给 StrategyManager 构造函数。相应的策略对象被创建并存储在一个内部数组中,$this->selectedStrategies

“first”、“second”、“third”和“fourth”是四种不同选择策略的内部数组的数组键。建立 StrategyManager 对象后,应用程序execute在流程生命周期的不同时刻使用四种策略的方法。

所以,简而言之......每次应用程序需要执行策略编号“一”的方法时,它都会执行此操作,并且结果会根据用户为策略“一”选择的策略而有所不同

4

3 回答 3

2

嗯,嗯,我不认为它太脆。你不需要字符串。您可以简单地使用有序数组,因为无论如何命名都对应于 0,1,2,3。如果您担心提供的策略或类无效,您可以在管理器中进行一些验证。

public function __construct() {
    $this->selectedStrategies = array(
        /* could add some default strategies */
    );
}
public function load(array $userStrategies) {
    for( $i=0; $i<3; $i++ ) {
        try {
            $rc = new ReflectionClass($userStrategies[$i]);
            if( $rc->implementsInterface('Iterator') ) {
                $this->selectedStrategies[$i] = new $userStrategies[$i];
            } else {
                throw new InvalidArgumentException('Not a Strategy');
            }
        } catch(ReflectionException $e) {
            throw new InvalidArgumentException('Not a Class');
        }
    }
}

而不是使用关联键调用策略,您只需

$this->selectedStrategies[0]->execute();

等等。


另一种方法是使用

class StrategyCollection
{
    protected $strategies;

    public function __construct() {
        $this->strategies = new SplFixedArray(4);
    }

    public function add(IStrategy $strategy) {
        $this->strategies[] = $strategy;
        return $this;
    }
}

然后从外部填充Manager/Collection。使用 typehintIStrategy可以确保只有实现策略接口的类最终出现在管理器中。这可以在创建策略时为您节省一些昂贵的反射调用。当SplFixedArray您尝试添加四个以上的策略时,确保存在运行时异常。


在旁注中,不要相信来自选择框的输入。仅仅因为选择框提供了固定选项,并不意味着恶意用户无法修改请求。所有请求数据都必须经过清理和仔细检查。

于 2010-10-18T17:22:44.260 回答
1

根据您的评论和更新,我不认为这段代码太脆弱。如果您更改策略类型(do_one、do_two 等)的调用链或添加策略,将更难维护。相反,我建议使用抽象工厂来创建“策略”。然后,在您需要策略的代码中,获取策略对象本身......

我更喜欢这种方法的原因有两个。首先,它只根据需要创建策略,因此您不会构建不需要的对象。其次,它封装了用户的选择,因为这是唯一需要查找它的地方(您可以使用依赖注入来构建它,但您也需要其他地方来管理构建)。

class StrategyFactory {

    protected $strategies = array();

    //If you like getter syntax
    public function __call($method, $arguments) {
        $method = strtolower($method);
        if (substr($method, 0, 3) == 'get') {
            $strategy = substr($method, 3);
            return $this->getStrategy($strategy);
        }
        throw new BadMethodCallException('Unknown Method Called');
    }

    public function getStrategy($strategy) {
        if (isset($this->strategies[$strategy])) {
            return $this->strategies[$strategy];
        } elseif ($this->makeStrategy($strategy)) {
            return $this->strategies[$strategy];
        }
        throw new LogicException('Could not create requested strategy');
    }

    protected function makeStrategy($name) {
        //pick strategy from user input
        if ($strategyFound) {
            $this->strategies[$name] = new $strategy();
            return true;
        } else {
            return false;
        }
    }
}

然后,像这样使用:

$strategy = $factory->getSomeStrategyName();
$strategy->execute();

甚至改变:

$factory->getSomeStrategyName()->execute();

或者没有魔法方法:

$factory->getStrategy('strategyName')->execute();
于 2010-10-18T17:24:51.433 回答
0

如果策略函数不需要状态,您可以切换到函数式编程并将整个类替换为:(call_user_func($strategy['first']);第二个等)。如果关注全局命名空间,它们的函数可以存储为类的静态成员 - 即,call_user_func(array('Strategies', $strategy['first']));您可以使用 获取所有有效策略(用于生成和测试选择框)的列表get_class_methods('Strategies');,这可以通过只有一个来简化代码有效策略的全局列表。

如果您确实需要使用策略函数存储状态 - 我可能会使用某种缓存调用函数 - 例如

function doStrategy($class) {
    static $selectedStrategies = array();

    if (!isset($selectedStrategies[$class])) {
        $selectedStrategies[$class] = new $class;
    }

    $Strategy = $selectedStrategies[$class];
    $Strategy->execute(); // some versions of PHP require two lines here
}

当然,您仍然可以在函数上使用类来执行此操作:P。

“Stringly Typed”这一称号不适用于 PHP,因为它既是弱类型,又在内部已经使用字符串来存储符号(类和函数名、变量等)。因此,对于反射,字符串数据类型通常是最合适的。我们不会深入探讨这对整个语言意味着什么。

于 2010-10-18T17:54:33.133 回答