代码之家  ›  专栏  ›  技术社区  ›  Stephen

这个密码太脆弱了吗?

  •  5
  • Stephen  · 技术社区  · 14 年前

    我需要创建一个策略模式,用户可以从20个或30个惟一的策略对象列表中选择4个策略。策略列表将随着项目的成熟而扩展,用户可以随时更改他们选择的策略。

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

    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语句的情况下实现此目标?

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

    “第一”、“第二”、“第三”和“第四”是四种不同选择策略的内部数组的数组键。在构建StrategyManager对象之后,应用程序使用 execute 四种策略的方法,在过程生命周期的不同时刻。

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

    3 回复  |  直到 8 年前
        1
  •  1
  •   ircmaxell    14 年前

    根据您的评论和更新,我认为这段代码不会太脆弱。如果你改变一个策略类型的调用链(dou1,dou2,等等)或者添加策略,那么维护起来会更困难。我建议使用 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
  •  2
  •   Gordon Haim Evgi    14 年前

    嗯,好吧,我觉得它不太脆。不过,你不需要绳子。您可以简单地使用有序数组,因为命名无论如何都对应于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


    另一方面,不要相信selectbox的输入。仅仅因为selectbox提供了固定的选项,并不意味着恶意用户无法修补请求。必须对所有请求数据进行清理和双重检查。

        3
  •  0
  •   Iiridayn    14 年前

    如果策略函数不需要状态,则可以切换到函数式编程,并将整个类替换为: 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,因为它是弱类型的,并且已经在内部使用字符串存储符号(类和函数名、变量等)。因此,对于反射,字符串数据类型通常是最适合的。我们不会讨论这对整个语言意味着什么。