OOP:ブリッジは、明日にかける橋
このワークシートはMath by Codeの一部です。
今回はブリッジパターンです。
1.ブリッジパタンがなぜ、明日にかける橋なのか
うっかりプログラミングをしていると、
継承関係(サブクラス化)ばかり考えがちです。
その結果大変なことになることが考えられますね。
よくある例がリモコンの例です。
昔のテレビのリモコンは電源オン、電源オフ、チャンネルボタンだけだった。
だから、テレビのインタース(抽象クラス)の機能はOn,Off,CH1~CH12 でよかった。
テレビの会社東芝,ナショナルの2つの子クラスでその機能を実装すればよかったね。
ところが、電波が増えると、地上波だけでなくBSも見られるようにしたり、
ボタンにチャンネルをセットする機能をつけることになった。また、テレビ会社もSONY,サムスンとふえていく。
だから「新リモコン」というクラスをインターフェースにぶら下げて機能を追加する。
その新リモコンというクラスの下に新ナショナル、新SONY,新東芝、新サムスンという「孫クラス」で実装する。
今はどうでしょう。BS,CS,BSK電波、動画再生、録画、再生、。。。。と機能が増えています。
「新新リモコン」の子クラスと孫クラスを作ることになるのでしょうか。
こんなことをしてたら、「明日の希望」が見えません。
仕事は増えるけれど、生産性がない仕事ですね。
機能の数の拡張とテレビメーカーの数の増加に対応するには、
ピラミッド構造の子クラス、孫クラスが膨大になることは火をみるより明らかです。
そこで、「明日にかける橋」として、ブリッジパターンが登場します。
転ばぬ先の橋ですね。
なぜ、
明日にかける橋(Bridge over Troubled Water)
なのか。
「橋(ブリッジ)」という意味は2つあります。
ブリッジパタンの発想としてはAbstractFactoryの「2軸構造を分解してから合成する」
とそっくりです。
それは、「機能と実装の2軸に分解してから連携する」
この「連携、つなぐ」という意味をブリッジと名付けたともいえます。
しかし、もって視覚的な意味もあります。
UML図をかくと、左に機能クラスの親子の柱が、右に実装クラスの親子の柱が
別々にそびえます。それを機能クラスが実装クラスに指示を移譲することで、
つながりができるのです。UML図が橋の形に見えてきませんか?
まさにブリッジ(橋)ですね。
「明日にかける」というのは、不要な仕事をしないということです。
新機能は橋の左の機能の柱を変えるだけです。
新機能に対応するリモコンは対応する会社があれば、それに実装を増やせばよいのです。
つまり、
機能と実装の上にくる、「神のような不動のインターフェース」をなくします。
機能と実装を別々に階層化したり、更新することができる。
それを両軸の連携をブリッジが解決するわけですね。
2.ブリッジパタンの実装
実装例としては、東芝とナショナル(今はパナソニックですが)の2つのテレビとリモコンがあるとします。
昔の基本リモコン機能としてpower, channelがあります。
TVのソフトの実装側はそれに1対1に対応するメソッドを持ってます。
そこで、リモコン機能側にchannel番号指定ではなく、次ボタンnextchannelに進む機能をつけます。
リモコンを新しくするだけで、同じテレビなのにそれに反応できるようになるはずです。
# Bridge.py
# ==========================================
# 【右の柱】実装の階層(TV制御ソフト:裏のつくり)
# ==========================================
class ITVImplementor:
"""実装の規格(中身)"""
def control_power(self, status: str): raise NotImplementedError()
def control_channel(self, num: int): raise NotImplementedError()
class ToshibaImplementor(ITVImplementor):
def control_power(self, status: str): print(f"[東芝] 電源が {status} ")
def control_channel(self, num: int): print(f"[東芝] 信号が {num}ch ")
class NationalImplementor(ITVImplementor):
def control_power(self, status: str): print(f"[ナショナル] 主電源を {status} にしました。")
def control_channel(self, num: int): print(f"[ナショナル] ダイヤルを {num}chに回しまた。")
# ==========================================
# 【左の柱】機能の階層(ユーザーが触るリモコン:表のボタン)
# ==========================================
class RemoteControl:
"""基本リモコン:内部に「実装の柱」を橋渡し(移譲)で持つ"""
def __init__(self, tv_hardware: ITVImplementor):
self._tv = tv_hardware # これが「架けられた橋」
def put_power_on(self): self._tv.control_power("ON")
def put_power_off(self): self._tv.control_power("OFF")
def set_channel(self, num: int): self._tv.control_channel(num)
class AdvancedRemoteControl(RemoteControl):
"""【機能の拡張】新リモコンのボタン(機能)だけを増やす"""
def next_channel(self, current_num: int):
print("\n☆ [新機能] チャンネル順送りボタンが押されました。")
self.set_channel(current_num + 1) # 橋を渡ってメーカーに指示を出す
# ==========================================
# 【作動テスト】
# ==========================================
print("=== Bridge Remote Algorithm: Start ===\n")
# 1. 昔のナショナルテレビに、基本リモコンを繋ぐ
print("--- 1. 昔のナショナルリモコンの動作 ---")
national_tv = NationalImplementor()
old_remote = RemoteControl(national_tv)
old_remote.put_power_on()
old_remote.set_channel(1)
[OUT]
# 2. 東芝の「新リモコン」にしたら、同じテレビなのに機能が増えた。
print("\n--- 2. 新しい東芝リモコンの動作(無駄な孫クラスはゼロ!) ---")
toshiba_tv = ToshibaImplementor()
new_remote = AdvancedRemoteControl(toshiba_tv)
new_remote.put_power_on()
new_remote.set_channel(1)
new_remote.next_channel(7)
=== Bridge Remote Algorithm: Start ===
--- 1. 昔のナショナルリモコンの動作 ---
[ナショナル] 主電源を ON にしました。
[ナショナル] ダイヤルを 1chに回しまた。
--- 2. 新しい東芝リモコンの動作(無駄な孫クラスはゼロ!) ---
[東芝] 電源が ON
[東芝] 信号が 1ch
☆ [新機能] チャンネル順送りボタンが押されました。
[東芝] 信号が 8ch
3.振り返り
<振り返り>
最近でも、過去に逆行する発想を、残念ながら見聞きします。
学習者には学習機能がない。
すべてコピーできるように教え込まないと、覚えない。
学習する方も、学習は知識のインストールすることであり、「写経」と「暗記」が一番だ。
数学は暗記だ。受験は学習量で決まる。
そんな風潮が今でも蔓延している気がします。
どんな実装(脳内)かを問わずにボタンの反応を見ているだけの問題が多いと、
入試問題や資格試験問題は劣化するでしょう。
ブリッヂパターンに出会ったことで、そんな不安が増大します。
なぜだかわかりますか。
ブリッジパターンで機能は、テレビに対して「正しく反応できますか?」
という課題ともいえます。
機能が増えるということは、テレビにとっては反応すべき課題が増えるわけですね。
さっきのコードは、テレビはそのままで、リモコンを賢くしてテレビが反応できるようにしたのです。
リモコンに教え込んだわけです。テレビが賢くなったわけではありません。
機能ボタンを押すのは「教授者」が問いかけで、反応するテレビは「学習者」です。
それぞれ別系統で、別階層に作られています。
だから、互いのことは、中身・裏はわからないのです。
しかし、言い方をかえると、
不安は希望にも変えられます。
このブリッジ構造は絶望ではなく、本来は「希望」です。
なぜなら、教授者はB君の脳内(実装)を「普通の子」へと矯正・支配しなくても、
ただ問いのインターフェースを美しく拡張してあげるだけで、
B君の個性をそのまま生かしたまま、新しい世界(わり算)へと導くことができたからです。
正解していることだけからは、相手の「中身」は何もわかりません。
しかし、
「中身がわからないからこそ、お互いの個性を尊重したまま、
表の関係だけで共に未来へと成長していける」。
また、学習者が機械ではなく人間やAIならば、
教授者に教えてもらわなくても、自分で問い(新機能)を作り出し、
その答え方を作ってしまうことさえ可能なはずです。
ブリッジパターンは「明日にかける橋、未来にかける橋」なんだという気持ちになりました。
人間もAIもこのどっちにもいける自由の価値を
見失わなければ、明るくなる未来はあるでしょう。
課題:geogebraでブリッジパターンのイメージをつかめるものを作ってみよう。
回答者系があり、A君とB君がいます。
機能の系列があり、たし算、ひき算、かけ算です。
ここで新機能「わり算」をA君とB君がその実装に関係なくできるように教えたい。
ところで、A君はいわゆるふつうにできる子で、教授者の教えた通りにふつうに演算します。
B君は考えることを楽しみすぎる子です。
たし算(S,T)といわれたら、(S×S-T×T)÷(S-T)を計算して、たし算せずに答えを出します。
引き算(S,T)といわれたら、S+□=Tとなる□を探すことも考えますが、
一発で出せる(S×S-T×T)÷(S+T)で計算します。
かけ算(S,T)ならば、SをT個たし算することも思いつきますが、(S+T)/(1/S+1/T)を計算します。
新機能を教える教授者はたし算、ひき算、かけ算を使って教えなければなりませんね。
わり算(S,T)なら、SからTを引き続け、引いた回数に対する残りの数列を作り、負の数が初めて出た
数列の直前の番号を答えるように伝えるかもしれません。
まだ、余りのない割り算を教えているのならば、
余りが0ピッタリになるときの引き算回数が商になります。
アプレットのタイトルは「学習者が正解していることからわかること」
S=n
T=i
Badd=(S*S-T*T)/(S-T)
BdiffS(x)=(S*S-x*x)/(S+x)
BmT(x)=(x+T)/(1/x+1/T)
#B君の実装でも割り算ができるように教授します。
Bdiv=IndexOt(0,Sequence(BdiffS(BmT(k)), k,1,S)) #割り切れない割り算は知らないので?と答えます。
Bdiff=BdiffS(T)
Bmulti=BmT(S)
text1=
"B君の回答
\\"+S+"+"+T+"="+Badd+"
\\"+S+"-"+T+"="+Bdiff+"
\\"+S+"×"+T+"="+Bmulti+"
\\"+S+"×"+T+"="+Bdiv+"\\"
n=Slider(10,100,1) 見出し[S]
i=Slider(1,n-1,1) 見出し[T]
S,Tのスライダーを動かすと、B君の四則計算は正しくできます。
教授者はわり算を教えただけです。
正解していることだけから、何がわかるのでしょう?????