オブザーバは通知と追従の1対1の複数分解
このワークシートはMath by Codeの一部です。
今まで、生成のデザインパタンを見てきました。
これからは、振る舞いグループをさぐろう。
振る舞いというのは、状態や変化の関係にかかわるものです。
今回は、オブザーバーパタン、ObserverPattern
日本語では、傍観者と訳されることがある傍観者。
では、監視人?観察者?
いやいや、ちがいます。
GOF本では、「オブジェクトの状態変更が、依存オブジェクトに通知されて、更新されるように1対多の関係を定義する。」
とあります。
だから、
親ガモは子に背中を見せながら気ままによろよろ進む。
子ガモたちは親に必死でついていくが、たまに遅れるやつもいたりするね。
そんな親子ガモを頭に描いてみてください。
略して、通知と追従です。
親ガモは、子ガモにいちいち、ああしろ、こうしろと指示しません。
ただ、動いたことが子ガモからはわかるように通知はします。
子ガモは同じようにみえても実は個性豊かです。個性に応じて親を追いかけるでしょう。
そんなようすを実装してみよう。
1.通知と追従パタンを実装しよう
<通知と追従パタンの実装>
import math
# ==========================================
# 【クラスO】子ガモ(親を凝視追従する義務がある)
# ==========================================
class ObserverContract:
def update(self, updated_value):
"""親に追従すべしのメソッド"""
raise NotImplementedError()
# ==========================================
# 【クラスS】被凝視者(親ガモのシステム)
# ==========================================
class Subject:
"""親ガモは子ガモリストを管理するが、子ガモに変化を通知して、追従を促すが、
子ガモのことを何も知らない。疎結合"""
def __init__(self):
self._observers = [] # 子ガモのリスト
def attach(self, observer: ObserverContract):
"""子ガモの追加"""
self._observers.append(observer)
def notify(self, new_value):
"""自分の変化を背中で子ガモへの追従を促す。"""
for observer in self._observers:
observer.update(new_value)
# ==========================================
# 【カスタマイズ】ユーザーが作る必死な子ガモたち(Oのサブクラス化)
# ==========================================
class RealObserver(ObserverContract):
"""子ガモ1号"""
def update(self, updated_value):
print(f"1号:親は {updated_value}㎜進みました。急ごう!")
class AboutObserver(ObserverContract):
"""子ガモ2号"""
def update(self, updated_value):
print(f"2号:親が {math.floor(updated_value/10)}cm ぐらい進んだみたい。少し動こう。 ")
class LazyObserver(ObserverContract):
"""子ガモ3号"""
def update(self, updated_value):
print(f"3号:約 {math.floor(updated_value/100 + 0.5)}mすすんだ。でも、なんとかなるさ。 ")
# ==========================================
# 親鳥の具体的なデータ(計算コア)
# ==========================================
class MathDataCore(Subject):
def __init__(self):
super().__init__()
self._g = 0
def change_value(self, new_g):
self._g = new_g
print(f"[親データ] は{self._g}になった。すると、 ")
self.notify(self._g)
# 1. 親子ガモ登場と登録
parent_data = MathDataCore()
parent_data.attach(RealObserver())
parent_data.attach(AboutObserver())
parent_data.attach(LazyObserver())
# 2.親データの値に子ガモが追従するはず ---
parent_data.change_value(15)
parent_data.change_value(111)
parent_data.change_value(199)
[OUT]
[親データ] は15になった。すると子ガモたちは、
1号:親は 15㎜進みました。急ごう!
2号:親が 1cm ぐらい進んだみたい。少し動こう。
3号:約 0mすすんだ。でも、なんとかなるさ。
[親データ] は111になった。すると子ガモたちは、
1号:親は 111㎜進みました。急ごう!
2号:親が 11cm ぐらい進んだみたい。少し動こう。
3号:約 1mすすんだ。でも、なんとかなるさ。
[親データ] は199になった。すると子ガモたちは、
1号:親は 199㎜進みました。急ごう!
2号:親が 19cm ぐらい進んだみたい。少し動こう。
3号:約 2mすすんだ。でも、なんとかなるさ。
課題:geogebraでオブザーバーパタン、通知と追従パタンを視覚化するにはどうしましょう。
アプレットタイトルは「オブザーバー:番号の通知で数式、グラフ、対応表が変わる」
fx={2^(x),0.5 x+1,x^(2)+x,2 x^(3)-8 x,x^(4)-x^(3)+x+2}
n=slider(1,5,1)
a:fx(n,x)
text1=FormulaText(a)
lx={1,2,3,4,5}
ly={a(1),a(2),a(3),a(4),a(5)}
TableText({lx,ly},"|_“, 60, 80")
親ガモ(スライダ)の通知に応じて
子ガモ(グラフa、数式Text1,対応表Text2)が追従して一斉に変わるね。
グラフを青く太さ5くらいにし、
数式の文字を大きくし、
対応表の背景色を不透明な白にするなど、
好きなようにデザインしよう。
また、関数リストfxを変えるのもありだね。
オブザーバー:番号の通知で数式、グラフ、対応表が変わる
2.通知と追従の例をふやそう
<振り返り>
通知と追従というのはたいてい、1対多関係とかかれたりします。
しかし、実際には1対1対応の関係が複数あるだけだと気付きます。
1通知は親1個から子リスト1個にいきます。
子リスト1個にわたった1通知が、子リストのメンバ1個に届きます。
だから、1子からみたら、1親から1通知が来ただけです。
ただ、多というのがインスタンスレベルでおきるのは
子オブジェクトが多であること、
子リストにいる子が多であるからです。
詳細にみていくと、1対1のつながりの複数あるだけだと気付きますね。
だから、これは、現実の複雑な現象、相互関係を分析するときに、
複雑に見えて、実は1対1が複数あるだけだと分解してから合成する発想につながるでしょう。
つまり、数学に限らず、科学的な分析や
複雑な現象を
単純な規則性、ルールに分解して分析したり、創造することができるという
希望を与えてくれます。
たとえば、音楽には、音高、音長、音響と振動、静寂、音量、リズム、音色、時間的な変化、。。。。
などなど、さまざまな要素がからんでます。
でも、コード進行というやや複雑に見えるものも、この通知と追従というパタンがつかえるかもしれませんね。
連動して操作できるということは
データ(モデルMとなる親ガモ)ビューV(反応する子ガモたち、音)コントロールC(データ変更)
というMVCシステムのベースが作れるね。
課題:楽曲のフレーズを通知と追従パタン連携させるMVCシステム例をgeogebraで作ってみよう。
たとえば、
Cのダイヤトニックスケールコードは
C,Dm,Em,F,G,Am,B,C
0,1,2, 3,4,5,6,
コード進行をたとえば、次のようにします。
C,C,Am,Am,F,F,G,G
0,0,5,5,3,3,4,4
この度数差の分だけ、
C(親ガモ)のメロディに
他のコードのメロディを追従させましょう。
なお、CのメロディーはCを1番とする自然数で1,2,3,4,5,6,7のように、
ドレミファソラシドをあらわし、音長の代わりに、同じ音高数字を連続してかいてます。
#メロディの通知と追従関係を作る。
Rsub={1,3,5,3,1,1,1,1} #親ガモのデータM
#次は追従する子ガモV
RAm = Rsub + 5
RF = Rsub + 3
RG = Rsub + 4
Rj = Join(Rsub, Rsub, RAm, RAm, RF, RF, RG, RG)
Rj=Join(Rsub,Rsub,RAm,RAm,RF,RF,RG,RG) #メロディ音高リスト
T60=slider(0,80,1) #タイマーのように動かす。
size=Length(Rj)
amari=Mod(T60,size)
ind=if(amari==0,1,amari)
Px=if(T60>0,Element(Rj,ind),0) #メロディ音高リストの番号
#スピード
velo=slider(50,1000,1)
tm=60/velo
#音高を周波数に変える
fr={1,1.11111111111111,1.25,1.33333333333333,1.5,1.66666666666667,1.875}
f2=fr*2
f3=fr*3
frs=Join(f,f2,f3)
ff=Element(fr,If(amari≟0,1,Rj(amari)))
f=220 ff
th=f*2 π
#追従する子ガモVでもある音波
p(x)=Sum(Sequence(((1)/(k)) sin(th k x) (-1)^(k+1),k,1,6))
最後の仕上げで、コントロール系Cと表示系を貼り付けましょう。
#親メロディの変更ボックスです。見出しを"Cのフレーズ="にします。
textField=InputBox(Rsub)
#「開始」ボタンをはりつけてスクリプト記述
SetValue[T60,0]
StartAnimation[T60,true]
PlaySound(true)
PlaySound(p(x),0, range)
#「停止」ボタンをはりつけてスクリプト記述
StartAnimation[T60,false]
PlaySound(false)
#「リセット」ボタンをはりつけてスクリプト記述
SetValue[T60,0]
StartAnimation[T60,false]
text1="
Cのフレーズを
C,C,Am,Am,F,F,G,Gにスライドして
演奏できます。
"
text2="コードにあわせて進行するフレーズ="+Rj
これで、MVCsystemができましたね。
もっと、シンプルにしたり、複雑化したりすることもできる。
この発想は、Geogebra以外にも移植できそうだね。
ただし、音は相当悪くて、雑音レベルです。
うるさいと思ったら数字の変化だけたどってください。