著作一覧 |
public interface BarReceiver { void setBar(Bar bar); } public class Foo { private Bar createBar() throws OperationException { .... } /** * 〜して〜して〜したうえでbarを設定する。ただし〜の場合にはbarを設定しない。 * @param barReceiver barを設定するオブジェクト * @return barを設定したら真。設定しなければ偽。(注:明示的にnullを設定することはない) * @throws OperationException 処理に失敗。この場合、barの設定は行われない */ boolean fetchBar(BarReceiver barReceiver) throws OperationException { boolean isBarCreatable = false; ... // いろいろ前提条件のチェック ... if (isBarCreatable) { barReceiver.setBar(createBar()); } return isBarCreatable; } } public class Client implements BarReceiver { private Bar bar; public void setBar(Bar newBar) { bar = newBar; } void someMethod { .... Foo foo = new Foo(); bar = Bar.createDefault(); foo.fetchBar(this); // 設定/未設定を問わない bar.doSomething(); ... bar = null; if (foo.fetchBar(this)) { // 設定された場合のみ処理 bar.doSomething(); } ... } }これがやり過ぎなのは、Clientクラスが全知全能なところ。こいつはthisを知っているのは当然として、FooもBarも知っている。また、直観的なインターフェイスかどうかは怪しい。
Tellでやる場合、追加に弱いというのがある。
呼び出し側と、呼ばれる側、必ず2つのソースが修正される(状況によっては、できるだけ少ないファイル数だけ交換したい場合もある)。
スクリプティングだと、処理の追加は、スクリプトメソッドの修正で済ませられるかも知れない。
そのため、Barを取り出すという処理が不可欠なのだと前提する。
もともとの論点は、へまなプログラマはnullチェックしないとか、nullチェックは変だ、ということで、問い合わせメソッドを追加すれば解決ということだった。しかし、nullチェックしないへまなプログラマは問い合わせメソッドも呼ばないかも知れないから、抑止効果は限定的だ。
一方、Tellにしろ、上の方法にしろ(戻り値をチェックしない、デフォルトもセットしないだとだめだけど)、最初から検証を不要にしているから、論点をほぼ完全に(だって戻り値をチェックしなかったりしたら)クリアしている。Tellは手元にBarを戻さないが、上のやつ(コールバックだけど)は手元にBarが戻る。
あと呼び出しの原子性についていえば、前提がnullチェックもしないへまなプログラマを想定しているのだから、当然、クリティカルセクションを作るかどうかも怪しいので状態チェックメソッドではだめでしょう。でも、Tellやコールバックなら、Fooの実装者が制御できるから安全。
ルール(手順といっても良い)を減らすというのが良いインターフェイスだと思う。とは言えケースバイケースだけど。一般論として。
public interface BarReceiver { void doWithBar(Bar bar) throws OperationException; } //Fooはほとんど同じ(呼び出すメソッドが上のに変わる) //Client void someMethod { .... Foo foo = new Foo(); foo.fetchBar(new BarReceiver() { public doWithBar(Bar bar) throws OperationException { .. barを使った処理 } }); ... }後腐れないし、処理の実行順序はソース上と一致(無名インナークラスのメソッドが実行されるのは、記述した場所と同じ)しているから、スクリプトの流れがとぎれない(ブロックは途切れるが、そこは適宜finalを使ったり)。
ジェズイットを見習え |