例題で学ぶデザインパターン #余録:〈GoF〉Iterator を反面教師に
例題で学ぶ Jython/Swing デザインパターン《Jython2.5》
〈GoF〉Iterator を反面教師に
■ 概要
例題により、アプリケーションを作成する過程を通して、Jython/Swing によるデザインパターンを習得します。
この課題では、Swing/GUI を使って階層構造を持つ情報を提示します。〈GoF〉Composite/Iterator/Visitor/Command パターンを導入すると、if/for 文によるコードの汚染、配列の境界問題が解消されるので、要求仕様の変更にも柔軟に対処でき、簡潔で見通しの良いコードを記述できるようになります。
《Note》JPython1.1.x/Jython2.1.x 用に作成したセミナー課題を、Jython2.5 で再構成しました。
何が問題か:特定のプロトコルに依存するコードの汚染
Jython には伝統的な(C言語風の)for 文がありません。それには、理由があります。for 文に伴うコードの汚染には、さまざまな要因が考えられます。ここではその一因として、特定のプロトコルに依存する場合を考察します。
《Note》〈GoF〉Iterator には、4つ組のプロトコル(First/Next/IsDone/CurrentItem)が規定されています。しかし、特定のプロトコルに依存して「コードが汚染される」一因にもなります。
■ 事例:Vector
まず、次のコードの断片を見てください。
c = list("ABC") v = Vector(c) for e in v: print e
これは「ベクトル v から各要素 e を取り出して、それを出力 print したい」ことを端的に表現しています。冗長な表現は不要です。
《Note》for..in を利用すると、簡潔で見通しの良いコードを記述できます。参照していない要素が残っているかを判定したり、次の要素を得たりするには、どうするのか(how)を知る必要はなく、何をしたいか(what)を記述するだけです。特定のプロトコルに依存しないので、コードが汚染されなくなります。
これを実行すると、次のように、
A B C
3つの要素が順に出力されます。
《Note》ベクトル v が保持する、各要素 e を参照したい(what)ときに、それをどのように実現しているか(how)を知る必要はありません。すると、情報隠蔽の原則に沿って、内部構造に依存しない抽象的な表現が可能になります。C言語で採用された、伝統的な for 文との違いはここにあります。
■ 事例:StringTokenizer
s = """ StringTokenizer st = new StringTokenizer(s); while (st.hasMoreTokens()) System.out.println(st.nextToken()); """ e = StringTokenizer(s) # Java-like while e.hasMoreTokens(): print e.nextToken()
Jython では、これと同じことを、次のように記述できます。
for e in StringTokenizer(s): # Jython print e
これは「文字列(Java のコードの断片)s から各トークン e を取り出して、それを出力 print したい」ことを端的に表現しています。冗長な表現(StringTokenizer に依存するプロトコル)は不要です。どちらのコードを実行しても、次のように、
StringTokenizer st = new StringTokenizer(s); while (st.hasMoreTokens()) System.out.println(st.nextToken());
■ java.util.Enumeration
メソッド elements によって、Vector の各要素を順に参照する Enumeration が得られます。
""" Enumeratione = v.elements(); while (e.hasMoreElements()) System.out.println(e.nextElement()); """ e = v.elements() while e.hasMoreElements(): print e.nextElement()
前述した for 文と違って、各要素を参照する(what)ためには、特定のプロトコル(how)を知る必要があります。メソッド hasMoreElements で、まだ参照していない要素が残っているかを判定するとともに、メソッド nextElement で、次の要素が得られます。
■ java.util.Iterator
メソッド iterator によって、Vector の各要素を順に参照する Iterator が得られます。
""" Iteratorit = v.iterator(); while (it.hasNext()) System.out.println(it.next()); """ it = v.iterator() while it.hasNext(): print it.next()
前述した for 文と違って、各要素を参照する(what)ためには、特定のプロトコル(how)を知る必要があります。メソッド hasNext で、まだ参照していない要素が残っているかを判定するとともに、メソッド next で、次の要素が得られます。
《Note》このほかにも、前述した StringTokenizer では、hasMoreTokens/nextToken など、似て非なるさまざまなプロトコルに精通する必要があります。また、さらに新たなプロトコルが規定されるたびに、既存の「コードが汚染される」のを防げません。すると、仕様の変更に伴う「メンテナンスの悪夢」が待ち受けています。
Iterator の効用
Jython では、Vector に対しても Iterator の効用が期待できます。
■ 組み込み関数 map との融合
>>> print map(ord, v) [65, 66, 67]
組み込み関数 map の第2引数にベクトル v を指定すると、各要素を順に参照するので、ASCII コード値を列挙したリストが得られます。
■ 組み込み関数 filter との融合
>>> print filter(lambda e: ord(e)%2, v) [u'A', u'C']
組み込み関数 filter の第2引数にベクトル v を指定すると、各要素 e を順に参照するので、ASCII コード値が奇数になる要素だけを列挙したリストが得られます。
■ 組み込み関数 reduce との融合
>>> print reduce(lambda s,e: e+s, v, "") CBA
組み込み関数 reduce の第2引数にベクトル v を指定すると、各要素 e を順に参照するので、順序を入れ換えた文字列が得られます。
■ 内包との融合
>>> [ord(e) for e in v] [65, 66, 67] >>> [e for e in v if ord(e)%2] [u'A', u'C'] >>> [e+str(n) for e,n in zip(v,range(3))] [u'A0', u'B1', u'C2'] >>>
内包を利用すると、任意の条件を満たす、要素を列挙したリストが簡単に得られます。
■ ジェネレーター式との融合
>>> dict((e,ord(e)) for e in v) {u'C': 67, u'B': 66, u'A': 65}
Tips
》作業中です《
《Note》前述した for 文と同様に、各ノードを参照する(what)ために内部構造(how)を知る必要はありません。