「実行」イディオムとは何ですか?
-
19-08-2019 - |
質問
これは私が聞いてきたイディオム(または同様の)を「実行する」イディオム(または同様の)とは何ですか?なぜ私はそれを使用するのではないか、そしてなぜそれを使いたくないのでしょうか?
解決
基本的に、これは、常に必要なことを行う方法を作成するパターンです。たとえば、リソースの割り当てやクリーンアップなど、「リソースでやりたいこと」で発信者をパスします。例えば:
public interface InputStreamAction
{
void useStream(InputStream stream) throws IOException;
}
// Somewhere else
public void executeWithFile(String filename, InputStreamAction action)
throws IOException
{
InputStream stream = new FileInputStream(filename);
try {
action.useStream(stream);
} finally {
stream.close();
}
}
// Calling it
executeWithFile("filename.txt", new InputStreamAction()
{
public void useStream(InputStream stream) throws IOException
{
// Code to use the stream goes here
}
});
// Calling it with Java 8 Lambda Expression:
executeWithFile("filename.txt", s -> System.out.println(s.read()));
// Or with Java 8 Method reference:
executeWithFile("filename.txt", ClassName::methodName);
通話コードは、オープン/クリーンアップ側について心配する必要はありません - それは executeWithFile
.
閉鎖は非常に言葉白されていたため、これはJavaで率直に痛みを伴いました。Java8Lambda式から始まる他の多くの言語(C#Lambda式、またはGroovy)のように実装でき、この特別なケースはJava 7以降で処理されます try-with-resources
と AutoClosable
ストリーム。
「割り当てとクリーンアップ」は与えられた典型的な例ですが、他にも多くの可能な例があります - トランザクションの取り扱い、ロギング、より多くの特権のあるコードの実行など。基本的には、基本的には少し似ています テンプレートメソッドパターン しかし、相続なしで。
他のヒント
イディオムの周りの実行は、このようなことをしなければならないことに気付いたときに使用されます。
//... chunk of init/preparation code ...
task A
//... chunk of cleanup/finishing code ...
//... chunk of identical init/preparation code ...
task B
//... chunk of identical cleanup/finishing code ...
//... chunk of identical init/preparation code ...
task C
//... chunk of identical cleanup/finishing code ...
//... and so on.
実際のタスクの周りで常に実行されるこの冗長コードをすべて繰り返すことを避けるために、自動的に世話をするクラスを作成します。
//pseudo-code:
class DoTask()
{
do(task T)
{
// .. chunk of prep code
// execute task T
// .. chunk of cleanup code
}
};
DoTask.do(task A)
DoTask.do(task B)
DoTask.do(task C)
このイディオムは、複雑な冗長コードをすべて1か所に移動し、メインプログラムをより読みやすくします(そして保守可能です!)
an メソッドの周りを実行します 任意のコードをメソッドに渡す場所であり、セットアップおよび/または分解コードを実行し、その間にコードを実行する場合があります。
Javaは、私がこれを行うことを選択した言語ではありません。議論として閉鎖(またはラムダ表現)を渡す方がスタイリッシュです。ただし、オブジェクトは間違いなくありますが 閉鎖に相当します.
executute ounderメソッドはあるようなものであるように思えます 制御の反転 (依存噴射)メソッドを呼び出すたびに、アドホックを変えることができること。
しかし、それはまた、制御結合の例として解釈することもできます(この場合、文字通り、その議論によって何をすべきかを伝える)。
ここにJavaタグがあると思うので、パターンがプラットフォーム固有ではないにもかかわらず、Javaを例として使用します。
アイデアは、コードを実行する前とコードを実行した後、常に同じボイラープレートを含むコードがあることもあります。良い例はJDBCです。実際のクエリを実行して結果セットを処理する前に、常に接続をつかみ、ステートメント(または準備されたステートメント)を作成します。その後、常に同じボイラープレートのクリーンアップを行い、ステートメントと接続をクロージングします。
execute-Aroundのアイデアは、ボイラープレートコードを考慮することができればより良いということです。それにより、いくつかのタイピングを節約しますが、その理由はより深いです。ここでは、それは自分自身ではない(乾燥した)原則ではありません。コードを1つの場所に隔離するので、バグがあるか、変更する必要がある場合、またはそれを理解したい場合は、すべて1か所にあります。
しかし、この種のFactoring-Outで少し厄介なことは、「前」と「後」の両方の部品が見る必要がある参照があるということです。 JDBCの例には、これには接続と(準備)ステートメントが含まれます。したがって、ターゲットコードを本質的に「ターゲットコード」で「ラップ」することを処理します。
Javaのいくつかの一般的なケースに精通しているかもしれません。 1つはサーブレットフィルターです。もう1つはアドバイスに関するAOPです。 3番目は、春のさまざまなxxxtemplateクラスです。いずれの場合も、「興味深い」コード(JDBCクエリと結果セット処理など)が注入されるラッパーオブジェクトがあります。ラッパーオブジェクトは「前」の部分を実行し、興味深いコードを呼び出してから「後」パーツを実行します。
参照してください コードサンドイッチ, 、多くのプログラミング言語でこの構成を調査し、興味深い研究のアイデアを提供します。なぜそれを使用するのかという特定の質問に関して、上記の論文はいくつかの具体的な例を提供します。
このような状況は、プログラムが共有リソースを操作するたびに発生します。ロック、ソケット、ファイル、またはデータベース接続のAPIには、以前に取得したリソースを明示的に閉じるかリリースするプログラムが必要になる場合があります。ゴミ収集のない言語では、プログラマーは、使用前にメモリを割り当て、使用後にリリースする責任があります。一般に、さまざまなプログラミングタスクでは、プログラムが変更を加え、その変更のコンテキストで動作し、変更を取り消すためのプログラムが必要です。そのような状況コードサンドイッチと呼びます。
以降:
コードサンドイッチは、多くのプログラミング状況に表示されます。いくつかの一般的な例は、ロック、ファイル記述子、ソケット接続などの希少なリソースの取得とリリースに関連しています。より一般的な場合、プログラム状態の一時的な変更にはコードサンドイッチが必要になる場合があります。たとえば、GUIベースのプログラムは、ユーザー入力を一時的に無視したり、OSカーネルがハードウェア割り込みを一時的に無効にする場合があります。これらのケースで以前の状態を復元できないと、深刻なバグが発生します。
論文はその理由を探求しません いいえ このイディオムを使用するには、言語レベルのヘルプなしでイディオムが簡単に間違えられる理由を説明しています。
欠陥のあるコードサンドイッチは、例外とそれに関連する目に見えない制御フローの存在下で最も頻繁に発生します。実際、コードサンドイッチを管理するための特別な言語機能は、主に例外をサポートする言語で発生します。
ただし、コードサンドイッチの欠陥の原因は例外だけではありません。変更が行われるたびに 体 コード、新しい制御パスが発生する可能性があります 後 コード。最も簡単な場合、メンテナーは追加する必要があります
return
サンドイッチへの声明 体 新しい欠陥を導入するには、サイレントエラーにつながる可能性があります。いつ 体コードは大きいです 前 と 後 広く分離されているので、そのような間違いは視覚的に検出するのが難しい場合があります。
これは私に思い出させます 戦略設計パターン. 。私が指し示したリンクには、パターンのJavaコードが含まれていることに注意してください。
明らかに、初期化とクリーンアップコードを作成し、戦略を渡すだけで「実行」を実行できます。これは、常に初期化とクリーンアップコードにラップされます。
コードの繰り返しを減らすために使用される手法と同様に、少なくとも2つのケースが必要になるまで使用しないでください。コードの繰り返しを削除するとメンテナンスが削減されると(コードのコピーが少なくなると、各コピーで修正をコピーするのに費やす時間が短くなります)が、メンテナンスが増加することを意味します(合計コードの増加)。したがって、このトリックのコストは、コードを追加することです。
このタイプのテクニックは、初期化とクリーンアップだけではありません。また、機能を簡単に呼び出すことができる場合にも適しています(たとえば、ウィザードで使用できるように、「次の」ボタンと「以前の」ボタンは、行くために何をすべきかを決定するために巨大なケースステートメントを必要としないようにすることができます。次/前のページ。
私は4歳のように説明しようとします:
例1
サンタが町にやってくる。彼のエルフは、彼らが彼の背中の後ろに望むものを何でもコードし、彼らが変化しない限り、物事が少し繰り返される:
- 包装紙を入手してください
- 得る スーパー任天堂.
- 包んでください。
またはこれ:
- 包装紙を入手してください
- 得る バービー人形.
- 包んでください。
.... 100万回の異なるプレゼントで100万回の吐き気:唯一の違うものはステップ2であることに注意してください。ステップ2が違う唯一のものである場合、なぜサンタはコードを複製しているのですか? 1と3は100万回?百万のプレゼントは、彼が不必要に手順1と3を100万回繰り返していることを意味します。
実行中は、その問題を解決するのに役立ちます。コードを削除するのに役立ちます。ステップ1と3は基本的に一定であり、ステップ2が変化する唯一の部分になることができます。
例#2
まだ手に入らない場合は、別の例があります。砂を考えてみてください。外側のパンは常に同じですが、選択した砂の種類に応じて内側にあるものが変わります(.eg ham、チーズ、ジャム、ピーナッツバターなど)。パンは常に外側にあり、あなたが作っているあらゆる種類の砂で10億回それを繰り返す必要はありません。
上記の説明を読んだら、おそらく理解しやすいと思うでしょう。この説明があなたを助けてくれることを願っています。
あなたがグルーヴィーなイディオムが欲しいなら、ここにあります:
//-- the target class
class Resource {
def open () { // sensitive operation }
def close () { // sensitive operation }
//-- target method
def doWork() { println "working";} }
//-- the execute around code
def static use (closure) {
def res = new Resource();
try {
res.open();
closure(res)
} finally {
res.close();
}
}
//-- using the code
Resource.use { res -> res.doWork(); }