このワークシートは[url=https://www.geogebra.org/m/twxxx3yq]Math by Code[/url]の一部です。[br][br]GoFのデザインパタン、全23種類のうち21種類に触れてきました。[br][br]今回はGOFの残り2パタン、使い回しとたらい回しを扱います。[br]それって何でしょう?[br]
[color=#0000ff][b][size=150]フライ級Flyweight:[br]細かいオブジェクトのNewを減らし、[br]「使い回し」で使用メモリを軽く(フライ級に)します。[br][/size][/b][/color][br]細かいけれどパーツとして多数生成されがちなクラスというものがあります。[br]重複してパーツをNewしてインスタンス数をふやすと、使用メモリが増加しますね。[br]それを避けるために、同一のパーツは使い回しをしてメモリを軽くしよう。[br][br]たとえば、定積分ingegral(f,x,a,b)を図形的文字列CharFigsとして表示するとしたら、[br]∫^a_b f dx となるとします。この文字列のパーツがCharsだとする。[br][br]∫^0_100 f dx =∫^0_10 f dx+∫^10_100 f dx[br][br]つまり、[br][math]\text{\int^0_{100} f dx = \int^0_{10} f dx + \int^{10}_{100} f dx[br]}[/math][br][br]という[数式N]ではパーツCharsは リストm={∫,f,dx,0,1,=,+ }だけですね。[br]美しく繰り返す属性は[b]文字フォント[/b]です。これらを並べたNの中では、[br][b]共有させる情報、内部の固有情報(intrinsic)[/b]としておけば、情報量がへります。[br]実際、上のgeogebraのMathオブジェクトもフォントの指定はできません。[br][br]しかし、[b]共有させない、変えてよい情報(extrinsic)[/b]もありますね。[br]リストmの要素が数式Nの左から何番目に出てくるかはCharsの内部に閉じ込めるのではなく[br]外のCharFigsに出す(exにする)ものです。[br][br]このように使い回しをするには属性の[b][color=#0000ff]内部(in)と外部(ex)[/color][/b]を区別が必要になりますね。[br]
責任分割つなぎパタンChainOfResponsibility:[br]対応する責任を分割して複数の責任オブジェクトに分割して連結すれば、[br]その鎖を順次わたると要求するオブジェクトが見つかるかもしれません。[br][br]対応側は要求側に全面対応する必要がなく、責任と関係ない要求は答えずに次に回せるので、[br]対応する責任オブジェクト(Supportの子クラス)は責任が軽くて楽です。[br]要求する側は、たらい回しにあうかもれませんが、[br]責任体制が充実していれば、もし複数の要求がある場合、ワンストップサービスを受けることができるかもしれません。[br]実装イメージはデコレタパタンパタンで、要求者を受け入れてできる対応があればして出す、[br]厚化粧のインスタンスを作ればよいのではないでしょうか。[br][br][b][color=#0000ff]責任分割つなぎパタンはたらい回しパタンという悪名[/color][/b]がありますが、[br]たらい回しをされて、[b][color=#0000ff]なにもしてくれない方がいい場合[/color][/b]もありますよ。[br][br]たとえば、入力のバリデーションは、会員登録画面を作るときは必要ですね。[br]「パスワードを半角英数字で英字と数字の両方があり12文字以上で、大文字を1つは含んでください。」[br]とメッセージがでます。[br]入力サポートクラスSupportは入力文字を引数にして、リターンはそのままです。[br]まずい入力にはメッセージを表示します。[br]1つのクラスですべてをまかなうとif~elifが長くなります。[br]そこで、責任分割をしましょう。[br][br]半角英字なしを見つける子クラス、[br]半角数字なしを見つける子クラス、[br]文字数が12未満を見つける子クラス、[br]Upperクラスの文字数が0であることを見つける子クラス、[br][br]それらが、互いのことは気にせず、バラバラに、[br]しかし連続してミス発見をサポートしてくれます。[br]この[b]たらい回しで無事何もしてくれない[/b]と、[br]正しい入力だったことがわかります。[br]たらい回しでスルーされることが、正しいことの証明になるんですね。
[b][size=150]<実装例>[/size][/b][br]# ChainOfResponsibility.py[br][br]class Support:[br] """入力サポート(責任の鎖)の抽象クラス"""[br] def __init__(self):[br] self._next = None[br][br] def set_next(self, next_handler):[br] self._next = next_handler[br] return next_handler # 鎖を数珠繋ぎにしやすくするため自身を返す[br][br] def check(self, password: str) -> bool:[br] # 1. 自分の担当するエラーをチェックする[br] if self._has_error(password):[br] self._print_message()[br] return False # ミスを発見したのでここでストップ[br] [br] # 2. 自分に問題がなければ、黙って次の門番にたらい回しする[br] if self._next is not None:[br] return self._next.check(password)[br] [br] # 3. 最後の鎖まで誰も何もしてくれなかったら「合格」![br] return True[br][br] def _has_error(self, password: str) -> bool: raise NotImplementedError()[br] def _print_message(self): raise NotImplementedError()[br][br][br][color=#0000ff]# ==========================================[br]# 各門番の実装(互いのことは気にせずバラバラに作る)[br]# ==========================================[br][/color]class LengthCheck(Support):[br] """文字数が12未満を見つける"""[br] def _has_error(self, password: str) -> bool: return len(password) < 12[br] def _print_message(self): print("エラー: パスワードは12文字以上で入力してください。")[br][br]class DigitCheck(Support):[br] """半角数字なしを見つける"""[br] def _has_error(self, password: str) -> bool: return not any(c.isdigit() for c in password)[br] def _print_message(self): print("エラー: 数字を最低1文字は含んでください。")[br][br]class AlphaCheck(Support):[br] """半角英字なしを見つける"""[br] def _has_error(self, password: str) -> bool: return not any(c.isalpha() for c in password)[br] def _print_message(self): print("エラー: 英字を最低1文字は含んでください。")[br][br]class UpperCheck(Support):[br] """大文字の文字数が0であることを見つける"""[br] def _has_error(self, password: str) -> bool: return not any(c.isupper() for c in password)[br] def _print_message(self): print("エラー: 大文字を最低1文字は含んでください。")[br][br][br][color=#0000ff]# ==========================================[br]# 【作動テスト】[br]# ==========================================[br][/color]print("=== Validation Chain: Start ===\n")[br][br]# 鎖を構築する(長さ ➔ 数字 ➔ 英字 ➔ 大文字)[br]gate = LengthCheck()[br]gate.set_next(DigitCheck()).set_next(AlphaCheck()).set_next(UpperCheck())[br][br]# 入力例1: 短すぎる入力[br]print("--- 入力例1: 'abc' ---")[br]if gate.check("abc"): print("結果: 登録成功!")[br][br]# 入力例2: 大文字だけを忘れた惜しい入力[br]print("\n--- 入力例2: 'gofpattern2026' ---")[br]if gate.check("gofpattern2026"): print("結果: 登録成功!")[br][br]# 入力例3: すべての門番を「たらい回し」された完璧な入力[br]print("\n--- 入力例3: 'GoFPattern2026' ---")[br]if gate.check("GoFPattern2026"):print("結果: 登録成功!")[br][br][OUT][br][b]=== Validation Chain: Start ===[br][br]--- 入力例1: 'abc' ---[br]エラー: パスワードは12文字以上で入力してください。[br][br]--- 入力例2: 'gofpattern2026' ---[br]エラー: 大文字を最低1文字は含んでください。[br][br]--- 入力例3: 'GoFPattern2026' ---[br]結果: 登録成功![/b]
[b][size=150]<振り返り>[/size][/b][br][br]デザインパタンはあなたにとって、[br]後期ウィトゲンシュタインの哲学のように[br]思考のくせやゆがみを映し出す鏡と感じられたでしょうか。[br][br][color=#0000ff][b][size=150]デザインパタンは抽象的なだけあって、[br]それは多様な具体的な場面や状況にあてはめることができます。[br][/size][/b][/color][b][size=150]極端な構造化をしているので、それが思考に刺激をもたらします。[br][/size][/b][br]そこは、数学・哲学に似ているところですね。[br][br]ただ、数学よりもさらに哲学に似ています。[br]哲学はそのままでは何も解決してくれませんから。[br][br]数学は何かしら、計算してくれたり、何かに使えたりします。[br]しかしデザインパタンはコードは作ったりしましたが、ただのサンプルであり[br]なんの強制力も力もありません。一般性もありません。[br]ただ、[color=#0000ff][b]そこから見えてくる発想、感じられる構造化の特徴[/b][/color][br]それが少しでもあるという希望をもって載せています。[br][br]GOF本にも実装はいろいろできるでしょうけど。。。的なゆるーい感じの記述が多いですね。[br]サンプルコードがたとえば、javaでかかれていていると、[br]Javaのもとクラス名とインスタンス名などの冗長さやアクセス指定や型の厳密さなど、[br]java言語の重さと見た目の複雑さに圧倒されがちです。[br]テーマがGOF本のように迷路ゲームや図形エディタなど興味深いものであって、[br]その手前で挫折する可能性があります。[br]GOF本ではこのコードを動かすとこうなりますね。というような手取り足取りはありません。[br]結城本にはコードが動かせるCDROMがついているので、寄り添っている感が強いですね。[br]でも、しょせんJava言語です。なれている人はどうということはないでしょうけどね。[br]このGeogebraのページでかいた「Pythonで学ぶデザインパタン」は[br][b]Pythonなので、多くの人がコードにつまづく可能性は低くなります[/b]。[br][br]また、私はプログラミングの教師でもないし、数学の教授でもありません。[br]ただ、プログラミングも数学も好きだというだけの暇な人です。[br]だから、穴だらけの記述・表現ですが、商売につなげる気もないので、[br]体裁を無理やり整えることもしてません。[br][br]ただ、[b]身近な例、GOF本の要約によるパタン紹介、Pythonコード、振り返り、直観的アプレット[br][/b][br]できるだけ、この流れを目指しました。[br][br]とりあえず。このページを区切りとします。[br][br]しかし、もう次を考えています。[br]GOF本は時代背景からみてもOOPがさかんなときの本です。[br]しかし今は純粋関数型のパラダイムの言語や書き方がさかんになってます。[br]この続きはその方向でいきます。[br][br]つづく。