0

私にはうまく機能するパターンがいくつかありますが、仲間のプログラマーに説明するのが少し難しいです。根拠や参考文献を探しています。

私は個人的に PHP を扱っていますが、これは Java、Javascript、C++、および同様の言語にも適用できます。例は PHP または疑似コードになります。

アイデアは、中間結果に遅延評価コンテナーを使用して、同じ中間値の複数の計算を回避することです。

「動的プログラミング」:

http://en.wikipedia.org/wiki/Dynamic_programming

動的計画法のアプローチでは、各部分問題を 1 回だけ解こうとするため、計算回数が削減されます。特定の部分問題の解が計算されると、それは保存または「メモ化」されます。次に同じ解が必要になったときに、単に見上げられる

遅延評価コンテナー:

class LazyEvaluationContainer {

  protected $values = array();

  function get($key) {
    if (isset($this->values[$key])) {
      return $this->values[$key];
    }
    if (method_exists($this, $key)) {
      return $this->values[$key] = $this->$key();
    }
    throw new Exception("Key $key not supported.");
  }

  protected function foo() {
    // Make sure that bar() runs only once.
    return $this->get('bar') + $this->get('bar');
  }

  protected function bar() {
    .. // expensive computation.
  }
}

同様のコンテナーが、依存性注入コンテナー (DIC) などとして使用されます。

詳細

私は通常、これのいくつかのバリエーションを使用します。

  • データ計算メソッドとは異なるオブジェクトに実際のデータメソッドを含めることは可能ですか?
  • ネストされた配列を持つキャッシュを使用して、パラメーターを持つ計算メソッドを持つことは可能ですか?
  • PHP では、メインの検索メソッドにマジック メソッド (__get() または __call()) を使用できます。クラス docblock の "@property" と組み合わせると、各 "仮想" プロパティの型ヒントが可能になります。
  • 通常のメソッドと区別するために、「get_someValue()」のようなメソッド名をよく使用します。「someValue」は実際のキーです。
  • データ計算を複数のオブジェクトに分散して、ある種の関心を分離することは可能ですか?
  • 一部の値を事前に初期化することは可能ですか?

編集:質問

Spring @Configuration クラスのかわいいメカニックについて話している素敵な答えがすでにあります。

これをより便利で興味深いものにするために、質問を少し拡張/明確化します。

  • 動的計画法から中間値を保存することは、これの正当な使用例ですか?
  • これを PHP で実装するためのベスト プラクティスは何ですか? 「詳細」の一部は悪くて醜いですか?
4

2 に答える 2

1

私があなたを正しく理解していれば、これは非常に標準的な手順ですが、あなたが正しく認めているように、DI (またはアプリケーションのブートストラップ) に関連しています。

@Configuration具体的で標準的な例は、遅延 Bean 定義を持つ任意の Springクラスです。あなたが説明したのとまったく同じ動作を表示すると思いますが、それを達成する実際のコードはビューから隠されています(そして舞台裏で生成されています)。実際の Java コードは次のようになります。

@Configuration
public class Whatever {

  @Bean @Lazy
  public OneThing createOneThing() {
    return new OneThing();
  }

  @Bean @Lazy
  public SomeOtherThing createSomeOtherThing() {
    return new SomeOtherThing();
  }

  // here the magic begins:

  @Bean @Lazy
  public SomeThirdThing getSomeThirdThing() {
    return new SomeThirdThing(this.createOneThing(), this.createOneThing(), this.createOneThing(), createSomeOtherThing());
  }
}

でマークされた各メソッドは、@Bean @Lazy一度必要になった (そしてメソッドが呼び出された) ときに作成される 1 つの「リソース」を表します。ロード中に実際のコードを変更する魔法)。createOneThing()そのため、 inは で 2 回呼び出されているように見えますがcreateOneThing()、呼び出しは 1 回だけ発生します (それは、誰かが呼び出しを試みた後、createSomeThirdThing()または を呼び出した後でのみ発生getBean(SomeThirdThing.class)しますApplicationContext)。

于 2013-10-30T23:29:23.570 に答える
1

すべてに対して普遍的な遅延評価コンテナーを持つことはできないと思います。

まず、あなたが本当に持っているものについて話し合いましょう。怠惰な評価ではないと思います。遅延評価は、値が実際に必要とされるポイントまで評価を遅らせ、その値に対するさらなる要求で既に評価された値を共有することとして定義されます。

私の頭に浮かぶ典型的な例は、データベース接続です。必要なときにその接続を使用できるようにすべてを準備しますが、実際にデータベースクエリが必要な場合にのみ、接続が作成され、後続のクエリと共有されます。

典型的な実装では、接続文字列をコンストラクターに渡し、それを内部に格納します。クエリ メソッドが呼び出されると、最初に接続ハンドルを返すメソッドが呼び出され、そのハンドルが作成され、接続と共に保存されます。存在しない場合は文字列。そのオブジェクトを後で呼び出すと、既存の接続が再利用されます。

このようなデータベース オブジェクトは、データベース接続の遅延評価に適しています。本当に必要な場合にのみ作成され、他のすべてのクエリで共有されます。

あなたの実装を見ると、「本当に必要な場合にのみ評価する」という資格はなく、一度作成された値のみが保存されます。したがって、実際にはある種のキャッシュにすぎません。

また、高価な計算をグローバルに一度だけ評価するという問題を実際に解決することはできません。インスタンスが 2 つある場合は、高価な関数を 2 回実行します。しかし一方で、それを 2 回評価しないと、グローバルな状態が導入されます。これは、明示的に宣言されていない限り、悪いことと見なされるべきです。通常、コードを適切にテストすることが非常に難しくなります。個人的にはそれは避けたいです。

データ計算メソッドとは異なるオブジェクトに実際のデータメソッドを含めることは可能ですか?

Zend Framework がキャッシュ パターン ( ) をどのように提供しているか\Zend\Cache\Pattern\{Callback,Class,Object}Cacheを見ると、実際の作業クラスがデコレーターをラップして取得していることがわかります。保存された値を取得して読み戻すためのすべての内部処理は内部で処理され、外部からは以前と同じようにメソッドを呼び出します。

欠点は、元のクラスの型のオブジェクトがないことです。したがって、型ヒントを使用する場合、元のオブジェクトの代わりに装飾されたキャッシュ オブジェクトを渡すことはできません。解決策は、インターフェイスを実装することです。元のクラスは実際の関数を使用してそれを実装し、キャッシュ デコレーターを拡張してインターフェイスも実装する別のクラスを作成します。このオブジェクトはタイプ ヒンティング チェックに合格しますが、すべてのインターフェイス メソッドを手動で実装する必要があります。これらのメソッドは、そうでなければインターセプトされる内部マジック関数への呼び出しを渡すだけです。

interface Foo
{
    public function foo();
}

class FooExpensive implements Foo
{
    public function foo()
    {
        sleep(100);
        return "bar";
    }
}

class FooCached extends \Zend\Cache\Pattern\ObjectPattern implements Foo
{
    public function foo()
    {
        //internally uses instance of FooExpensive to calculate once
        $args = func_get_args();
        return $this->call(__FUNCTION__, $args); 
    }
}

PHP では、少なくともこれら 2 つのクラスと 1 つのインターフェイスなしでキャッシュを実装することは不可能であることがわかりました (ただし、一方で、インターフェイスに対して実装することは良いことであり、気にする必要はありません)。ネイティブ キャッシュ オブジェクトをそのまま直接使用することはできません。

ネストされた配列を持つキャッシュを使用して、パラメーターを持つ計算メソッドを持つことは可能ですか?

パラメータは上記の実装で機能し、キャッシュ キーの内部生成に使用されます。おそらく、\Zend\Cache\Pattern\CallbackCache::generateCallbackKeyメソッドを確認する必要があります。

PHP では、メインの検索メソッドにマジック メソッド (__get() または __call()) を使用できます。クラス docblock の "@property" と組み合わせると、各 "仮想" プロパティの型ヒントが可能になります。

魔法の方法は悪です。ドキュメント ブロックは、実際に動作するコードではないため、時代遅れであると見なす必要があります。のように任意のプロパティに任意の値を格納できる、非常にわかりやすい値オブジェクト コードでマジック ゲッターとセッターを使用することは許容できると思いましたが、 .stdClass__call

通常のメソッドと区別するために、「get_someValue()」のようなメソッド名をよく使用します。「someValue」は実際のキーです。

これは PSR-1 の違反であると考えます: 「4.3. メソッド: メソッド名は で宣言する必要がありcamelCase()ます。」そして、これらのメソッドを特別なものとしてマークする理由はありますか? 彼らはまったく特別ですか?は値を返しますね。

データ計算を複数のオブジェクトに分散して、ある種の関心を分離することは可能ですか?

オブジェクトの複雑な構造をキャッシュする場合、これは完全に可能です。

一部の値を事前に初期化することは可能ですか?

これはキャッシュの問題ではなく、実装自体の問題です。高価な計算を実行するのではなく、プリセット値を返すことのポイントは何ですか? それが実際のユースケースである場合 (パラメーターが定義された範囲外にある場合に即座に NULL を返すなど)、実装自体の一部である必要があります。このような場合に値を返すために、オブジェクトの周囲に追加のレイヤーを当てにしないでください。

動的計画法から中間値を保存することは、これの正当な使用例ですか?

動的計画法の問題がありますか? リンクしたウィキペディアのページに次の文があります。

動的計画法を適用するには、問題が持つ必要がある 2 つの重要な属性があります。それは、最適部分構造と重複部分問題です。重複しない部分問題に最適解を組み合わせることで問題を解決できる場合、その戦略は代わりに「分割統治」と呼ばれます。

あなたの例の遅延評価部分を解決するように見える既存のパターンが既にあると思います:Singleton、ServiceLocator、Factory。(私はここでシングルトンを宣伝しているわけではありません!)

「プロミス」の概念もあります。要求された場合に後で実際の値を返すことを約束するオブジェクトが返されますが、値が現在必要でない限り、代わりに渡すことができる値の置換として機能します。このブログ投稿を読むことをお勧めします: http://blog.ircmaxell.com/2013/01/promise-for-clean-code.html

これを PHP で実装するためのベスト プラクティスは何ですか? 「詳細」の一部は悪くて醜いですか?

おそらくフィボナッチの例に近い例を使用しました。この例で気に入らない点は、単一のインスタンスを使用してすべての値を収集することです。ある意味では、ここでグローバルな状態を集約しています。これがおそらく、この概念全体の目的です。しかし、グローバルな状態は悪であり、私はその余分なレイヤーが好きではありません. そして、パラメータの問題を十分に解決していません。

bar()insideへの呼び出しが実際に 2 つあるのはなぜだろうかfoo()。より明白な方法は、結果を で直接複製しfoo()、それを「追加」することです。

全体として、私は今まであまり感銘を受けていません。この単純なレベルで、このような汎用ソリューションの実際の使用例を予測することはできません。私は IDE の自動提案サポートが好きで、ダックタイピング (互換性をシミュレートするだけで、インスタンスを保証できないオブジェクトを渡す) は好きではありません。

于 2013-11-06T22:24:11.283 に答える