这段代码太脆了吗?

时间:2010-10-18 16:34:59

标签: php string strategy-pattern

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

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

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”和“4th”是四种不同选择策略的内部数组的数组键。在构建StrategyManager对象之后,应用程序在整个过程的生命周期中的各个时刻使用四种策略的execute方法。

因此,简而言之......每当应用程序需要执行策略编号“one”的方法时,它就会这样做,并且结果会有所不同,具体取决于用户为策略“one”选择的策略< / p>

3 个答案:

答案 0 :(得分: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;
    }
}

然后从外面填充经理/收藏。使用IStrategy的typehint,您可以确定只有实现策略接口的类才会在管理器中结束。在创建策略时,这可以节省一些昂贵的Reflection调用。当您尝试添加四个以上的策略时,SplFixedArray会确保存在运行时异常。


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

答案 1 :(得分:1)

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

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

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

甚至是chaning:

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

或者没有魔术方法:

$factory->getStrategy('strategyName')->execute();

答案 2 :(得分: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,因为它既有弱类型,也已在内部使用字符串来存储符号(类和函数名,变量等)。因此,对于反射,字符串数据类型通常是最合适的。我们不会深入研究整个语言的含义。