2012-04-26
ジェネリクスによるVisitorパターン拡張の考察
先日twitterで "Expression Problem" という問題を知った。
静的な型付けの下で、場合分けのデータ構造に対して、新しい場合分けとその場合に対する新しい処理を、元のソースコードに手を加えることなく拡張定義すること
2009-05-16 - まぁ、そんなもんでしょう。
この問題が意図するところを語るにはまずオブジェクト指向から流れを辿らねばなるまい。
オブジェクト指向のポリモーフィズム
Javaのようなオブジェクト指向の言語で、ある特定のメソッドがあることを抽象クラスHogeで保証するとしよう。
public interface Hoge { void hoge(); }
このとき、機能性、つまりメソッドというのは増えることがない固定のものだが、継承して実装されたクラスというのは自由に増やすことができる。そして、抽象型Hogeを扱っている既存コードは修正する必要がない。
これはいわゆる開放/閉鎖原則(OCP: open/closed principle)というやつでJava的OOP言語は機能(提供されるメソッド)が固定で要素(ここではinterfaceの実装クラス)が増えることについてはポリモーフィズムによって容易に行えるというのが特徴だった。
Visitorパターン
これに対して、機能の方を開放/閉鎖原則にのっとり拡張することができるかどうかという問が生まれる。その答えはGoFデザインパターンのひとつ、Visitorパターンである。
このVisitorパターンはGoFの23のデザインパターンのうち、もっとも難解なパターンとも言われる。それは多分、ダブルディスパッチと呼ばれる相互に呼び合う動きが混乱を招くからだろう。
過去に異型のオブジェクト比較という稿でもVisitorについては解説を行なっているが、今一度解説してみよう。
まず、ある型Nodeを継承するA, B, Cのクラスがあったとする。これはList<Node>型のようなデータ型にごちゃまぜにして格納することができる。
List<Node> list = new ArrayList<>(); list.add(new A()); list.add(new B()); list.add(new C());
これを順に取り出してそれぞれの型によって処理を分岐したい。
for (Node node : list) { if (node instanceof A) { // Aに対する処理 } else if (node instanceof B) { // Bに対する処理 } else if (node instanceof C) { // Cに対する処理 } }
ダサい。Javaにおいてinstanceofというのはやむを得ない時を除いて使うべきではない演算子だ。こうした型別に処理をしたい場合、ポリモーフィズムに処理をするのがよしとされる。なのでこの条件分岐をポリモーフィズムを用いた分岐に書き換えよう。
そのため、これらA,B,Cの処理を行うメソッドを持ったクラスVisitorを定義する
public class Visitor { public void doAの処理() { // Aに対する処理 } public void doBの処理() { // Bに対する処理 } public void doCの処理() { // Cに対する処理 } }
このそれぞれのメソッドをA,B,Cの各Nodeからポリモーフィズムを用いて呼び出す作りにしよう。そのためNode型に抽象メソッドを足し、各具象型で次のように実装する
public interface Node { void doVisitorへの処理の委譲(Visitor v); } public class A implements Node { void doVisitorへの処理の委譲(Visitor v) { v.doAの処理(); } } public class B implements Node { void doVisitorへの処理の委譲(Visitor v) { v.doBの処理(); } } public class C implements Node { void doVisitorへの処理の委譲(Visitor v) { v.doCの処理(); } }
こうすると先ほどのfor文は
Visitor v = new Visitor(); for (Node node : list) { node.doVisitorへの処理の委譲(v); }
となる。instanceofの分岐が「doVisitorへの処理の委譲()」メソッドのポリモーフィズムになった。そして、それぞれの箇所で「doAの処理()」「doBの処理()」「doCの処理()」を呼び出すこととなった。
List<Node>型にごちゃまぜに詰め込まれていたオブジェクトを具象型別に分岐して処理することができたわけだ。これがVisitorパターンのひとつの機能である。
ここでは簡単のためVisitorクラスを具象型として作った。これをinterfaceと具象型に差し替えよう。
public class Visitor { void doAの処理(); void doBの処理(); void doCの処理(); } public class VisitorImpl implements Visitor { void doAの処理() { // Aに対する処理 } void doBの処理() { // Bに対する処理 } void doCの処理() { // Cに対する処理 } }
すると、このVisitorの実装型を切り替えることでA,B,Cのそれぞれに対して何を行うかという「機能」の部分が差し替え可能となった。機能の方を開放/閉鎖原則にのっとり拡張することができるというわけだ。
Visitorの制約
ところでここでA,B,Cという3つのクラスに新たにDを追加してみよう。
public class D implements Node { void doVisitorへの処理の委譲(Visitor v) { // ? } }
ここでVisitorにはDに対する処理が存在しないのだった。クラスDはどのようにVisitorへの処理の委譲を行えばいいのか困ってしまう。
このように、Visitorは機能性を追加することは容易だが、要素(ここではA,B,C,D)が追加されると困るのだ。
このVisitorの制約についてはGoF本にも書かれている。古くから知られる「常識」である。
そして、ポリモーフィズムは要素を増やすことは容易だが、機能性を追加する(要するにメソッドを増やす)のは困るのだ。
ここを両立させることは困難だというのが常識であった。
Expression Problem
この両方を既存部分に手を入れることなく増やせるようにする(解放/閉鎖原則)にはどうしたらよいか?というのを問うた一例が"Expression Problem"というわけだ。
Expression Problemは、古くから言われている問題の言い替えに過ぎない。その目的は、静的な型付けの下で、場合分けのデータ構造に対して、新しい場合分けとその場合に対する新しい処理を、元のソースコードに手を加えることなく拡張定義することだ。具体的な例として、式をデータ構造(最初は定数だけ)、式の評価関数を一つの処理と考え、これに足し算と新しい処理(文字列への変換)を加えてみたい。
ある言語がこのExpression Problemを解決できるかどうかは、その言語の表現能力を示す顕著な指標だ。行と列を持つ表を考えると、いうなれば場合分けを行、処理を列と捉えることができる。関数型言語では、列(処理)を加えることは簡単だけれども、行は(データ型宣言により)固定されてしまう。オブジェクト指向言語では、新しい行(サブクラス)を加えることは簡単だけれども、列は(メソッド定義により)固定されてしまう。我々は、行と列の両方を簡単に加えたいのだ。
2009-05-16 - まぁ、そんなもんでしょう。
この原文は
http://www.daimi.au.dk/~madst/tool/papers/expression.txt
でPhilip Wadler氏(モナドで有名な人らしい)がGeneric Javaに関する議論でメーリングリストに投稿した文章らしい。
この文が投稿された1998年の11月の時代背景だが、まだJavaのバージョンが1.1だった。1998年の12月に1.2がリリースされているから、その直前のあたりの話だ。Javaに実際にジェネリクスが載ったのはJava5からで、2004年9月30日のことである。
なので、この原文はまだ確定していないJavaのジェネリクスの実装についての議論であり、掲載されているソースコードは動かそうとしてもコンパイルすることができないので注意が必要。
このソースコードでは再帰ジェネリクスを用いた上で内部クラスを利用した型変数の共有を行なっている。もっともここでは内部クラスのように内部interfaceを定義して外部クラスの型変数を利用するようなコードとなっているがinterfaceは暗黙にpublicでstaticなため構文的にNGなのである。
とにかく、こういうジェネリクスが使えるならこんな感じで機能と要素と両方を増やすことが出来るんじゃない?という話のようだ。
じゃあ、ジェネリクスが利用できるJava5以降の世界ではそれは実現できるのだろうか?
機能と要素を増やす
Philip Wadler氏のコードはやけに複雑ではあるが、そこまでする必要はない。
Nodeの実装型を増やした場合、既存のVisitorでは対応できなくなることは先に述べたとおりだ。要素が増えたならそれに対応したVisitorを新たに作らなければならない。Nodeの実装型も新たなVisitor型へと切り替える必要が出てくる。
型が変わることに対応するとなれば型変数。ジェネリクスの出番だ。ここではNodeに対して自分自身を処理できるVisitorを型変数で明示するようにしてみよう。
public interface Node<V extends Visitor> { void doVisitorへの処理の委譲(V v); }
Visitorのインターフェースはとりあえずメソッドなしで用意する
public interface Visitor {}
簡単のため最初はNodeはひとつだけとしよう。NodeAクラスである。
public class NodeA implements Node<VisitorA> { @Override public void doVisitorへの処理の委譲(VisitorA v) { v.doAの処理(this); } }
このNodeAだけを処理するVisitorAを作る
public interface VisitorA extends Visitor { void doAの処理(NodeA a); }
この時点でVisitorAの具象型を作ればどんどん機能の方は増やせる状態だ。
では、ここに新しくNodeBを追加するときはどうするのか。
public class NodeB implements Node<VisitorB> { @Override public void doVisitorへの処理の委譲(VisitorB v) { v.doBの処理(this); } } public interface VisitorB extends VisitorA { void doBの処理(NodeB b); }
新しく追加したNodeBを既存のVisitorAで扱うことは不可能であるから、新たにVisitorAを拡張したinterface VisitorBを作る。VisitorBでは見ての通りBを処理するメソッドが追加される。NodeBはNodeをimplementsする際に自信を処理できるVisitorとしてVisitorBを指定している。そのため、
public void doVisitorへの処理の委譲(VisitorB v)
というように渡されるVisitorがVisitorBになるわけだ。
Visitorの具象型はどうなるだろうか。たとえばSystem.out.println()するだけのVisitor実装を作ってみよう。
public class VisitorAimpl implements VisitorA { @Override public void doAの処理(NodeA a) { System.out.println(a); } }
この実装を利用しつつ、NodeBにも対応したVisitorBimplを作る場合は
public class VisitorBimpl extends VisitorAimpl implements VisitorB { @Override public void doBの処理(NodeB b) { System.out.println(b); } }
といった具合。追加になった分のNodeBの処理だけ書けば済む。
まとめ
古典的Visitorパターンは要素の追加が困難とされてきたがジェネリクスを導入することでこの欠点を克服することが可能だ。
Visitorパターンは構文解析木の処理などに利用されることが多いが言語仕様が変更になって構文の要素が追加になることを想定して言語仕様のバージョンごとに先に挙げたようなVisitorの継承階層を作ると良いかもしれない。
2012-04-07
Semicolonless Java 2012
Java, Generics, Semicolonless | |
JavaOne Tokyo 2012でLTをしてきました。内容は寺田さんの要請でSemicolonless Java(セミコロンレス・ジャバ)です。
当日は風邪で喉をやられたので音声が聞き取りにくかったと思います。申し訳ないです。公式のフォトライブラリーがあるので当日の会場の雰囲気がいくらか分かるかと思います。僕も写ってます。
LT資料はそのうち公式ページで見れるようになると思います。が、LT用なので資料だけ見てもよく分からないと思います。というわけで解説をしておきます。
レギュレーション
Semicolonless Javaのルールは至ってシンプルです。
今のところJava7を使用しています。
Hello world
通常System.out.println();を利用しますが、このセミコロンをどうやって回避するのか。
public class HelloWorld { public static void main(String[] args) throws Exception { if(System.out.printf("Hello world.")==null){} } }
System.out.println()は戻り値がないので呼び出しが困難(後述)ですが、printf()であればPrintStreamを返すのでこれを適当な値(ここではnull値)と比較してif文に押し込めることでセミコロンを取り除くことができます。
戻り値のあるメソッドは呼び出すのは簡単ですが、セミコロンレスではreturnを記述できないため宣言することが不可能です。既存のJava APIのメソッドを呼ぶことしか出来ません。
戻り値がvoidのメソッドは通常の呼び方では呼び出せませんが、リフレクションを利用することで呼び出すことができます。
if (java.io.PrintStream.class.getMethod( "println", String.class).invoke( System.out, "Hello world.") == null){}
ただし、このリフレクションによるメソッドコールは多用しないほうが美しいとされます。
FizzBuzz
for(int i=1; i<100; i++) {}
ここでforではなくwhileを利用することもできますが、
いずれにせよループカウンタを宣言しようとするとセミコロンが必要になります。
どうするのか?
public class FizzBuzz { public static void main(String[] args) { for (int i : new int[] { 0 }) { while (++i <= 100) { if (i % 3 == 0 && i % 5 == 0) { if (null==System.out.printf("FizzBuzz\n")){} } else if (i % 3 == 0) { if (null==System.out.printf("Fizz\n")){} } else if (i % 5 == 0) { if (null==System.out.printf("Buzz\n")){} } else { if (null==System.out.printf("%d\n", i)){} } } } } }
変数の宣言をしたい場合はfor-each構文を利用します。
for (int i : new int[] { 0 }) { // ... }
クイックソート
クイックソートを作る場合、再帰呼び出しを行うのが一般的です。戻り値がvoidのメソッドは呼び出しにリフレクションが必要になりますし、戻り値があるメソッドはセミコロンなしには宣言できません。メソッドの代わりになるものがなくては構造化プログラミングがままなりませんね。
どうすればいいのか?
public class Sort { public static void main(String[] args) { for (java.util.List<Integer> list : new java.util.ArrayList<java.util.List<Integer>>() {{ if (add(new java.util.ArrayList<Integer>( java.util.Arrays.asList( 5, 3, 9, 0, 1, 8, 7, 2, 4, 6)) { })) {} }}) { if (System.out.printf(list + "\n") == null) {} if (new QuickSort(list, 0, list.size() - 1) == null) {} if (System.out.printf("" + list) == null) {} } } public static class QuickSort { public QuickSort(java.util.List<Integer> list, int left, int right) { if (left <= right) { for (int p : new int[] { list.get((left + right) / 2) }) { for (int l : new int[] { left }) { for (int r : new int[] { right }) { while (l <= r) { while (list.get(l) < p) { if (++l == 0) {} } while (list.get(r) > p) { if (--r == 0) {} } if (l <= r) { if (list.set(l, list.set(r, list.get(l))) == null) {} if (++l == 0) {} if (--r == 0) {} } } if (new QuickSort(list, left, r) == null) {} if (new QuickSort(list, l, right) == null) {} }}} } } } }
こうすることで呼び出しが簡単になります。
Pointクラスを作る
X,Y座標を持ち、距離を取得する機能を持った“Point クラス”を作ってみましょう。オブジェクト指向プログラミングしたいですもんね。
セミコロンを用いないとクラスにフィールドを宣言することができません。ローカル変数はfor-each文で代用することが出来ましたがどうすればフィールド宣言を代替できるのか?
public class Point extends java.util.HashMap<Point.FieldKey, Double> { enum FieldKey { X, Y, } public Point(double x, double y) { if(this.put(FieldKey.X, x)==null){} if(this.put(FieldKey.Y, y)==null){} } public class GetX extends java.util.Stack<Double> {{ if (push(Point.this.get(FieldKey.X))==null){} }} public class GetY extends java.util.Stack<Double> {{ if (push(Point.this.get(FieldKey.Y))==null){} }} public class GetDistance extends java.util.Stack<Double> { public GetDistance(double x, double y) { if (push(Math.sqrt( Math.pow((Point.this.get(FieldKey.X) - x), 2) +Math.pow((Point.this.get(FieldKey.Y) - y), 2)))==null){} } } public static void main(String[] args) { for (Point p : new Point[]{new Point(3, 4)}) { if (System.out.printf("x: %e, y: %e, distance: %e", p.new GetX().pop(), p.new GetY().pop(), p.new GetDistance(0, 0).pop())==null){} } } }
フィールド宣言が行えないのでHashMapを継承することでフィールドの代わりとします。キーは列挙型にしておくとよいでしょう。値はここではDouble型にしていますが
一般的にフィールドは同じクラス内でもいろんな型を用いますからObject型としておきキャストすることになります。
戻り値を戻すメソッドの代替ですが、Stackを継承した内部クラスを作ることで解決できます。
public class GetX extends java.util.Stack<Double> {{ if (push(Point.this.get(FieldKey.X))==null){} }}
このようにしておくと呼び出し側では
p.new GetX().pop()
とすることで値を得ることができます。
内部クラスをnewするときは 外部クラスの変数.new とします。
詳しくは別記事を参照。
型安全
さて、ここまでが前ふりですね。
フィールドの代替としてHashMapを継承してObject型からキャストするということでしたがやはりJavaを使うからには型安全にしたいですよね。
これこそが2012年版のSemicolonless Javaのハイライトです。
Mapはキーごとに値の型を変えることができません。
HashMap<Key, Object> map = new HashMap<>(); enum Key{ NAME, AGE, } String name = (String)map.get(Key.NAME); int age = (Integer)map.get(Key.AGE);
とりあえず、キーを列挙からTypesafe-enumパターンを利用してキーごとに値の型を持たせてみましょう。
public final class Key<V>{ public static final Key<String> NAME = new Key<>(); public static final Key<Integer> AGE = new Key<>(); }
すると、Mapのフィールド
HashMap<Key<?>, Object> map = new HashMap<>();
があったとして、
public <T> T get(Key<T> key) { return (T)map.get(key); }
と書くことで
String name = get(Key.NAME);
int age = get(Key.AGE);
というように型安全にすることができます。
ところが、Semicolonless Java ではフィールドは使えないのでこのような機能性をもったMapのラッパークラスを作り、それをextendsして使いたい。
とりあえず単純に
public class KeyValue<K> { KeyValue<K, Object> map; public <T> T get(K<T> key) { // Compile error ! return map.get(key); } }
というようにクラスに括りだすとK<T>というように型変数に型変数を持たせることができないのでコンパイルエラーになります。
そこでまず
public class Group { /** singleton */ private static final Group g = new Group(); public class Key<T> {} public static final Key<String> NAME = g.new Key<>(); public static final Key<Integer> AGE = g.new Key<>(); }
というようにキーをGroup型とその内部クラスKey型という2階層構造にします。そしてこのGroup型を抽象化して具象型と分離します
public abstract class Group<G extends Group<G>> { public class Key<T> {} } public class PersonKey extends Group<PersonKey> { /** singleton */ private static final PersonKey g = new PersonKey(); public static final Key<String> NAME = g.new Key<>(); public static final Key<Integer> AGE = g.new Key<>(); }
このGroup型を先ほどのK<T>の代わりに使用します。
public class KeyValue<K extends KeyValue.Group<K>> { public static abstract class Group<G extends Group<G>> { public class Key<T> {} } HashMap<Group<K>.Key<?>, Object> map = new HashMap<>(); @SuppressWarnings("unchecked") public <T> T get(Group<K>.Key<T> key) { return (T)map.get(key); } }
このKeyValueクラスを使用する場合は
KeyValue<PersonKey> map = new KeyValue<>(); String name = map.get(PersonKey.NAME); int age = map.get(PersonKey.AGE);
という感じになります。キーごとに戻り値の型が定まりました。これは以前に Javaによる高階型変数の実装 - プログラマーの脳みそというエントリで紹介したジェネリクスのテクニックです。
この技術をSemicolonless Javaに応用してみましょう。
Semicolonless化
まずキーの列挙ですが、static finalなフィールドを作って定数とするのがそもそもSemicolonlessには不可能なので、定数の代わりに内部クラスを使います。
public class PersonKey extends KeyValue.Group<PersonKey> { class Name extends PersonKey.Key<String> {} class Age extends PersonKey.Key<Integer> {} }
ここでextendsしているPersonKey.Keyクラスですがこれは親のクラスで宣言されている内部クラスが実体です。
public static abstract class Group<S extends Group<S>> { public class Key<T> {} }
ただ、このPersonKey.NameやAgeをMapのキーとして使ってもうまくMapが機能しません。
MapのキーにするためにはhashCode()とequals()を
オーバーライドしなければいけないのでしたね。ところがSemicolonlessにこれら値を返すメソッドのオーバーライドはできないのでした。
そこで、すでにあるものを使えばいいということでjava.awt.Pointなどの既存のクラスを利用します。
public static abstract class Group<S extends Group<S>> { public class Key<T> extends java.awt.Point {} }
こうすることでName型とAge型はどのインスタンスも同値と判断されるクラスになりました。Mapのキーとして使えるようになりましたね。
あとはメソッドをSemicolonless化して
public class KeyValue<G extends KeyValue.Group<G>> extends java.util.HashMap<Object, Object>{ public static abstract class Group<S extends Group<S>> { public class Key<T> extends java.awt.Point {} } public class Put { public <T> Put(Group<G>.Key<T> key, T value){ if (KeyValue.this.put(key, value) == null){} } } public class Get<T> extends java.util.Stack<T> { @SuppressWarnings("unchecked") public Get(Group<G>.Key<T> key) { if (this.push((T)KeyValue.this.get(key))==null){} } } }
というKeyValueクラスを作ります。これがキーによって値の型がかわるタイプセーフでSemicolonlessなMapの代替型です。
これを継承したクラスを作り
public class Main extends KeyValue<PersonKey>{ public static void main(String[] args) { if (new Main()==null){} } public Main() { if (this.new Put(new PersonKey().new Name(), "なぎせ ゆうき") == null){} if (this.new Put(new PersonKey().new Age(), 0x20) == null){} for(String name : new String[]{ this.new Get<>(new PersonKey().new Name()).pop() }) { for(int age : new int[]{ this.new Get<>(new PersonKey().new Age()).pop() }) { if (System.out.printf(name+" (%d)", age)==null){} }} } }
というように利用します。
これでタイプセーフなフィールド宣言の代替ができるようになりました。
まとめ
Semicolonless Javaは2010年のjava-ja温泉で生まれたネタですが、
2012年時点ではライブラリを作る基礎技術が確立しました。
これでそれなりの規模のコードも書けることがお分かりいただけると思います。
拙いLTでしたが会場のみなさん喜んでくれていたようでうれしい限りです。
2012-03-16
アジャイルのための第一歩に僕が選んだこと - アジャイルジャパン2012富山サテライト
3/16にアジャイルジャパン2012が開催されました。本講演は今年は大阪会場で、大阪以外にも各地でサテライトが開催されました。僕は富山サテライトで参加です。
セッションのテーマ
僕のセッションのテーマは今の開発現場にどうやったらアジャイルを組み込めるか?ということです。タイトルは「できるアジャイル」
アジャイルをシステム開発の現場に導入するにはいくつか壁があります。とくに受託開発をしている現場に導入する場合、「契約」という大きな壁が立ちはだかります。いち開発者という立場でこれに立ち向かうのはさながらドン・キホーテのように思えるかもしれません。
これをどうにか一歩アジャイルに踏み出すにはどうしたらよいかという話をしました。
アジャイルとは何ぞ?
アジャイルとは何か?これを改めて問うてみましょう。アジャイルの原典というとアジャイルソフトウェア開発宣言です。
私たちは、ソフトウェア開発の実践
あるいは実践を手助けをする活動を通じて、
よりよい開発方法を見つけだそうとしている。
この活動を通して、私たちは以下の価値に至った。
プロセスやツールよりも個人と対話を、
包括的なドキュメントよりも動くソフトウェアを、
契約交渉よりも顧客との協調を、
計画に従うことよりも変化への対応を、
価値とする。すなわち、左記のことがらに価値があることを
認めながらも、私たちは右記のことがらにより価値をおく。
http://agilemanifesto.org/iso/ja/
これが、アジャイルです。アジャイルというのは思想なんですね。価値観なんです。そしてこの価値観を実践するための方法論がアジャイルプロセスなんです。アジャイルが目的でアジャイルプロセスが手段。
なので、プロセスばかりに注目して導入してもそれはアジャイル足りえません。アジャイルという目的を持たないでプロセスだけ導入してもダメなんです。
アジャイルをアジャイルしよう
僕がまず強調したのはアジャイルを取り入れることを一気にやろうとしないこと。ウォーターフォール的にアジャイル開発できる開発チームを作ろうとしたなら、その過程がアジャイルではない。ハードランディングはダメです。開発ができる状況を維持しつつ変化を取り込んでいきましょう。
そして、そのための第一歩として僕がチョイスしたのは「お客さんに動くソフトウェアを触ってもらう」ということです。
とりあえず、第一歩としてコレだけをまずやろう、という話です。
どのように触ってもらうか
一括受注している場合、途中で勝手にプロダクトとしてモノをリリースするというのは都合が悪いんです。最初からそういう契約になっていればまだしも、一括受注している場合、見積をして、発注が来て、設計をして、開発をして、テストをして、納品して、検収してもらって、そして初めてお金が払われるわけです。
この契約の流れを変えるというのはいち開発者には難しいことでしょう。
ですから、とりあえず、プロダクトではなくプロトタイプという体でモノを触ってもらうというのを努力目標としました。あくまでプロトタイプですよという建前ですね。これは大人の事情というか、方便のようなものですが、まずはそれでもいいのでお客さんに触ってもらう。触ってもらってフィードバックをもらう。これをアジャイル導入の第一歩としました。
変化への対応
変化への対応について、強調したのは「お金は請求してください」ということです。お客さんが追加でこういうことをしたいという分には、無料で対応するわけではなく「これぐらいの費用がかかりますね」という話をして下さいということを言いました。
なぜか。じゃないと会社が許さないからです。僕らは霞を食べて生きているわけではないので、タダ働きするわけにはいきません。お客さんが価値あるソフトウェアを作るために僕らに追加の仕事を依頼するなら、当然、仕事の報酬を請求してください。
追加の依頼が有償であるなら、そしてその報酬が妥当であれば、会社は変化を受け入れることに反対することはまずないでしょう。アジャイルの精神を取り入れるために、報酬の心配をしなくていいようにしましょう。
落とし穴
変化への対応をするのはいいのですが、お客さんが仕様変更するに至った経緯をちゃんと記録しましょう。これは歴史書として流れが見えるようにまとめる必要があります。
それは堂々巡りを避けるためでもあり、あるいは不当に瑕疵であると言われないための予防線でもあります。未来への議論のために過去の流れを残しておきましょう。さもなくば、往々にして過去にやった議論を再度蒸し返して無駄な時間を費やすことになります。目的はあくまで未来の議論のためです。
まとめ
僕が主張したのはこれだけなんです。最初の第一歩なので。とにかく、お客さんに触って貰い、フィードバックをうけ、変化を受け入れる。これをやりましょうということを言いました。
そのためには契約の壁があります。方便としてプロトタイプの体でいいのでお客さんに触ってもらいましょうという話です。
これがアジャイル開発を今の現場に取り入れる第一歩として非常に有効だと思っています。その他、取り込みたいところは順次取り入れていけばいいと思います。それがアジャイルってもんですからね。
2012-03-09
AsyncTaskの使い方考察
Android | |
Androidでの開発ではAndroid OSをある種のフレームワークと捉えてその作法に則ってうまく「使われる」プログラムを書かなくてはならない。なのでそのフレームワークがどういう仕組で、どういう流れで僕らの書いたコードを呼び出すのかということを理解することがよい設計に結びつく。と、大風呂敷を広げているが僕もさほどAndroidに精通していないのでこれは努力目標みたいなもんです。
大雑把に割愛して、今回のテーマに関係の深いところをピックアップする感じでいきますか。
テーマのAsyncTaskだけども、要は非同期処理をしたい時に使う。Javaで非同期処理といえばThreadなんだけどもAndroidでは一般にAsyncTaskを使う。AsyncTaskではUIスレッドを使った非同期処理を簡単に(?)実装できるという触れ込み。
UIスレッドとは?
そこでまずUIスレッドとは何かを理解せねばなるまい。ここでいうUIはユーザインタフェースの略のUIで特に画面を指すGUI(グラフィカル-)とタッチパネル操作などの入力インターフェースを併せてUIという。
で、このUIってのが厄介で、非同期処理したいんだけども状態の不整合がおきて同期が難しくなるってんでよく採用されるのがシングルスレッドモデル。これはUIをいじるスレッドを1つだけに限定して、そのスレッドに細切れにしたUIをいじる処理を渡して順次実行していく、という代物。そのUIをいじる用スレッド(これはフレームワークが作成し管理している)のことをUIスレッドと呼ぶ。
このシングルスレッドモデルはJavaのGUIのSwingなどでも採用されている。GUIフレームワークではメジャーな手法となっている。
Androidの場合はこのUIスレッドに処理をさせるにあたって
あたりがメジャーなところではないかと。
AsyncTaskの概要
AsyncTaskはジェネリックなクラスで
AsyncTask<Params, Progress, Result>
protected abstract Result doInBackground(Params... params)
で、このメソッドの部分が非同期実行される。これはUIスレッドではない。
冒頭の型変数のParamsとResultはこのメソッドの引数・戻り値で、自分で好きに型を選択できるということだ。paramsはAsyncTaskを実行開始するexecute()メソッドの引数に渡されたものがこのdoInBackgroundのparamsとして渡ってくる。
doInBackground()の実行が終わると
protected void onPostExecute(Result result)
が呼び出される。この引数resultは先のdoInBackground()でreturnしたオブジェクトだ。このonPostExecute()はUIスレッドから呼び出される。
つまり、doInBackgroundで非同期処理をして取得した情報をResult型に詰め込んでおき、それをonPostExecute()で受け取ってUIに反映させるという使い方が通常想定される使い方だろう。
特記事項として
void onProgressUpdate(Progress... values)
というメソッドがあって、これはdoInBackground()中でpublishProgress(Progress... values)を呼び出すとUIスレッドがこのonProgressUpdate()を呼び出すというモノ。名前からしてもプログレスバーなどの進捗具合を表示するための機能であることが伺える。
Javaの無名クラス
このAsyncTaskだけども、extends AsyncTaskとしたトップレベルクラスを作ることはあまりない。大抵は無名クラスなどで済ませることが多い。
無名クラスには便利な(そしてややこしい)機能があって、メソッド中で無名クラスを作った場合、finalなローカル変数であれば無名クラスの中で参照することができる。
インスタンスメソッドの中で宣言した無名クラスの中からは、外部クラスのインスタンスフィールドを参照することができる。この際、無名クラスに同名のフィールドがあったりした場合は外部クラス名.this.フィールド名という記法で参照することができる。this.だと無名クラス自身のフィールドを参照することになる。
このあたりは別稿にまとめておいたのでそちらを参照してほしい。
こんな無名クラスを用いてAsyncTaskを使うとどのようになるのか。
AsyncTask使用例
まずはdoInBackground()でint値とString値を渡し、加工して、onPostExecute()にint値とString値を渡し、UIに反映させるような例を考えよう。
class Param { int param1; String param2; } Param param = new Param(); class Result { int result1; String result2; } AsyncTask<Param, Void, Result> task = new AsyncTask<Param, Void, Result>() { @Override protected Result doInBackground(Param... params) { Param param = params[0]; // 処理をしてonPostExecute()に渡すResult型オブジェクトに格納 Result result = new Result(); result.result1 = param.param1; result.result2 = param.param2; return result; // ここでreturnしたオブジェクトがonPostExecute()に渡される } @Override protected void onPostExecute(Result result) { System.out.println(result.result1); System.out.println(result.result2); } }; task.execute(param); // パラメータを渡す
ParamとResultはここではローカル内部クラス*1で宣言した。
onProgressUpdate()は今回使わないので型変数は不要だ。こういう場合、java.lang.Voidという型があるのでこれで潰しておく。
無名クラスを使った場合、外部のメソッドでfinal変数を宣言すれば無名クラス内で参照できるのであった。とするとdoInBackground()の引数pramsを使う必要が薄れる。単一のパラメータを渡すだけならまだアリだが、pramsは型変数Paramsの可変長引数であるからあくまでParams型が複数並んでいるケースでもなければデータを渡すのに使いにくい。
となると可変長引数だからといって配列として渡すなんてのは滅多にやらず、もっぱらParams型オブジェクトのフィールドに渡すべきものを全部並べるという使い方になる。ところが、その場合、Params型を定義しなくてはならず、そんなことならfinal変数作って直接内部で参照してしまえばいいじゃないか、となってしまうわけである。
かくして、無名クラスでのAsyncTaskはParams型変数はどうでもよくなる。execute()の引数にはnullを渡してもいいが、可変長引数なのでそもそも何も渡さないのがシンプルだろう。
さて、Result型をどうするかだが、これはonPostExecute()で使いたいデータを格納するクラスを定義してフィールドに並べるのが妥当だろう。ところが、これまた渡したいデータが複数ある場合にいちいちクラスを宣言するのかよという話になって、無名クラスにフィールド宣言してそこにdoInBackground()でデータを格納してonPostExecute()で参照すればいいんじゃないの、となる。かくしてResult型もどうでもよくなる。
// doInBackgroundに渡したいデータ。final宣言した変数を用いる final int param1 = 0; final String param2 = "hoge"; AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { // 処理をしてonPostExecute()に渡すデータをインスタンスフィールドに格納 result1 = param1; result2 = param2; return null; // Void型なのでnull値を返しておく } int result1; String result2; @Override protected void onPostExecute(Void result) { System.out.println(result1); System.out.println(result2); } }; task.execute(); // ここでは何も渡さない
だいたいこんな書き方をしている人が多いんじゃないだろうか。
連続した処理
非同期処理でやることといえば時間のかかることが中心だ。例えばDBからのデータ読込みとかネットワークからのデータ取得とか。こうした時間のかかる処理をいくつか行わなければならいというケースがままある。そのとき、結果を順次GUIに反映したいとする。最初の時間のかかる処理をAsyncTaskのdoInBackground()→onPostExecute()としてUIに反映させた後にonPostExecute()から次の処理を実行するAsyncTaskを呼び出すという作りになることだろう。
通信してAを読み込んで結果大丈夫であればBを読んで、さらに大丈夫ならCを呼ぶ…といった要求に対してかつて僕は次のようなコードを書いた。
AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { // 処理その1 // ... result1 = 0; return null; } int result1; // 処理その1の結果 @Override protected void onPostExecute(Void result) { // UIスレッドでの処理その1 // ... System.out.println(result1); // 処理を終えたら次のタスクを起動する AsyncTask<Void, Void, Void> task2 = new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { // 処理その2 // ... result2 = "hoge"; // 処理その2の結果 return null; } String result2; @Override protected void onPostExecute(Void result) { // UIスレッドでの処理その2 // ... System.out.println(result2); // さらに処理を終えたら次のタスクを起動する AsyncTask<Void, Void, Void> task3; // 省略 } }; task2.execute(); } }; task.execute();
まぁなんと面倒な事か。
解決策
これについてonProgressUpdate()を利用して実装する方法を考えた。onProgressUpdate()のパラメータに関数を渡せるようにしてしまえばいい。Javaでは関数ポインタのようなものを直接的に扱えないのでここではRunnableインターフェースを用いる。
AsyncTask<Void, Runnable, Void> task = new AsyncTask<Void, Runnable, Void>() { @Override protected Void doInBackground(Void... params) { // 処理その1 // ... final int result1 = 0; // 処理その1の結果 publishProgress(new Runnable() { @Override public void run() { // UIスレッドでの処理その1 // ... // ここでは先の処理その1の結果をfinal変数を用いて受けとって利用できる System.out.println(result1); } }); // 処理その2 // ... final String result2 = "hoge"; // 処理その2の結果 publishProgress(new Runnable() { @Override public void run() { // UIスレッドでの処理その2 // ... System.out.println(result2); } }); // 処理その3 // ... return null; } @Override protected void onProgressUpdate(Runnable... values) { for (Runnable runnable : values) { runnable.run(); } } }; task.execute();
onProgressUpdate()の実装は引数に渡されたRunnableのrun()を呼び出すだけ。一応、可変長引数になっているのでループで回して順次呼び出すようにしている。
これにより、ネットワークやDBなどからデータを読み込んで、読めたものから順次UIに反映させるプログラムが比較的スムーズに書けるのではないか。
まとめ
- AsyncTaskのパラメータ渡すのは面倒くさい
- 使い回ししないその場限りのAsyncTaskが比較的多い
- AsyncTaskを無名クラスで実装する場合、final変数を参照できる機能性を利用するとコーディングが楽
- さらにonProgressUpdate()にRunnableを渡す作りにすると随時UIスレッドを呼べて良いのではないか
このデザインパターンに対して仮にRunnable型変数AsyncTaskとでも名付けておこうと思う。使い勝手や機能性についての指摘など、ご意見、感想お待ちしております。
2012-03-08
Javaのクラス宣言5種+α
java | |
Javaのクラス宣言には5種類ある。
トップレベルクラス・ネストしたクラス・内部クラス・ローカル内部クラス・無名クラスの5種類だ。
今回はこの5種類のクラス宣言のおさらい。
トップレベルクラス
これは普段使っているクラス。拡張子が.javaのファイルを作り、そのファイル名とクラス名を合致させなくてはいけない。そのjavaファイルのトップレベルに位置する。
ネストしたクラス
「ネストしたクラス」(Nested class)とはクラスの中にクラスがネストしている状態。トップレベルクラスの内側にstaticキーワードをつけてクラス宣言を行う。
public class Outer { public static class Nested { } }
このネストしたクラスは、トップレベルクラスと同等の機能性を持つ。
クラス名はOuter.Nestedという名前で扱われるが、import文の記載によってNestedという単体の識別子でも利用できる。
内部クラス
内部クラス(Inner class)は外部クラスのインスタンスに紐付く。外部クラスのインスタンス1つに対して複数の内部クラスのインスタンスが生成できる。そして、内部クラスからは外部クラスのフィールドなどを参照することができる。
トップレベルクラスの内側にクラス宣言を行う。staticキーワードはつけない。
public class Outer { public class Inner { } }
この内部クラス、喩えるならば、classとインスタンスの関係に近い。
外部クラスのインスタンスと内部クラスのインスタンスの関係はclassとインスタンスの関係のようで、
classに属するstaticフィールドはすべてのインスタンスから参照できるように
外部クラスのインスタンスフィールドは紐付く内部クラスのインスタンスから参照することができる。*1
そもそも紐付けとは何か。
内部クラスをnewする際、外部クラスのインスタンスを用意して
Outer o = new Outer(); Inner i = o.new Inner();
というように記述する。
このとき、コードがOuterクラスのインスタンスメソッド内であればOuterのインスタンスとしてthisを利用できる。
public class Outer { public class Inner {} public void hoge() { Inner i = this.new Inner(); } }
そして、thisは省略可能なので以下のようなコードで書かれることが多い。
public class Outer { public class Inner {} public void hoge() { Inner i = new Inner(); } }
ところで、staticメソッドの場合、thisは利用できない。
そのため、staticメソッド内で内部クラスをインスタンス化しようとした場合は先のように外部クラスのインスタンスを作ってからnewすることになる。
public class Outer { public class Inner {} public static void main(String[] args) { Outer o = new Outer(); Inner i = o.new Inner(); } }
まだ、InnerクラスはOuterクラスのインスタンス1つにつき複数作ることができる。
Outer o = new Outer(); Inner i1 = o.new Inner(); Inner i2 = o.new Inner(); Inner i3 = o.new Inner();
これらi1-i3のインスタンスは、oのインスタンスフィールドにアクセスできる。
Outer o2 = new Outer(); Inner ix = o2.new Inner();
というように別のOuterのインスタンスを用意してInnerをnewした場合、このixのインスタンスからはo2のインスタンスフィールドを参照する。
なのでixからOuterのフィールドをいじった所でo2には影響を与えるがoには影響を与えない。
Inner型から見てOuter型のことをエンクロージング型(Enclosing type)と呼ぶ。
Innerクラスのインスタンスから見てOuterクラスのインスタンスをエンクロージングインスタンス(Enclosing instance)と呼ぶ。あるいはエンクロージングオブジェクトという表現がされることもある。
エンクロージングインスタンスの操作
InnerクラスからはOuterのインスタンスフィールドを参照することができる。また、インスタンスメソッドを呼び出すこともできる。
このとき、記法としては以下のようになる。
public class Outer { public String str; public void hoge() {} public class Inner { void piyo() { // 外部クラスのフィールド参照 System.out.println(Outer.this.str); // 外部クラスのhoge()メソッド呼び出し Outer.this.hoge(); } } }
Innerクラスのメソッド内でthis.hoge()と書いた場合、このthisはInnerクラス自身のインスタンスを指す。
Outerクラスのインスタンス、つまりエンクロージングインスタンスを参照したい場合、そのOuterクラスのクラス名.thisという表記になる。
InnerクラスとOuterクラスでメソッド名やフィールド名が衝突しない場合、このOuter.thisは省略可能なので先の例は単に
public class Outer { public void hoge() {} public class Inner { void piyo() { // 外部クラスのフィールド参照 System.out.println(str); // 外部クラスのhoge()メソッド呼び出し hoge(); } } }
というように書かれることが多い。
メソッド名やフィールド名が衝突した場合、Outer.thisを明記することで外部クラスのものを参照可能だ。
Outer.thisが省略されている場合、メソッド名・フィールド名はInner側のものが優先となる。
もし、多段の内部クラスを作った場合、直接のエンクロージングインスタンスの他にもエンクロージングインスタンスのエンクロージングインスタンスといった存在が生まれる。
これらにアクセスする場合、やはりクラス名.thisで参照できる。
public class Outer { class Inner { class Inner2 { void hoge() { System.out.println(Outer.this); System.out.println(Inner.this); } } } }
ローカル内部クラス
ローカル内部クラス(Local inner class)はメソッドやコンストラクタなどのブロック内で宣言されるクラス。
これらのクラスはそのブロック内のみがスコープとなるので、ブロックの外側からはクラスの識別子を利用できない。
public class Outer { public static void main(String[] args) { class LocalClass {} LocalClass l = new LocalClass(); // OK } LocalClass field; // コンパイルエラー static void hoge() { LocalClass l = new LocalClass(); // コンパイルエラー } }
IF文などのブロック内で宣言した場合、フロックの外側では利用できない。
if (true) { class LocalClass {} } LocalClass l = new LocalClass(); // NG
ローカル内部クラスは2種類あり、staticメソッド内で宣言されたものと、インスタンスメソッド内で宣言されたものだ。*2
インスタンスメソッド内で宣言された場合、内部クラス同様にOuterクラスのインスタンスへアクセス可能でOuter.thisを利用できる。
staticメソッド内で宣言されたものはこれができない。
無名クラス
無名クラス(Anonymous class)は主に抽象クラスやinterfaceなどに対して名前を付けずにその場で実装を書くもの。
Runnable r = new Runnable() { @Override public void run() { } };
例はRunnableインターフェースを実装する無名クラスをnewしたところ。
最後のセミコロンに注意。これはr = の部分に対してのセミコロンとなる。
new Runnable()の後ろの{}までが無名クラスの宣言なのだが、これはインスタンスを扱うどこにでも記述可能で
通常の代入式の右辺の部分がnew Runnable(){...}に差し替わったと理解するといい。
ソースコードの見た目はいびつになるが、構文解析としてはその部分をまるまるくくりだして落ち着いて考えれば大丈夫。
無名クラスは抽象クラスやinterfaceに対して利用されることが多いが、具象型に対しても使うことはできる。
List<String> list = new ArrayList<String>(){{add("hoge")}};
これは自動テストなどでListなどの初期化が面倒くさい時とかに使われていることがある。
構文的には、無名クラスをつくりインスタンス初期化ブロックの中でaddメソッドを呼んでいるわけだ。
デバッグ用にtoString()だけオーバライドしたりなどいろいろやれるが、わりと邪道なテクニックなので多用するべきではないだろう。
この無名クラスも2種類あり、ローカル内部クラス同様にインスタンスメソッド内で宣言されたものはOuter.thisにアクセス可能となる。
無名クラスはnewするその瞬間に実装を書くので、インスタンスを2つ以上つくりたいような場合はローカル内部クラスにするか内部クラスにするかしよう。
外部メソッドのfinal変数の参照
ローカル内部クラスと無名クラスでは宣言された箇所で参照可能なfinalなローカル変数をクラス内部で参照することができる。
public static void main(String[] args) { final int i = 0; class LocalClass { void hoge() { System.out.println(i); } } }
これは無名クラスなどで実装を書く時などによく利用される。見た目に外のメソッドにある変数が使えるように見えるわけだが、実際にはこのローカル内部クラスや無名クラスのインスタンスはどこかに運ばれてそこで初めてメソッドが呼び出されたりするかもしれない。そのとき、外のメソッドにある変数が参照できるとはどういうことなのか?
端的に言えば、これらの情報はクラス生成時点でこっそり渡されているというカラクリになっている。詳しく知りたければclassファイルの中を覗いてみるなり逆コンパイルしてみるなどするといい。
interfaceと列挙
ネストしたクラスと同様でトップレベルのクラスの下にだけネスト可能。
interfaceとenumはstaticをつけて宣言しても、つけずに宣言しても、static扱いとなる。
トップレベルクラスに併記
名称があるのかよく分からないのだけど、トップレベルクラスと同じjavaファイル内にパッケージプライベートなクラスを併記することができる。
public class Hoge{ } class Piyo { }
と書くとコンパイルすることができる。
まとめ
5種類のクラス宣言についておさらいした。とくに内部クラスからエンクロージングインスタンスを参照するあたりは実際にコードを書いていじらないとしっくりこないかもしれない。
内部クラスの利用例としてはIteratorなどが挙げられる。Listから取得されるIteratorはListとは別の型で別のインスタンスなわけだが、元となるList内のデータを参照することになる。JDK付属のソースを読むとわかるが、このIteratorはList(の実装クラスの共通部にあたるAbstractList)の内部クラスとして作らている。
AndroidのUI開発などをすると設計上、こうしたインスタンス間の紐付けを多用することになるので油断ならない。しっかり基礎を固めておこう。

http://ideone.com/OsrS6
ここであきらめてキャストするとなると、例えばdoJackの処理の戻り値の型をStringとかにできてしまって、型安全性が崩ます。
まだクラス内に閉じていればそういうこともないかもしれませんが、この後Jokerを追加したVisitorを書くわけで、そっちで戻り値の型を間違えてしまう、という可能性は残ります。
ここをキャストなしで通すには、CardVisitorに型引数が無いと駄目なわけですが、それをするとCardに型引数を追加しなきゃいけなくなります。
また、nextの実装もやはり厳しいので、元の文章はそこらへんをうまく扱うために複雑になっていると予想しました(元の文章読んでませんが・・・)。
このままでも各Visitorがメソッドで戻り値を返さず、状態変数を持つことで回避することもできますが、やはり戻り値として値が返せないと不便なので、この方法も微妙な気がします。
http://www.daimi.au.dk/~madst/tool/papers/expression.txt
での構想は
http://d.hatena.ne.jp/Nagise/20110124/1295874192
で利用した技法と同種のものだと思うのですが、いかんせんJavaでは内部クラス的にinterfaceは実装できないからな…。
もっと掘り下げてみたいと思います
id:bleis-tiftさんが既に戻り値型を多相に出来ない問題を詳しく書いておられますが、他にも、
例えばNodeBクラスにNode型のフィールドを持たせたいとき、
public class NodeB implements Node<VisitorB> {
private Node<X> n;
public NodeB(Node<X> n) { this.n = n; }
...
}
これのXのところに何の型を書くべきか、みたいな問題があると思います。
試しに、Xに型を入れるために型パラメータを追加して、
public class NodeB<V extends VisitorB> implements Node<VisitorB> {
private Node<? super V> n;
public NodeB(Node<? super V> n) { this.n = n; }
...
}
こういう定義に変えてみます(他のやり方でも良いですが)。
しかしこれに対する処理を実装しようとすると今度は、
public class VisitorBimpl extends VisitorAimpl implements VisitorB {
@Override
public <V extends VisitorB> void doBの処理(NodeB<V> b) {
//b.getN().doVisitorへの処理の委譲(this); //コンパイルを通らない
System.out.println(b);
}
}
これのthisを渡す部分でコンパイルエラーになります。
ネストした構造を扱ったりすると、こういう風に芋づる式に問題が出てきてしまうので、javaでExpression Problemを解くのは結構難儀かもしれません。
この問題の詳細はこちらに書かれています。 http://www.daimi.au.dk/~madst/ecoop04/main.pdf
もちろんid:Nagiseさんのアプローチがフィットする場面もあるので、そのときはそれで良いと思います。
「こういうシチュエーションで困る」という具体例というのはなかなか挙がってないもので問題認識の第一歩となりますから。
個人的にはこの困難さがどこからくるのかについてもうちょっと理解を深めたいと思っています。見解がまとまったらまたblogにでも公表すると思いますので頃合いを見てまた通りすがってください。
public interface Visitor<V extends Visitor<V>>{
...
public V getThis();
...
}
で、拡張性を維持する為、Visitorの実装には、型引数をつけ続けます。
public abstract VisitorImpl<V extends Visitor<V>> implements Visitor<V>{
...
}
その後、最終的に使用する直前に実装クラスを作り、getThisメソッドを実装します。
public interface VisitorInterface implements Visitor<VisitorInterface>{}
public class UseVisitor extends VisitorImpl<UseVisitor> implements VisitorInterface
...
public V getThis(){
return this;
}
...
}
偶然にも、つい先日Javaでこのあたりの問題を解消するサンプルを書いているので、(コメントも全然書いてないけど)参考していただければと思います。
http://d.hatena.ne.jp/seraphr/20120427/1335506022
https://github.com/seraphr/ExpressionProblemJava
> public class Visitor {
interface になっていませんよ。
参考までに以下、ジェネリックスを使わずにオーバーロードを利用して要素を追加するコードです。
import java.util.ArrayList;
import java.util.List;
interface Visitor {}
interface Node {
void accept(Visitor v);
}
class A implements Node {
public void accept(Visitor v){
((Visitor1) v).visit(this);
}
}
class B implements Node {
public void accept(Visitor v){
((Visitor1) v).visit(this);
}
}
class Visitor1 implements Visitor {
void visit(A a) {
System.out.println("A");
}
void visit(B a) {
System.out.println("B");
}
}
class C implements Node {
public void accept(Visitor v){
((Visitor2) v).visit(this);
}
}
class Visitor2 extends Visitor1 {
void visit(C a) {
System.out.println("C");
}
}
public class TestVisitor {
public static void main(String[] args) {
List<Node> list = new ArrayList<Node>();
list.add(new A());
list.add(new B());
list.add(new C());
Visitor v = new Visitor2();
for(Node n: list) {
n.accept(v);
}
}
}
せっかくなので、感想を残させていただきます。
あまりJavaに詳しくないので、とんちんかんなこと言ってたら申し訳ないです。
このジェネリクスのVisitorパターンを使うと(というか反変性を利用すると)、
どうしてもクライアントコードを抽象的に書くことが難しくなるような気がします。
例えば、NodeAとNodeBのオブジェクトを格納できる変数を定義するときには、
List<Node<? super VisitorB>> nodes
= new ArrayList<Node<? super VisitorB>>();
nodes.add(new NodeA());
nodes.add(new NodeB());
これを処理するときには、
VisitorB vis = new VisitorBimpl();
for(Node<? super VisitorB> n : nodes)
{
n.doVisitorへの処理の委譲(vis);
}
というように、「最も多くの要素を処理できるVisitorインターフェイス」の名前が何回も
出現するようなクライアントコードになると思います。
Visitorのインターフェイスを変更することなく、要素を追加できるようになりますが、
その一方で、クライアントコードの変更が必要となります。
実際にこの手法を使うとなると、この辺のトレードオフはよく考える必要がありそうです。
また、要素を追加するたびに、Visitorインターフェイスの継承関係が深くなっていくので、
分岐が発生したりすると、マージする必要が出たときに面倒になるのではないか、
という気もします。
>tさん
のオーバロード版だと、acceptでVisitorがポリモーフィックに動かないので、
Visitorたる意味がなくなってしまうのではないでしょうか?
(新しいVisitorを追加した場合に機能しない)
> のオーバロード版だと、acceptでVisitorがポリモーフィックに動かないので、
> Visitorたる意味がなくなってしまうのではないでしょうか?
ぐはっ、仰る通りです。
後半を以下のとおりに直させて下さい。orz
interface Visitor1 extends Visitor {
void visit(A a);
void visit(B b);
}
interface Visitor2 extends Visitor1 {
void visit(C c);
}
class Visitor1Impl implements Visitor1 {
public void visit(A a) {
System.out.println("A");
}
public void visit(B a) {
System.out.println("B");
}
}
class C implements Node {
public void accept(Visitor v){
((Visitor2) v).visit(this);
}
}
class Visitor2Impl extends Visitor1Impl implements Visitor2 {
public void visit(C a) {
System.out.println("C");
}
}
すいません、何度も長文を。
>この文が投稿された1998年の11月の時代背景だが、まだJavaのバージョンが1.1だった。
> 1998年の12月に1.2がリリースされているから、その直前のあたりの話だ。Javaに
> 実際にジェネリクスが載ったのはJava5からで、2004年9月30日のことである。
> なので、この原文はまだ確定していないJavaのジェネリクスの実装についての議論であり、
> 掲載されているソースコードは動かそうとしてもコンパイルすることができないので
> 注意が必要。
Philip Wadlerは当時、GJ(JavaにGenericsを導入するプロジェクト)チームの一員であり、触れられているコードは当時のGJ実装では動作したものだと考えられます(GJでの解と書いてあります)。
ちなみに、GJの成果(OderskyのGJコンパイラ含む)は、5年以上の歳月をかけて、ほとんどそのまま(ワイルドカードはGJの後に追加されたものですが)Java 5のGenericsとして取り込まれる事になります。
cf. http://homepages.inf.ed.ac.uk/wadler/gj/