2010-10-18 60 views
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。有没有更好的方法来实现这个目标,而不使用条件或大型开关语句?

顺便说一句:用户在选择四种策略时不输入字符串。我需要维护一个字符串列表,以便在选择框中显示给用户,并在添加新策略对象时将新列表添加到列表中。

说明
ircmaxell表示关于它是什么我试图做一些混乱。在上面的例子中,用户从列表中选择了四个策略,并且它们以字符串数组的形式传递给了StrategyManager构造函数。相应的策略对象被创建并存储在内部阵列中,“first”,“second”,“third”和“fourth”是四种不同选定策略的内部阵列的数组键。 StrategyManager对象构建完成后,应用程序会在流程的整个生命周期的各个时刻使用四种策略的execute方法。

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

+0

我很困惑。是“第一个”,“第二个”,“第三个”和“第四个”不同的可能策略,或者它们是选定策略的一系列命令(在构建管理器之前选择)。如果是这样,[责任链](http://sourcemaking.com/design_patterns/chain_of_responsibility)或[命令](http://sourcemaking.com/design_patterns/command)模式会更好地工作吗?你能解释一下你究竟在做什么(以及代码的作用,为什么存在不同的策略)? – ircmaxell 2010-10-18 16:47:00

+0

我会更新问题。 – Stephen 2010-10-18 16:48:35

+0

总是有4个策略?他们是否总是按顺序执行?或者,他们是四种不同的策略,你只是试图一起管理? – ircmaxell 2010-10-18 17:02:55

回答

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

我喜欢你在这里提供的工厂实现。谢谢! – Stephen 2010-10-18 19:38:52

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的类型提示,您可以确定只有实施策略接口的类最终在管理器中。这在创建策略时可以为您节省一些昂贵的反射电话。 SplFixedArray确保在尝试添加四个以上策略时存在运行时异常。


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

+1

对于sidenote和StrategyCollection类来说是+1,它很简单并且很好地构建。 – Iiridayn 2010-10-18 18:00:35

+1

SplFixedArray +1。尼斯。 – Stephen 2010-10-18 19:38:34

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,因为它既是弱类型的,也是内部使用字符串来存储符号(类和函数名称,变量等)的。因此,对于反射来说,字符串数据类型通常是最合适的。我们不会理解整个语言的含义。