我需要创建一个策略模式,用户从二十或三十个唯一策略对象的列表中选择四个策略。随着项目的成熟,策略列表将会扩展,用户可以随时更改所选策略。
我计划将他们选择的策略名称存储为字符串,然后使用类似这样的方法加载与他们选择的字符串相对应的策略类。
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>
答案 0 :(得分:2)
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,因为它既有弱类型,也已在内部使用字符串来存储符号(类和函数名,变量等)。因此,对于反射,字符串数据类型通常是最合适的。我们不会深入研究整个语言的含义。