愛の十字架:隘路を切り開く
「無くて七癖…」と言われますが、頭では分かっていても(無意識に)身体が反応します。tryの十字架を背負いそうになったら、この話を思い出してください。
まず、生命体を単なる値ではなく、オブジェクトとして表現することから始めます。

class Life:
def __init__ (self, x, y):
self.x = x
self.y = y
self.present = False
self.future = False
self.neighbor = []
def __repr__(self):
s = StringBuffer()
s.append(self.xyString())
s.append(`self.present` + "->")
s.append(`self.future` + ":")
for e in self.neighbor:
s.append(e.xyString())
s.append(":" + `self.neighbors()`)
return `s`
def __str__(self):
return repr(self)
任意の生命体は、座標x/yの他に、現在presentと次世代futureの生死の状態を持つインスタンスと見なせます。ここでは、生命体の左上の隅をその座標値とします。インスタンスに固有の情報を文字列として出力すると、次のようになります。
>>> (4,3)False->False:(3,2)(4,2)(5,2)(3,3)(5,3)(3,4)(4,4)(5,4):0
この結果は、メソッド__str__で規定した文字列表現から得られます。
配列に値を保持させるのではなく、その対象を「オブジェクト」として実現します。生命体は、単なるbool型の値ではありません。自分が次世代で生存できるかは、生命体自身で判断します。つまり、オブジェクト自身が「思考」すると共に、周りの仲間とも相談するというわけです。
デザインパターン:Null Object
GoFのカタログにはない、古典的なパターンの一つにNull Objectがあります。今回はこのNull Objectを利用して、境界の外側を定義します。
class Life:
def xyString(self):
return "(%r,%r)"%(self.x, self.y)
def reverse(self):
self.present = not self.present
生命体には、2つのメソッドを定義します。メソッドxyStringは、座標を表す文字列をリターン値とします。生成した生命体の位置は不変です。メソッドreverseは、生命体の状態色(生存 Color.red/死滅 Color.white)を規定します。

先の連載第2回で紹介したNull Objectは、境界の外側を特殊な生命体と見なします。これは、すべてのオブジェクトを平等に扱いつつ、その個性を尊重するという管理手法に適います。Null Objectに関する広範な論議は、Smalltalkが参考になります。
インスタンスに固有のメソッド定義
Null = Life(0, 0) Null.xyString = lambda: "()" Null.reverse = lambda: None
境界外の生命体Nullには、2つのメソッドを再定義します。メソッドxyStringは、座標を表示する必要がないので、文字列 "()" をリターン値とします。メソッドreverseは、何もする必要がないので、その本体は空Noneとなっています。すると、Nullは、他の生命体と違う個性を発揮します。
Nullの実体は一つだけしか存在しませんが、あたかも境界外を取り囲むかのように図示してあります。条件式や例外処理など、煩雑で面倒な処理を一手に引き受けるNull Objectは、目立たず(逆に目立っては困る)頼もしい限りの存在です。まるで舞台に登場する黒子(存在しないものと見なされる)のように。
class Life:
def paintItem(self, g):
edge = self.Edge
x = self.x * edge
y = self.y * edge
g.setColor(self.fillColor())
g.fillRect(x, y, edge, edge)
g.setColor(Color.black)
g.drawRect(x, y, edge, edge)
def fillColor(self):
return (Color.white, Color.red)[self.present]
メソッドpaintItemは、生命体を描きます。背景を描くために、fillColorを再利用します。生命体が生存なら赤Color.redで、死滅なら白Color.whiteでそれぞれ表示します。
Nullを導入すると、配列の添字が範囲を超えたときの例外に対処するtry文が不要になるので、コードの見通しがよくなります。Nullの存在を確認するには、例えば、ウィンドウの大きさを変形させて、右/下の境界外をクリックします。すると、次のような情報が出力されます。
>>> ()False->False::0
