リファクタリング: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 だけを使って、コードを記述できるようになります。つまり、要求仕様の変更に際して、その影響を受ける恐れがなくなります。