トップ «前の日記(2004-04-08) 最新 次の日記(2004-04-10)» 編集

日々の破片

Subscribe with livedoor Reader
著作一覧

2004-04-09

_ おもしろいからもう一発

これはやり過ぎだと思う。でも、こういうのもありかも。
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も知っている。また、直観的なインターフェイスかどうかは怪しい。
こういうのもありかなぁ、というのは、Fooの実装の自由度の高さ。およびClientがBarが未設定の場合の処理を制御できる点。
この場合、BarReceiverは、Barのインナーインターフェイスか、Fooのインナーインターフェイスとしてスコープを狭めたほうが良いかも知れない。Clientのやり過ぎ性をそれによって気分的に抑制する。
BarがFooに特化した処理オブジェクトの場合とか(たとえば、Fooの後付けのビルダーとか)には面白いかも。
IoM(Inversion Of Method)メソッドの逆転(って言うか、単なるコールバックだけど)。考えてみたら列挙処理とかだと良くあるパターンだな。

_ さらに考察

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を使ったり)。
この場合、Fooは、Barの生成方法は知っているが、それ以上Barのことは知らなくて良い(完全なTellだと知っている必要がある)。したがって、全知全能のスクリプターのClientに対して、Fooは単なる値オブジェクトに毛が生えただけのオブジェクトコンテナの状態に留めておけるところがミソかも。

2003|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|

ジェズイットを見習え