継承VS移譲:子だくさんか、外注職人選択か?
このワークシートはMath by Codeの一部です。
「テンプレート」というと何を連想しますか?
セルロイド板に△や□や〇の穴があいていて、図形をかくのが苦手な人でも、
きれいな図形がかけたりします。
「安心して使えるし、好きな色や筆記用具で変化の自由が楽しめます」。
これにあきたらないと、シールを貼ったりとちがう方向にいく場合もあるでしょう。
メールをかくときにテンプレート書式が用意されていて、そこに順番に埋めていくだけで、それなりに見える。もちろん、中身の「自由度は高い」ので、制約されているという気持ちにはならないでしょう。
病院にいけば、初診の場合はアンケート用紙のような質問項目に順番に記入選択をすると、
お医者さんが考えながら質問する、「手間が省けるから便利」ですね。
このように、変えられない骨格を決めておき、一部分だけ中身を具体的にうめていくのがテンプレート、
書式というものですね。
今回はテンプレートメソッド周辺をやります。
周辺と書いたのは、「型枠法」パタン、テンプレート法パタンは「下請け工場」パタンと似ているし、「ストラテジー」、戦略メソッドとも共通するものがあるからです。
それらの比較も含めて3つのつながりも探っていこう。
1.下請け工場なしに軽やかに見栄えを変える型枠法
<FactoryMethodの思い起こし>
「下請け工場手法、FactoryMethod:
インターフェースだけのクラスを作り、「インスタンス化」はサブクラスに任せる。」
作るものが数Bで、くりっくされると画面に数を追加するという工場Aがペアになったフレームワークでしたね。フレームワークはマウスクリックのときの動作だけが具体で、あとはただのインターフェースでした。だから、フレームワークがゆるく軽い分だけ、数Bの具体化は自由に広げることができたのでした。
しかし、Bのクラスのサブクラスの数だけ工場Aが必要になりました。
カスタマイズの自由度が高い分だけユーザがサブクラスを作る手間は膨大になります。
<軽やかに見栄えを変えるTemplateMethod>
下請け工場手法が重くなる原因は何でしょうか。「インスタンス」を作るからです。
ただの振る舞いで済むようしたらどうでしょうか。
つまり、数はただの文字列だと割り切るのです。
テンプレートメソッド、型枠法は新しいインスタンスを作るのではなく、振る舞いだけ変えるのです。
「型枠手法、TemplateMethod:
アルゴリズムの構造は変えずに、アルゴリズムの骨格を定義して、詳細はサブクラスごとに定義できるようにする。」
文字列を受け取って流すということであれば、
文字列を囲んだり、
文字列の先頭にラベルをつけたり、
文字の長さが決まってないという自由さを逆手にとり、
型枠には文章でさえ流せる。
まさに、不自由の中の安定した自由がある。
# Template.py
# ==========================================
# 【クラスA】ツール(イベント処理で数の画面追加)
# ==========================================
# ただの文字列の画面表示と割り切れば、どんな数でも文字列のdata
# だから、数のインスタンス化は不要
class CanvasTemplate:
def __init__(self, data):
self.data = data
def on_mouse_click(self):
print("[CanvasTemplate],clicked ", end="")
print (self.draw_number())
def draw_number(self):
raise NotImplementedError("子クラスで、自分が作る数を指定してください")
# ==========================================
# 【カスタマイズ】利用者が作ったコード
# ==========================================
# サブクラスを作る
class Natural(CanvasTemplate):
def draw_number(self):
return f"自然数 {self.data}"
class Fraction(CanvasTemplate):
def draw_number(self):
return f"分数 {self.data}"
# ==========================================
# 実行
# ==========================================
nat=Natural("2027")
nat.on_mouse_click()
fr=Fraction("20/27")
fr.on_mouse_click()
[OUT]
[CanvasTemplate],clicked 自然数 2027
[CanvasTemplate],clicked 分数 20/27
2.戦略パタンはパラダイムシフトか?
<垂直解決から水平解決へ>
戦術ではなく戦略Strategyというからには、
小手先の変更ではありません。
パラダイムシフト?
どういうことでしょうか。
対象の種類を増やすことが下請け工場の増加につながったFactoryMethod。
ユーザのサブクラス化が自由さを生むと同時に負担が数と工場のセットで増えました。
結果としてほしい物がただの表示の種類の多様化だと割り切って平板な枠組みにして対応したものがTemplateMethod、型枠法だった。やはりユーザのサブクラス化が自由さを生むと同時に負担は増えます。
つまり、どっちも、サブクラスをユーザたくさん作る、
ユーザが子だくさんになることで解決するという路線です。
継承、サブクラス化という垂直な依存関係、上下関係で横に膨らむUMLではなく、
移譲という水平な依存関係、つまり、外注で実行するUMLになるのが戦略パタンです。
「戦略 Strategyパタン:
アルゴリズムの集合を定義して、それぞれをカプセル化することで交換できる。
利用者からは独立にアルゴリズムを変更できる。」
パラダイムシフトというと、古典物理から相対性理論へ、古典物理から量子力学へ
などのような大転換という意味あいがありますね。
歴史の後戻りはしません。
しかし、ユーザーから見たらどうでしょう。
この程度の問題なら近似値でいいから、古典物理でやろう。かなり精密にしたいから量子力学も取り入れよう。などと、状況に応じて道具を使い分けます。
それに、1人の人間があらゆる最先端の科学の専門家でいられるはずはありません。
だから、その都度、必要に応じて道具として知の職人さんを入れ替えて使う
という発想があるでしょう。
だから、戦略パタンは「外注職人選択パタン」と言えるでしょう。
お金持ちのテレビ局のワイドショーで、次々とコメンテーターを入れ替えているみたいですね。
# Strategy.py
# ==========================================
# 【クラスN】職人クラスとサブクラス
# ==========================================
class NotationStrategy:
"""数式表現の『脳みそ』の契約。全員が同じ地平(水平)に並ぶ"""
def format_data(self, data):
raise NotImplementedError()
class NaturalStrategy(NotationStrategy):
def format_data(self, data): return f"自然数 {data}"
class FractionStrategy(NotationStrategy):
def format_data(self, data): return f"分数 {data}"
class BinaryStrategy(NotationStrategy):
def format_data(self, data): return f"2進数 {bin(int(data))}"
# ==========================================
# 【クラスC】外注するだけ(職人に丸投げ)
# ==========================================
class CanvasContext:
def __init__(self, strategy: NotationStrategy):
self.strategy = strategy # 水平に職人を雇う(委譲)
def change_strategy(self, new_strategy: NotationStrategy):
"""実行中に、いつでも外注先をカチッと切り替えられる"""
self.strategy = new_strategy
def on_mouse_click(self, data):
print("[CanvasContext] クリックされました -> ", end="")
print(self.strategy.format_data(data))
# 1. 職人をあらかじめスカウトしておく
natural_brain = NaturalStrategy()
fraction_brain = FractionStrategy()
binary_brain = BinaryStrategy()
# ==========================================
# 【カスタマイズ】利用者が作ったコード
# ==========================================
# クラスNのサブクラスなら自前の職人も使えるが、
# 基本、メニューの職人(戦略)を雇って丸投げする。
# 2. 最初は自然数職人で画面を起動
canvas = CanvasContext(natural_brain)
canvas.on_mouse_click("2026")
# 3. 画面はそのままで職人をスイッチ!
canvas.change_strategy(fraction_brain)
canvas.on_mouse_click("2026/05")
# 4. 職人をさらにスイッチ!
canvas.change_strategy(binary_brain)
canvas.on_mouse_click("42")
[OUT]
[CanvasContext] クリックされました -> 自然数 2026
[CanvasContext] クリックされました -> 分数 2026/05
[CanvasContext] クリックされました -> 2進数 0b101010
課題:ストラテジーパタンを視覚化するにはどうしましょう。
タイトルは「ストラテジーパタンは外注職人選択法」
[]内は設定>上級>オブジェクト表示の条件
n=4
nの見出しは「コメンテータを選ぶ」
a=10
b2=toBase(a,2) [非表示]
b16=toBase(a,16) [非表示]
t0=if(a>0,"正の数で、", "負の数で、")[非表示]
t1=if(mod(a,2)==0,"偶数です。","奇数です。”)[非表示]
f:x^2+3x [n==3]
b=integral(f, -6,a) [n==3]
factprime=PrimeFactors(a)[非表示]
factindex=Factors(a)[非表示]
text1=a+"は2進数だと"+b2+"ですが、16進数では"+b16+"です。" [n--1]
text2=""+a+"は"+t0+""+t1+"" [n==2]
text3="関数"+f+"を-6から"+a+"まで定積分した値は"+b+"だ。\\だから、どうということはないけどね。"
[n==3]
text4="整数"+a+"を素因数分解すると、約数となる素数は"+factprime+"で、指数も含めると”+factindex
[n==4]
そして、画面上に
InputBox(a)を貼り付けましょう。
見出しは「テーマとなる数」です。
こうすると、nを切り替えるだけで、aについてコメンテーターが専門的?に答えてくれます。
geogebraでワイドショーの見える化ですね。