SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

よろずプログラマーのためのPython導入ガイド

Java meets Python - 第3回 構造体とクラス:二分木

よろずプログラマーのためのPython導入ガイド (5)


ifと別れる50の方法:Null Object

 先の実現方法では、左右の部分木を持たないことを判定するのに、if文が必要でした。伝統的な構造化プログラミング(SP)では重宝されるif文も、オブジェクト指向プログラミング(OOP)では逆にそれが手枷足枷となります。if文を卒業すると、また一歩OOPの神髄へと近づけます。if文の呪縛から逃れるにはいくつかの術がありますが、ここでは、Null Objectを紹介します。Null Objectに関する広範な論議は、Smalltalkを参考にするのが良いでしょう。

class BinTree:
    tab = " "*6

 クラスBinTreeでは、抽象クラスとして子孫クラスに共通の特性を規定します。ここでは、インスタンス変数tabがタブに相当する文字列を保持します。

class TNone(BinTree):
    def __str__(self):        return ""
    def __len__(self):        return 0
    def max_len_word(self):   return 0
    def table(self):          return ""
    def _table(self, format): return ""
    def tree(self):           return ""
    def _tree(self, indent):  return "%s+--\n"%(self.tab*indent)

 クラスTNoneは、左右の部分木を持たない「末端にある」二分木のノード(葉)を表わします。左右の部分木を持たないので、どのメソッドも単純に実現できます。

class Tnode(BinTree):
    none = TNone()
    def __init__(self, word="", count=0, left=none, right=none):
        self.word  = word
        self.count = count
        self.left  = left
        self.right = right
    def __str__(self):
        return "%s(%d,%s,%s)"%(
            self.word, self.count,
            (self.left , "")[not self.left ],
            (self.right, "")[not self.right],
            )
    def __len__(self):
        node = 1
        left  = len(self.left)
        right = len(self.right)
        return left + node + right
    def max_len_word(self):
        node = len(self.word)
        left  = self.left .max_len_word()
        right = self.right.max_len_word()
        return max(left, node, right)
    def table(self):
        format = "%%%dr: %%d\n"%(self.max_len_word()+2)
        return self._table(format)
    def _table(self, format):
        node = format%(self.word, self.count)
        left  = self.left ._table(format)
        right = self.right._table(format)
        return left + node + right
    def tree(self):
        return self._tree(0)
    def _tree(self, indent):
        node = "%s+-- %r(%d)\n"%(
            self.tab*indent, self.word, self.count)
        left  = self.left ._tree(indent+1)
        right = self.right._tree(indent+1)
        return left + node + right

 クラスTnodeは、左右の部分木を持つ「末端にない」二分木のノード(節)を表わします。インスタンス変数noneは、葉TNone()を保持します。これは、メソッド__init__において、生成したてのTnodeインスタンスを初期設定するときに、左右の部分木の規定値として利用されます。他のすべてのメソッドを実現するときには、左右の部分木を持たないこと(境界条件)を判定していた、すべての冗長なif文を排除できます。そのため、見通しの良い簡潔なコードを記述できます。

Let's KISS (Keep It Simple, Stupid.)
 各作業は単純でも、それらが混在するだけで、不条理な苦難を強いられるものです。TNone/Tnodeを区別せずに、BinTreeですべてを仕切ろうとすると、事態はさらに混沌とします。if文が至る所に散在すると、コードの見通しが悪くなるだけでなく、要求仕様の変更に伴うメンテナンスの悪夢(西暦二千年問題)が蘇ります。単純な二分木の例題でも一苦労なのに、実際のシステム開発で同じ病巣を抱えたいと思いますか。

ifと別れる50の方法:Dynamic Class

 先と同じく、if文の呪縛から逃れる術として、Dynamic Classを紹介します。Dynamic Classに関する広範な論議も、Smalltalkを参考にするのが良いでしょう。

class Tnode: tab = " "*6

 クラスTnodeは、左右の部分木を持つ「末端にない」二分木のノード(節)を表わします。この段階では、まだメソッドを再定義していません。

TNone = Tnode()
TNone.__str__      = lambda: "."
TNone.__len__      = lambda: 0
TNone.max_len_word = lambda: 0
TNone.table        = lambda: ""
TNone._table       = lambda format: ""
TNone.tree         = lambda: ""
TNone._tree        = lambda indent: "%s+--\n"%(Tnode.tab*indent)

 変数TNoneは、クラスTnodeのインスタンスの一つで、「末端にある」二分木のノード(葉)を表現します。左右の部分木を持たないので、どのメソッドも単純に実現できます。これらはみな、TNoneインスタンスに固有のメソッドとなります。

class Tnode:
    tab = " "*6
    def __init__(self, word="", count=0, left=TNone, right=TNone):
        self.word  = word
        self.count = count
        self.left  = left
        self.right = right
    ...

 クラスTnodeは、TNoneインスタンスを生成した後でメソッドを再定義します。TNoneインスタンスは葉を保持します。これは、メソッド__init__において、生成したてのTnodeインスタンスを初期設定するときに、左右の部分木の規定値として利用されます。他のすべてのメソッドを実現するときには、境界条件を判定していた、すべての冗長なif文を排除できるので、見通しの良い簡潔なコードを記述できます。

継承に警鐘を鳴らす

 各インスタンスには(共通するクラスで定義されている/いないに関わらず)固有のメソッドを規定できます。クラス/クラス間のメソッド継承では、親クラスで規定したメソッドを再定義/再利用できます。同様に、クラス/インスタンス間のメソッド継承では、クラスで規定したメソッドを再定義/再利用できます。

 TNoneは、同じクラスTnodeのインスタンスでありながら、他のノードとは世代が異なります。つまり、世代を越えたインスタンスが、相互に情報を交換しながら、共通する問題の解決に貢献します。すると、Null Objectでは、抽象クラスを含めて複数のクラスのインスタンスが相互作用しますが、Dynamic Classでは、世代の異なる同一のクラスのインスタンスが相互作用します。

 TNone(および前項で紹介したTnode.none)は、sole instanceの一例です。GoFのデザインパターンでは、これがSingletonとして登録されています。

OOPへの道:初めの一歩、末の千里

 皆さんは今、正統な血を引くOOPの教典を求めて、天竺へと続く道の「初めの一歩」を踏み出したものとします。すると、次のような道標に遭遇するかもしれません。

オブジェクト指向の`正しい`間違い方 1
 《症状 1a》これはJavaで書かれてるからオブジェクト指向だ
 《症状 1b》これはCで書かれてるからオブジェクト指向でない
  
 セミナー会場やコンサルティング現場では、Javaで書かれていても、オブジェクト指向とは呼べない代物に遭遇します。逆に、Cで書かれていながら、OOPの精神を受け継ぐ秀作もあります。C言語でもOOPは可能です。論より証拠。初期のC++は、C言語に変換されてから、コンパイルされていました。

 最初の分岐点を過ぎると、今度は、次のような道標に遭遇するかもしれません。

オブジェクト指向の`正しい`間違い方 2
 《症状 2》クラスは型紙と同じだ
  
 クラスはただの「型紙」ではありません。確かに、クラスには型紙としての特徴があります。しかし、それはクラスが持つ特徴の一つに過ぎず、他のインスタンスと同格に「一人前のオブジェクト first-class object」としても扱われます。インスタンスであれば(オブジェクトの特徴を活かして)実行時にその状態(属性)を変化させます(そうでないと存在意義がありません)。クラスも同様に(オブジェクトの特徴を活かして)実行時にその状態(操作)を変化させます(だからこそ存在意義があります)。クラスは、実行時に成長を続けるので、動的な問題解決に適した(型紙にはない)特徴を発揮できます。これらは、agile言語には欠かせない特徴の一つです。

未熟な言語仕様のツケを清算するのはアナタ?

 プログラミング言語を設計するときの背景に、クラス/インスタンスの違いを包括する概念モデルがあると「実行時に属性を変更できるなら、その操作も変更できて当然」と考えます。Ruby/Pythonでは、Java/C#に課された不条理な制約がなく、洗練されたOOPの世界を堪能できます。未熟な言語仕様のツケを、プログラマーが清算する必要もありません。

 海外に長く滞在して外国語に習熟するにつれて、日常会話で日本語を使う機会も減ってきます。同様に、OOP言語に習熟するにつれて、if/for/配列(goto/switchは論外)を使う機会も減ってくるものです。それは、成長を自覚できる証にもなります。身近に「C言語などの非OOP言語と違って、Javaでは」と語る人がいたら、Javaでプログラムは書けるとしても「OOPを理解できているか」確認しておく必要があるかもしれません。

 OOPの奥義を極める旅の途中では、Java/C#の常識が通用しない場面に遭遇することがあります。例えば、クラスをインスタンスとして扱えたり、逆に、インスタンスをクラスに変身させたり。あるクラスのインスタンスが養子縁組をして、他のクラスのインスタンスになったり。もう、何でもありの世界です。幾度か、立ち止まって思案に暮れるのもいいでしょう。幾度も、振り出しに戻ることがあるかもしれません。それでも焦らずに、一歩ずつ着実に前進あるのみです。

 洗練されたOOPの支援機能が得られるなら、アジャイル開発はさらに加速します。皆さんも、サイヤ人から超サイヤ人に変身できるのです。天竺へ続く道は、まだ遠く険しいかもしれませんが、ドラゴンボールを一つずつ探す道程を楽しんでください。

オブジェクト指向の`正しい`間違い方 3
 《症状 3》C言語の関数をJavaではメソッドと呼ぶ
  
 この症状は、実際の開発現場でもよく見掛けます。if文の呪縛から逃れたいなら、その術を探す前に、関数とメソッドとの違いを理解しておくことが肝要です。それが、末の千里へと繋がります。今はまだ、初めの一歩を踏み出したばかりです。その話題は、後の連載で紹介します。
修正履歴

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
よろずプログラマーのためのPython導入ガイド連載記事一覧

もっと読む

この記事の著者

小泉ひよ子とタマゴ倶楽部(コイズミヒヨコトタマゴクラブ)

http://tamago-club.cocolog-nifty.com/「楽しくなければ仕事じゃない」が私たちのモットー。99%の苦悩の連続も、1%の成功に報われます。だからこそ、この仕事が楽しくて仕方がないのです。楽をするための努力なら惜しみません。何もせず楽をしているのと、努力をしたから楽ができるのと...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

河野 かえる(カワノ カエル)

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

河野 めだか(カワノ メダカ)

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

後藤 いるか(ゴトウ イルカ)

goto 不要論などもあって、この業界では何かと肩身の狭い思いをしている、全国の後藤さんに代わって「GoTo はいるか(ごとうは不要)」なんて言わないでくださいね。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1800 2007/12/05 09:54

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー