Этот код слишком хрупкий? - PullRequest
5 голосов
/ 18 октября 2010

Мне нужно создать шаблон стратегии, в котором пользователь выбирает четыре стратегии из списка из двадцати или тридцати уникальных объектов стратегии.Список стратегий будет расширяться по мере взросления проекта, и пользователи могут изменить выбранную стратегию в любое время.

Я планирую сохранить названия стратегий, которые они выбрали, в виде строк, а затем использовать такой метод, как этотчтобы загрузить классы стратегий, которые соответствуют выбранным строкам.

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

«first», «second», «third» и «четвертый» являются ключами массива внутреннего массива для четырех различных выбранныхстратегии.После создания объекта StrategyManager приложение использует метод execute из четырех стратегий в разные моменты времени процесса.

Итак, в двух словах ... каждый раз, когда приложению требуетсявыполнить метод Стратегии номер «один», он это делает, и результаты различаются в зависимости от того, какая стратегия была выбрана пользователем для Стратегии «один»

Ответы [ 3 ]

2 голосов
/ 18 октября 2010

Хм, ну, я не думаю, что это слишком хрупко. Вам не нужны строки, хотя. Вы можете просто использовать упорядоченный массив, так как наименование в любом случае соответствует 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;
    }
}

, а затем заполните диспетчер / коллекцию снаружи. С помощью typehint для IStrategy вы можете быть уверены, что только классы, реализующие интерфейс стратегии, окажутся в менеджере. Это спасает вас от некоторых дорогих вызовов Reflection при создании стратегий. SplFixedArray обеспечивает исключение времени выполнения при попытке добавить более четырех стратегий.


В sidenote не доверяйте вводу из окна выбора. Тот факт, что поле выбора предоставляет фиксированные параметры, не означает, что злоумышленники не могут возиться с запросом. Все данные запроса должны быть обработаны и проверены дважды.

1 голос
/ 18 октября 2010

Судя по вашим комментариям и обновлениям, я не думаю, что этот код слишком хрупкий. Поддерживать его будет сложнее, если вы измените цепочку вызовов для типа стратегии (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();
0 голосов
/ 18 октября 2010

Если функции стратегии не нуждаются в состоянии, вы можете переключиться на функциональный стиль программирования и заменить весь класс на: 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, так как он слабо типизирован и уже использует строки для хранения символов.(имена классов и функций, переменные и т. д.).Таким образом, для отражения строковый тип данных часто лучше всего подходит.Мы не будем вдаваться в то, что это значит для языка в целом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...