リファクタリング:Python におけるクロージャーの隘路

Python では、メソッド関数を使って(いくつかの制約はあるものの)クロージャーを記述できます。そこで、Smalltalk 版には及ばないものの、汎用性を損なわず簡潔な表現が可能となるように、Python 版でのリファクタリングを実践します。これを機に、リファクタリング前のコードの断片 《付録》Tetrimino3.py - 続・ひよ子のきもち との違いをつぶさに観察するのも一興です。

class Omino(object):
    def shift(self, n=0):
        ...
        def _points(s):
            return XPointCollection([XPoint(e.X + n*W, e.Y) for e in s])
        for s in self.shape:
            s.update(_points, s)
        ...    

メソッド _points では、クロージャーとしての特徴を活かして、テトリミノを構成する4つの多角形の各頂点の(移動後の)座標を与えます。引数 s の値は、実行時に確定します。そして、それは System.Windows.Shapes.Polygon の特徴を反映することになります。つまり、WPF に依存するコードの断片を、実行時に注入します。
ここで着目して欲しいのは、それを実行する状況(where)には依存するものの、それを実現する方法(how)には依存しないことです。これによって、アプリケーションに固有の情報と、アーキテクチャーに固有の情報とを、分割統治するのが容易になります。つまり、要求仕様の変更に際して、その影響をクロージャーの中に封じ込められます。

class Omino(object):
    def rotate(self, sign):
        ...
        def _points(n):
            return self.matrix1[self._rotate1(n)]
        for e, n in zip(self.shape[self.offset:], self.mino1):
            e.update(_points, n)
        ...        
class PiOmino(SigOmino):
    def rotate(self, sign):
        super(PiOmino, self).rotate(sign)             
        def _points(n):
            return self.matrix2[self._rotate2(n)]
        for e, n in zip(self.shape[len(self.mino1)+1:], self.mino2):
            e.update(_points, n)

テトリミノの回転についても、同様です。テトリミノを構成する4つの多角形の各頂点の(移動後の)座標を与えるとともに、引数 n の値は、実行時に確定します。そして、それは 10 種類のテトリミノの形状 mino1/mino2 を反映することになります。つまり、ゲームに依存するコードの断片を、実行時に注入します。

class XPolygon:
    def update(self, points, *args):
        self.value.Points = points(*args).value

ここで着目して欲しいのは、それを実行する状況(where)を知る必要がなく、それを実現する方法(how)だけを知っていることです。これによって、アプリケーションに固有の情報(クロージャー)points(*args) には依存しない、アーキテクチャーに固有の情報(プロパティー).Points だけを使って、コードを記述できるようになります。つまり、要求仕様の変更に際して、その影響を受ける恐れがなくなります。