例題で学ぶデザインパターン #余録:〈GoF〉Iterator を反面教師に

前の記事記事一覧次の記事

例題で学ぶ Jython/Swing デザインパターン《Jython2.5》
GoFIterator を反面教師に

《著》越智ことり+小粒ちゃん《監修》小泉ひよ子とタマゴ倶楽部
第1版♪2003/05/23 ● 第2版♪2009/04/03

■ 概要

例題により、アプリケーションを作成する過程を通して、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》GoFIterator には、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 のコードの断片を構成する)各トークンが順に出力されます。

java.util.Enumeration

メソッド elements によって、Vector の各要素を順に参照する Enumeration が得られます。

"""
    Enumeration e = 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 が得られます。

"""
    Iterator it = 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)を知る必要はありません。

Last updated♪09/06/17