OOP への道:if と別れる50の方法《07》The Java™ Tutorials の事例から
Python.use(better) # OOP への道 《Python3.1, Jython2.5.0, IronPython2.6.x》
The Java™ Tutorials の事例から
#2: テンプレートによる抽象表現
■ 概要
if/switch への未練を断ち切るのは容易ではありません。
伝統的なC言語風の for 文や悪名高い switch 文、配列の呪縛から解かれ、オブジェクト指向プログラミング〔OOP〕の醍醐味を堪能するための準備を行います。
The if-then-else Statement
》作業中です《
class TemplateDemo(object): def __init__(self, keys, values, default, cond): self.keys = keys self.values = values self.default = default self.cond = cond def index(self, s): for n, e in enumerate(self.keys): if self.cond(s, e): return self.values[n] else: return self.default def example(): ## ---------------------------------------- print("#", "-"*20, "HexDemo") demo = TemplateDemo( "0123456789ABCDEF", range(16), -1, lambda s, e: s == e, ) for e in "0123456789ABCDEF@": print("Hex = %2d"%demo.index(e), ":", e) ## ---------------------------------------- print("#", "-"*20, "IfElseDemo") demo = TemplateDemo( [90, 80, 70, 60], "ABCD", "F", lambda s, e: s >= e, ) for e in range(0, 101, 10): print("Grade = %s"%demo.index(e), ":", e)
HexDemoとIfElseDemoとの違いを、インスタンス属性ごとに検証します。
- インスタンス属性 .keys に対して、実引数として、文字列"0123456789ABCDEF"と数列[90, 80, 70, 60]とが与えられます。これらに共通するのはともにシーケンスであり、これらの相違は各要素が文字か数かです。
- インスタンス属性 .values に対して、実引数として、数列 range(16)と文字列"ABCD"とが与えられます。やはり、シーケンスとしては同じですが、各要素は文字と数とで異なります。
- インスタンス属性 .default に対して、実引数として、-1と"F"とが与えられます。これらは数と文字とで異なりますが、単一のオブジェクトということでは同じです。
- インスタンス属性 .cond に対して、どちらも bool をリターン値とする関数オブジェクトです。
テンプレートとして規定された、抽象メソッド index の役割に着目すると、
- 任意のシーケンスself.keysから各要素eを参照するとともに、
- 条件式self.cond(s, e)を使って判定を行うとともに、
- 任意のシーケンスself.valuesから取り出した要素をリターン値とすることと、
- 条件を満たさない場合には、規定値をリターン値にすると、
という、各要素の型には依存しない抽象表現になっているのが分かります。これらの条件を満たすなら、どのような場合にも対処できます。それ以外の場合には、何も保証していません。
これを実行すると、次のような結果が得られます。
>>> example() # -------------------- HexDemo Hex = 0 : 0 Hex = 1 : 1 Hex = 2 : 2 Hex = 3 : 3 Hex = 4 : 4 Hex = 5 : 5 Hex = 6 : 6 Hex = 7 : 7 Hex = 8 : 8 Hex = 9 : 9 Hex = 10 : A Hex = 11 : B Hex = 12 : C Hex = 13 : D Hex = 14 : E Hex = 15 : F Hex = -1 : @ # -------------------- IfElseDemo Grade = F : 0 Grade = F : 10 Grade = F : 20 Grade = F : 30 Grade = F : 40 Grade = F : 50 Grade = D : 60 Grade = C : 70 Grade = B : 80 Grade = A : 90 Grade = A : 100
ここまで来ると、switch 文の代用表現として、辞書 dict が有効になるのが分かるでしょう。実際に、dict.get などは、これらの抽象メソッドと同様の処理を行っているのが分かります。
これで、OOP への道を探求する「準備」は整いました。次回から、いよいよ「佳境」に入ります。
Tips
》作業中です《
@
OOP による解法への準備
OOP による解法を示す前に、前述した事例を再考します。
■ 事例:16進表記
前述した《事例》をクラスを用いて再構成すると、次のようになります。「指折り数えて何番目になるか」というメッセージを忠実に再現すると、次のようになります。
class HexDemo(object):
インスタンス属性 self.hex が保持する各各要素(長さ1の文字列)e を順にたどりながら、条件式 s == e を満たすときのオフセット n をリターン値とします。一致するものがないときには、-1 をリターン値とします。
各要素を順にたどりながら、条件を満たすときの値を得ることが「指折り数えて何番目になるか」に相当します。
これを実行すると、次のような結果が得られます。
def ex2(): demo = HexDemo("0123456789ABCDEF") for e in "0123456789ABCDEF@": print("Hex = %2d"%demo.index(e), ":", e) >>> ex2()
■ 事例:5段階評価
class IfElseDemo(object):
各要素を順にたどりながら、条件を満たすときの値を得ることが「指で寸法を測るといくつになるか」に相当します。
これを実行すると、次のような結果が得られます。
def ex1(): >>> ex1()
>>> SwitchDemo2 1900 : [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 365 1910 : [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 365 1920 : [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 366 1930 : [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 365 1940 : [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 366 1950 : [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 365 1960 : [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 366 1970 : [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 365 1980 : [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 366 1990 : [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 365 2000 : [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 366