Python.use(better)

前の記事記事一覧次の記事
Python.use(better)


実録:はじめてのプログラミング《17》

逆ポーランド課題(1)リファクタリング

まず「トークンを抽出する」箇所を、独立したクラス Parser に記述します。

class Parser(object):
    def scan(self, e):
        s = Value(e)
        if e in "+-*/":
            s = Op(e)
        return s

そのトークンが、四則演算子(を表わす文字列)なら、それをもとにクラス Op のインスタンスを生成します。それ以外は、数値(を表わす文字列)とみなして、それをもとにクラス Valueインスタンスを生成します。

次に「抽出したトークンに『固有』の処理」を、独立したクラス Value/Op に記述します。

class Value(Token):                # ver.1b
    def eval(self, client):
        client.eval_Value(self)        

class Op(Token):                   # ver.1c
    def eval(self, client):
        client.eval_Op(self)

さらに「抽出したトークンに『共通』の処理」を、独立した抽象クラス Token に記述します。そして実際には、これらの具体的な処理を client に委ねます。
《Note》 これは「ダブルディスパッチ」と呼ばれる古典的な手法のひとつです。メッセージのやり取りを繰り返すことで「抽象的な問題を段階的に具体化しながら解決したい」ときに便利です。《ひよ子》□

class Token(object):                # ver.1a
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return str(self.value)
    def eval(self, client):
        raise NotImplementedError(
            "class {0}: def eval(self, client)".format(self.__class__.__name__))

raise 文を使って例外 NotImplementedError を生成することで、プログラマーへの注意を促します。試しに、クラス Op のメソッド eval をコメントにする(か、他のメソッド名に変更する)と、

>>> ex()
>>> 3 4 +
Traceback (most recent call last):
  ...
NotImplementedError: class Op: def eval(self, client)

この出力結果から、クラス Op のメソッド eval が未定義であることが(プログラマーに)分かります。

何が問題か

メソッドの実現を見ると、その違いは「メソッドの名前」だけだと分かります。そこで、これらの違いだけを捨象して、さらにリファクタリングを進めます。

Tips:プログラム vs. プログラミング

例外を生成する目的のひとつは「プログラム〔product:成果〕が正しく機能しているか」を検証することと、もうひとつは「プログラミング〔process :過程〕が正しく機能しているか」ことを検証することです。前者は「利用者」に対するメッセージであり、後者は「開発者」に対するメッセージです。複数の利用者が関わるシステム(利用)環境、複数の開発者が関わるシステム(開発)環境にとって「どのような支援機能が望ましいか」これを機に再考してみてください。Oh 脳《053》プログラム(算譜)とプログラミング(作譜) - 続・ひよ子のきもち《ひよ子》□

Tips:抽象クラスの意義

「抽象」クラスの意義は、文法だけで規定する性質のものではありません。さらに、冗長な記述 abstract が不要になるので(Java/C# などのように)言語仕様そのものが「メタボリック症候群」に陥ることもありません。《ひよ子》□

読者への課題

変数を扱えるように拡張してください。たとえば、

    a = 3 + 4

変数への代入を表わす式は、

    3 4 + a =

このように記述するなど、いくつかの表現が考えられます。■

Last updated♪2009/03/09