OOP:いろいろな表と中身の関係
このワークシートはMath by Codeの一部です。
今まで、主要なデザインパタンをいろいろ扱ってきました。
これから、まだ残っているパタンたちの特徴をつかむために
さらに進んでいきましょう。
今回は、いろいろ、表と中身というテーマです。
・「いろいろある」にかかわるのが、singletonとfacade。
・「中身と表」にかかわるのがadapterとproxyとdecorator
さくさく、進みましょう。
1.1つにしたいシングルトンとファサード
<Singletonのインスタンスは唯一無二>
アプリのよっては複数起動できるもの、1つだけのもの、どちらも選べるものがあります。
メモリ上やプログラミング上の安全のため、アプリ自体のインスタンスは1個に限るものが多いです。
そのコードはGoF本では、
シングルトンクラスSingleton初期化のときに、インスタンス数_instance=0のときだけ、
new Singletonをするようにして、2つ以上のインスタンス作成を阻止してます。
また、結城本では、
クラス初期化のときのstaticをつけてnewすることで、インスタンスを1つに限定します。
Pythonで実装することもできますね。
class Singleton(object):
def __new__(cls):
if not hasattr(cls, 'instance'):
cls.instance = super(Singleton, cls).__new__(cls)
return cls.instance
<Facadeクラスは共通窓口ページ>
要するにイテレタクラスで調査対象のオブジェクトごとの差を吸収するAPIを提供してました。
これをデータ操作の文脈に限定せずにやろうということです。
Facadeファサードといえば、スペインのバルセロナにある
ガウディと仲間・弟子たちのサグラダファミリアの正面入り口です。
正面にある共通窓口くらいの意味でしょうか。
オブジェクト生成でみると抽象工場、
振る舞いでみると相談役の一元化などに似てますね。
そうはいっても、働きは窓口というくらいで地味です。
コンテンツになるクラスA,B,Cとあるときに、それを
「いらっしゃいませ」とあいせつして見出しをつけて
ページA,ページB,ページCへのリンクを選んでもらう。
というくらいの見かけは派手で、働きは地味です。
受付嬢はA,B,Cの中身は何もかえてませんね。
中身は変えないものはもっとあります。
次に進みましょう。
2.差異をみせないアダプターとプロキシ
<Adapterは差異吸収のラッパー>
ちがったものをつなぐのがFacadeでしたが、これは窓口をつなぐだけです。
受付嬢でした。
データをつないだり変換するという地味だけどタフな仕事人がいます。それがAdapter。
コンセントの口の違いを吸収するアダプターのイメージです。
プログラマーは「ラッパー(Wraper)」と呼ぶことの方が多いですね。
ADAPTER:
互換性のないインターフェースのために連携できないクラスを連携する。
昔からある便利なものAdapteeに対して、
ユーザが使いたい新型のものTargetがあるとしよう。
このTargetとAdapterを両立させたいという場面はよくあるね。
そこで登場するのがAdapterラッパーだ。
多重継承できる言語では、Adapterは窓口Targetと多様Adapteeの両方の子どもにします。
多重継承できない言語では、Adapterが多様Adapteeを雇って持つ(移譲)にします。
数学の例で考えてみよう。
整数は昔からある便利なものだ。
分数は複雑に見えるがさらに有能だ。
分数をTargetにするとき、整数をAdapteeにする。
では、Adapterはどうするだろうか。
分数ユーザが整数も使いたい。同じインターフェースにしたい。
カンタンですね。
整数Nを分数(1分のN)とするだけです。
整数計算のロジックは何も変えずに、
分数も整数もまぜて使えるようになりますね。
数学は自然数、正負の整数、小数・分数、実数、複素数と
数の拡張の歴史を持っている。
アダプター作成の歴史だともいえるね。
<プロキシは臨時代理>
アダプターはプログラミングと数学の両方での日常的なものだった。
プロキシはプログラミングの現場では何かと必要になる臨時代理職員のようなものだ。
PROXY:
オブジェクトのアクセス制御のために、オブジェクトの代理、または入れ物を提供する。
ものすごく多忙な人物は、最近ではAIアバターが仕事をこなしていたりする。
臨時職員なのに、正面に立って仕事をする防波堤のような存在だ。
ただの窓口ではなく、本物らしく、本物に近く対応できないと困る。それが代理だ。
仮想代理(Virtual Proxy)
オブジェクトが巨大になるソフトでは、本物のオブジェクトを作らずに仮想オブジェクトを作って対応することがある。
遠隔代理(Remote Proxy)
リモートで送受信処理をするコストがかかる状況では、ソフトがローカルと送受信して、必要ならリモートとつなぐ。
防御代理(Protection Proxy)
OSと直接やりとりさせるのはセキュリティ上問題がある。そこで、カーネルの代理で対応させることもある。
このように、かなり特殊なニーズだが、確実に必要性があるものだね。
一般的には忙しさを分散させるために、代理を中心に仕事をこなしてもらうという形で使われるでしょう。そうはいっても、代理クラスは仕事をこなせるものでないとけない。使うときは臨時であっても、作り込みが必要になるでしょう。それに対して、中身を変えずに表をさくっと変える技があります。
それが、デコレタパタンです。
3.デコレタは早業装備
DECORATOR:
オブジェクトに「付加的な責務を動的に付与すること」を、移譲によって柔軟に容易に提供する。
中身と表の共通インターフェースとしてITextがあり、displayで文字列表示ができます。
その子クラスとして素のテキストPlainTextがすでにあるとしましょう。
そのとき、
「付加的な責務を動的に付与すること」の中身が、文字列装飾、文字列デコり、だとしましょう。
デコレタを特別扱いせずにまるで素のテキストのように扱いたいので、
ITextの子どもとしてのデコレタTextDecoratorにも、displayで文字列表示ができます。
class TextDecorator(IText):
def __init__(self, target_text: IText):
このコンストラクタ。面白いですね。イニシャライズでITextを読み込んでます。
起動するときTextDecoratorは親ITextも含めた親族の仕事を取り込めるのです。
デコレタといっても、文字を使う装飾であれば、PlainTextと差がないですね。
だから、デコった後の文字列をさらに別のデコレタで重ね着、厚化粧ができてしまいます!
実際に、TextDecoratorの子を2つ作ります。
BracketDecorator(TextDecorator)は引数の【】で囲む機能
StarDecorator(TextDecorator)は☆で挟む機能
としましょう。
この機能を使って、繰り返し文字列装飾を重ねることができるはずです。
やってみましょう。
# Decorator.py
# ==========================================
# 共通の規格(コンポーネント)
# ==========================================
class IText:
def display(self) -> str: raise NotImplementedError()
# ==========================================
# 中身(プレーンなテキスト)
# ==========================================
class PlainText(IText):
def __init__(self, text: str):
self._text = text
def display(self) -> str:
return self._text # 中身はただ文字列を返すだけ(一切汚さない)
# ==========================================
# 付加機能(デコレーター群)
# ==========================================
class TextDecorator(IText):
"""自身もITextのフリをしながら、中身を移譲で抱え込む"""
def __init__(self, target_text: IText):
self._target = target_text
class BracketDecorator(TextDecorator):
"""【】で囲む機能"""
def display(self) -> str:
return f"【{self._target.display()}】"
class StarDecorator(TextDecorator):
"""☆で挟む化粧"""
def display(self) -> str:
return f" ☆ {self._target.display()} ☆ "
# ==========================================
# 【作動テスト】中身を変えずに、動的に重ね着させる
# ==========================================
print("=== Decorator Algorithm: Start ===")
# 1. 素のテキスト
core = PlainText("フィボナッチの秘密")
print(f"素顔: {core.display()}")
# 2. 【】を動的に着せる
wrapped_1 = BracketDecorator(core)
print(f"化粧1: {wrapped_1.display()}")
# 3. ☆☆を動的に着せる
wrapped_2 = StarDecorator(core)
print(f"化粧2: {wrapped_2.display()}")
# 4. さらにその上から【】を重ね着させる!
wrapped_3 = BracketDecorator(wrapped_2)
print(f"化粧3: {wrapped_3.display()}")
# 5. おまけに、その上から☆を重ね着させる!
wrapped_4 = StarDecorator(wrapped_3)
print(f"化粧4: {wrapped_4.display()}")
[OUT]
=== Decorator Algorithm: Start ===
素顔: フィボナッチの秘密
化粧1: 【フィボナッチの秘密】
化粧2: ☆ フィボナッチの秘密 ☆
化粧3: 【 ☆ フィボナッチの秘密 ☆ 】
化粧4: ☆ 【 ☆ フィボナッチの秘密 ☆ 】 ☆
デコレタは早業化粧でした。
化粧というとなんか薄っぺらくなりますが、機能拡張を多層的にできるということです。
「デコレタは早業装備」と呼んだ方が奥行きがあるかもしれません。
振り返りをかねて、次の課題に取り組みましょう。
課題:デコレタパタンをgeogebraで実感するにはどうしたらよいでしょう。
数学を中身を理解して証明や説明すべきものだと考えると、とても重たく先に進みにくくなる。
数学は、「数式に作用させて数式を作る」
のくりかえしだ。
と考えると、ITextが数式を出すもので、それを継承している。
数式を入れると数式を出してくれる操作、演算はデコると一緒だ。
演算子はデコレタだ。
そう考えると、数学がとたんに身軽になる。
アプレットのタイトルは「演算子でデコる」
n=slider(1,5,1)
f(x)=x^2
h(x)=e^x
これが素の数式だとしよう。
これを微分して、定積分と重ね着しよう。
g=Derivative(f)
a=integral(g,0,n)
i=Derivative(h)
b=integral(i,0,n)
それをベクトルとして、
A=(a,b)
これを単位ベクトルにして、長さをnにして、
u = n UnitVector(A)となる。
これは特別に意味のあることは何もしていないけれど、定積分の区間を変えるだけで、
結果としてベクトルuが動くので、何かしらの意味は生まれるのかもしれない。
意味を明確にして深堀して考えることも大事だけれど、
身軽に変化を楽しみ、観察するという素直な行動も、たまにはよいかも。