5

ユーザーが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ステートメントを使用せずにこの目標を達成するためのより良い方法はありますか?

ところで:ユーザーは4つの戦略を選択するときに文字列を入力しません。選択ボックスでユーザーに表示する文字列のリストを維持し、新しい戦略オブジェクトを追加するときにリストに新しい文字列を追加する必要があります。

説明
ircmaxellは、私がやろうとしていることに関して少し混乱を示しました。上記の例では、ユーザーはリストから4つの戦略を選択し、それらは文字列の配列としてStrategyManagerコンストラクターに渡されます。対応するストラテジーオブジェクトが作成され、内部配列に格納されます。$this->selectedStrategies

「first」、「second」、「third」、「fourth」は、選択した4つの異なる戦略の内部配列の配列キーです。StrategyManagerオブジェクトが構築された後、アプリケーションはexecute、プロセスの存続期間中のさまざまな瞬間に4つの戦略のメソッドを使用します。

つまり、一言で言えば...アプリケーションがストラテジー番号「1」のメソッドを実行する必要があるたびに実行され、ストラテジー「1」に対してユーザーが選択したストラテジーによって結果が異なります。

4

3 に答える 3

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すると、ストラテジーインターフェイスを実装するクラスのみがマネージャーに含まれるようになります。これにより、戦略を作成するときに、いくらか高価なReflection呼び出しを節約できます。はSplFixedArray、4つを超える戦略を追加しようとしたときに、ランタイム例外が発生することを確認します。


ちなみに、選択ボックスからの入力を信頼しないでください。選択ボックスが固定オプションを提供するからといって、悪意のあるユーザーがリクエストをいじることができないという意味ではありません。すべてのリクエストデータはサニタイズして再確認する必要があります。

于 2010-10-18T17:22:44.260 に答える
1

あなたのコメントと更新に基づいて、私はこのコードがあまりにも脆いとは思わない。ストラテジータイプ(do_one、do_twoなど)のコールチェーンを変更するか、ストラテジーを追加すると、保守が難しくなります。代わりに、抽象ファクトリを使用して「戦略」を作成することをお勧めします。次に、ストラテジーが必要なコードで、ストラテジーオブジェクト自体をフェッチします...

私がこの方法を好む理由は2つあります。まず、オンデマンドで戦略を作成するだけなので、不要なオブジェクトを作成することはありません。次に、ユーザーの選択をカプセル化します。これは、それを検索する必要がある唯一の場所だからです(依存性注入を使用して構築できますが、建物を管理するために別の場所が必要になります)。

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();
于 2010-10-18T17:24:51.433 に答える
0

ストラテジー関数が状態を必要としない場合は、プログラミングの機能スタイルに切り替えて、クラス全体を次のように置き換えることができますcall_user_func($strategy['first']);(2番目など)。グローバル名前空間が心配な場合は、それらの関数をクラスの静的メンバーとして保存できます。つまり、を使用して(call_user_func(array('Strategies', $strategy['first']));選択ボックスの生成とテストのための)すべての有効な戦略のリストを取得できますget_class_methods('Strategies');。これにより、コードを1つだけにすることでコードを簡素化できます。有効な戦略のグローバルリスト。

ストラテジー関数で保存された状態が必要な場合-私はある種のキャッシング呼び出し関数を使用するかもしれません-のようなもの

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。

「文字列型」の形容詞は、弱い型であり、すでに内部的に文字列を使用してシンボル(クラス名と関数名、変数など)を格納しているため、PHPには適用できません。そのため、リフレクションの場合、文字列データ型が最適であることがよくあります。それが言語全体にとって何を意味するのかについては説明しません。

于 2010-10-18T17:54:33.133 に答える