人間のあるべき姿の探索

思索・人形・エンジニアリング

GoFのデザインパターン全部書く

はじめに

GWなのでGoFデザインパターン23種をすべて書きます。巷ではJavaソースコードを用いた書籍はいくらかあるのですが、他の言語だと時折有志の方がブログにまとめていますが全部書いてあることは少なく、また実際に自分で書かないと使えそうなタイミングで思い出すこともできないので、同じく型システムがまともに使えて業務や趣味でもよく使用するC#で全部書いていきます。

また、例として紹介されるものは分かりやすいものや典型的な使用例が想定されるものもあるのですが、大体Personクラスが出てきて名前が云々といったことが多いので、折角なので自分で例を考えつつ書いていきます。できれば実装中のロボットに組み込んでいきたいので、ロボットの例が出せれば出していきます。また、学習の為に実装していく方式をとっているので、クラス図とか詳細な定義を知りたい方はWikipediaGoFのページをご参照ください。パターンによってJavaPython、JSのコードがあったりなかったりしますが、とりあえずクラス図と定義は書いてあるので参考になるかとは思います。

ということでネットの情報が微妙に体系的に網羅されていない感じがするので、参考書として『Javaデザインパターン徹底攻略』を使用します。たまたま市民図書館にあったので活用していきます。2円で購入できるみたいです。

www.amazon.co.jp

全部書いたやつをGitHubにうpしましたが、多分説明がないと読んでもよく分からないと思うので以下に説明を書いていきます。10000文字くらいあります。

github.com

実際に書いてみる

Factory Methodパターン

あるサブクラスと別のサブクラスを必ずペアにして使ってもらいたい場合に使用します。

今回はRobot抽象クラスを用いてRobotWithEyeサブクラスを作成しました。ここで、RobotWithEyeクラスはEyeクラスを使用してほしいので、RobotWithEyeクラス内にCreateEyeメソッドを作成してEyeクラスを使用するよう要請します。まぁこの場合は直接セットしてしまえばよいのですが、書籍の例だとあくまでインスタンスを返すだけなので、セットせずに使用する場合を考えたほうが良さそうです(例えばRobotWithEye専用のLoggerとかは自クラス内にLoggerを持たなければLogger側でRobotWithEyeインスタンスを引数にとってログを生成するのかなと)。

余談ですが、DDDの値オブジェクトのインスタンス化の際にもFactoryメソッドが使用されるのですが、そちらでは自クラスのインスタンス化を行うstaticメソッドを用意します。例えばMoney値オブジェクトはstatic Money Of(float amount, Currency currency){ if(amonut < 0) throw NotArrowedValueRangeException(); return Money(amount, currency);}のように、自身のコンストラクタを直接呼ばせるのではなく、FactoryメソッドであるOfメソッドを経由することで値のチェックをするユースケースが存在します。

Abstract Factoryパターン

組み合わせて使うサブクラス群をまとめて交換できるようにしたい場合に使用します。

RobotWithEye及びRobotWithArmを親クラスのRobotクラスとして使用することで、以下のように呼び出し側はサブクラスを意識することなく使用でき、どちらかを使用したい場合に簡単に交換できます。抽象メソッドに定義したPrintInfoメソッドは使用できるので、「動く」みたいな処理はカプセル化して記述できますが、目を動かすみたいな各サブクラス固有のメソッドはそのサブクラスでインスタンス化してやる必要がありそうです(ただ、単純にコンストラクタが隠蔽される点では分かりやすいかなと思います)。

Builderパターン

複数のメソッドを呼ぶことで一つのオブジェクトを生成させることで、通常のコンストラクタではできない複雑な初期値のオブジェクトを生成できます。多分.NET Frameworkを触っている方であればBuilder自体はStartup.csか.NET 6以降であればProgram.csファイルでよく見かけているのではないかと思います。

今回はロボットに目を生やしたり腕を生やしたりしました。

Prototypeパターン

いろんな種類のオブジェクトをたくさん作りたい場合に使用します。

機能面では違いがないが性質が少し異なるオブジェクトとして、図形における三角形や四角形が紹介されていますが、こういったときにFigureクラスのサブクラスにRectangleやTryangleクラスを実装するか、オブジェクトの種類を示すフラグ変数を持たせたり志しますが、メンテナンスしづらかったりオブジェクトの性質がフラグ変数以外に依存する場合に管理が大変になります。

…といった説明があるのですが、あまりピンとこず、以下の記事を参照しました。あるオブジェクトのコピーを作成したい場合に、プロパティの一部がprivateだったり、内部構造を知らないと適切にコンストラクタを呼ぶこともできないといった課題が挙げられています。

refactoring.guru

そこで、あらかじめ必要な種類の値をインスタンス変数にセットしたオブジェクトを作成して、陳列棚からそれらを取ってくるようにCloneしてくるというのがPrototypeのようです。図形の説明についてはPrototypeパターンの使用でどのように課題が解決されるか記述がないのですが、恐らく図形クラス及び三角形、四角形など各種クラスの実装自体は複雑でもCloneしてくれば呼び出し元としては何も考えずにコピーを取得できるということかなと思います。あまり複雑なプロパティの組み合わせによって成り立つオブジェクトを思いつけないので、一先ずシンプルに角度パラメータをCloneするコードを書いて、マスタスレーブを実施する際にマスタ側の角度をスレーブ側でも同様に使える実装を書いてみます。

Singletonパターン

あるクラスのオブジェクトを一つだけ作って共有したい場合に使用します。これまたよく見かけるのではないかと思います。個人的にはSQLサーバに接続するためのRepositoryクラスでよく使用するイメージで、実際にDDDに従ってUnitOfWorkクラスを記述した際にもRepositoryを一つだけ作成した記憶があります。

折角なのでロボットの例を考えると、ロボットにおけるRepositoryはセンサとかアクチュエータになります。モータのレベルだと複数あるので、左肩のモータみたいなイメージで作ってみます。getterを使用すると呼び出し側は何も意識せずgetできて嬉しいですね。

Adaptorパターン

既存のクラスに別のインターフェースを持たせたい場合に使用します。

ここではロボットの眼球が動くメソッドをMoveEyeとして実装しているものに対して、呼び出し元がLookというよりアプリケーションロジックに即したメソッド名を与えてやります。イメージとしてはDDDにおけるドメインレイヤーとサービスレイヤーの変換をしてくれるイメージですが、ドメインレイヤー内でも異なるコンテキストにおいて二つの呼び名がある場合等に使えそうだなと思いました(開発の場面では統一してくれ…)。

Bridgeパターン

関連の強い2つのクラスを互いに影響しないようにしたまま拡張したい場合に使用思案す。

例えばロボットクラスを定義して、ロボットの内部状態の遷移など変更の少ないコアになる部分はRobotクラスに、変更の多い首機構の動作はNeckクラスに切り出すとします。ここで、それぞれを派生したXXRobotを作ったりXXNeckクラスを作ったりすると組み合わせは4通りになってしまう問題があります。

そこで、Robotクラス側はNeckクラスとXXNeckクラスの呼び分けをNeckクラスに任せ、Neckクラス内で分岐させる処理を記述します。多分見た方が早いので書いていきます。

Compositeパターン

階層構造をなすオブジェクト全体に再帰的に一連の操作を実行させたい場合に使用します。

書籍における例だと枝及び葉をメタファとしてディレクトリ及びファイルの再帰的な表示の際にディレクトリとファイルを同様に扱う為に共通のインターフェースを定義しています。これについてはロボットにおける活用シーンをパッと思い浮かばない為、シンプルにC#に書き換える形で書いていきます。Compositeが枝で、Leafが葉になります。

Decoratorパターン

既存のクラスのインターフェースを変更せずに機能を追加したい場合に使用思案す。

Adaptorとの違いについて、Adaptorが呼び出し元にとって都合の良いインターフェースを用意するのに対して、Decoratorパターンは機能の追加が目的になります。ということで、ロボットの眼球が動くときにその値をロギングする機能を試しにつけてみます。

Facadeパターン

既存のクラスを複数組み合わせて使う手順を定石化したい場合に使用します。

これは単純で、複数のクラスのメソッドを呼び出すメソッドを別クラスで一つ作ってあげて、呼び出し元はそのメソッドを呼んであげる形になります。例としては、センサ値に応じてモータを回転させるために、値を読みだしてから数値を変えて書き込む処理を書いてみます。値はダミーかつ間にモータに投げる値を決める為のロジックが挟まるとは思いますが、とりあえずRandomで取ってきた値をそのままモータに投げるコードです。

Flyweightパターン

小さくて軽いオブジェクトをたくさん作りたい場合に使用します。

簡単に言えば、インスタンスをプールして、新規でインスタンスを作る際にプールに空きがあればそこに代入することでインスタンス化のコストを低減するイメージです。正直Arduinoみたいなメモリカツカツ勝海舟みたいなMachineでもなければ必要はないのかなと思ったのですが…書籍では会社員の例でHashmapのキーに部署を、値に人名をセットして、部署と名前でクエリを発行する際にインスタンス化する代わりにプーリングされたインスタンスを使用しています。が、あいにくクエリを発行する良い例を思いつかなかったので、一先ず写経します。

ちょうどこの前角度・位置計算の際に値オブジェクトを使おう!と言った際に数10FPS出しながら何度も値オブジェクトをインスタンス化するのは流石にはばかられるな…と思ったのですが、実質値オブジェクトの定義が壊れちゃう気がします。ただ、パフォーマンスの為に値オブジェクトを崩す際の例として捉えてみます。くそ遅い綺麗なコードはくそ遅いですからね…

単に値を保持しているだけですが、一応コードを書いておきます。イメージとしてはRobotクラスに各モータの値を保持しているイメージですが、値オブジェクトではないですね、悲しい…

こうやって書いてみると、目的は違えど処理としてはSingletonパターンに近いものを感じますね。

Proxyパターン

あるオブジェクトが他からアクセスされた時にフックしたい場合に使用します。

イメージとしてはDecoratorに近いですが、Decoratorは”機能の追加”であるのに対してProxyは”別の処理の挿入”になります。Decoratorは呼び出し元がどのような機能を持っているか明確に意識するのに加えて、Proxyは呼び出し元が感知しない内部処理を追加するイメージですね。…というのを意識しつつDecoratorと同じようなコードを書きます。DecoratorパターンではLookWithLoggingとしましたが、ProxyではLookメソッドの名前でロギングをフックします。

Proxyなので、IRobotインターフェースを継承して、元のRobotクラスと同じメソッド名でMoveEyeメソッドを作ります。余談ですがネットワークの文脈でリバースプロキシをプロキシと呼ばれると、プロキシのことを何て呼ぶのか気になってしまいますね。

Chain of Responsibilityパターン

一つのリクエストを複数のオブジェクトのいずれかで処理させたい場合に使用します。

書籍の例では、重なったGUI部品をクリックされた時にどれかしらで処理したいとのことですが、ここではLoggerを実装してみようと思います。Info, Warn, Errorとログレベルが低い順に遷移していってどこかでキャッチされる仕様ですね。ただ、各ロガーは次のロガーしか感知しない為、最後に番兵を置く処理は書いておかないとすり抜けるのが怖いですね。また、順番が固定になってしまう点もちょっと微妙なので、その辺り何とかする方法を考えないとですね…

Commandパターン

複雑な内容のリクエストを送れるようにしたい場合に使用します。

パッと思いつく例として、丁度今実装中のロボットで3自由度で動くバージョンと5自由度で動くバージョンが混在しているのですが、ドメインレイヤーに実装されている3自由度用のモータを駆動するメソッドと5自由度用のメソッドを呼び分けるのに使えそうです。衝撃の事実としてそのプログラムをPythonで書いているので、とりあえずまた新規で書いていきます。

余談ですが、DDDにおけるCommandオブジェクトとは異なる概念のようです。GoFのCommandパターンではメソッド内に呼び出し先のメソッドの列がFacade的に記述されていますが、DDDのCommandオブジェクトはあくまでRepositoryへのクエリのようなパラメータとなるプロパティをオブジェクトにまとめたDTOのようなもので、そのパラメータによって何を行うかは呼び出し先側に記述されます。

正直実装例を書きながらCommandオブジェクトとして3DoFか5DoF分のパラメータをもらってそれによってRobotクラス側で処理を分けた方が分かりやすいなと思ったので、Commandパターン(GoF)が役立つのはメソッドの呼び出し順も含めて制御したい場合なのかなと思います。例えばセンサ値を読んだら直ぐにモータを動かしたい場合とセンサ値を読んだ後にしばらくWaitする処理を呼ぶとか、アプリケーションサービスとして書きたいかなと納得しました。

Interpreterパターン

構文解析の結果を実行したい場合に使用します。

正直文字通りのインタプリタとしての機能以外思いつかないのですが、メリットとして構文解析結果を先頭から順番に参照しながら実行せずに構文解析結果を保持するオブジェクトの集合自体にその実行機能を併せ持つことができる点があるようで、オブジェクト自体に機能があるのはなんか素敵ですね。というわけで普通の加減算を作っていきます。

変数を保持するIntOperandはシンプルに変数を持っておくだけで、加算を実行するためのAdditionメソッドも値を二つ持ってそれを適当なタイミングでGetValueしたときに実行する形になります。機械学習やってる方だとTensorFlowのプレースホルダーでこういった処理を学んだ方もいるのではないでしょうか。僕は今更プレースホルダーってこういうことかと気づきました。Operatorの定義自体は値オブジェクトを一通り書いた方だと見たことある書き方なのではないでしょうか。プレースホルダーとして考えると、Flyweightみたいに同じ値の場合はインスタンスを作らずpoolからとってくる処理を書いても良いかなと思えますね。

ちなみにExpressionクラスはIntOperandクラスと共通のインターフェースとなるOperand型を継承してGetValueメソッドを実装しているので、勿論Expression自身を他のExpressionの左右のOperandとして代入することも可能です。丁度後輩に「Interfaceは重要なので理解しておくと良いですよ」と言ったばかりですが僕はあまり理解できていません。

Iteratorパターン

ごちゃごちゃと蓄積されたオブジェクト群に順番にアクセスしたい場合に使用します。

Iterator自体はC#に組み込みで使用できるインターフェースが存在するため、ご存じの方も多いのではないでしょうか。Javaでもjava.util.Iteratorが使用できるようなので、実装面としてはHasNextメソッドとNextメソッドを実装してあげてコレクションから順番に次の要素を取り出し続けてあげればよいです。ロボットっぽい例はあまり思いつかないですが、各モータの値を読み出す時に順序性は気にしないのでとりあえず標準出力したいみたいなパターンを想定して書いてみます。

Iteratorの役割を果たすServoIteratorクラスを作ってあげて、ServosクラスにもIteratorを返すメソッドを用意します。この状態だと相互参照になるので、あまりエレガントではないですね。

ちょっと調べてみた感じ、Enumratorを使えばテンプレート型を使用してIteratorに近いことができそうでした。あと、個人的にNextメソッドの挙動がIndex++か++Indexで変わってしまうのは怖いなと思いました、最初のアクセス時に0番目を取得することを考えるとIndex++が正しいですが、ぼうっとしていると、そこのミスでバグらせてしまう可能性がありますね…

Mediatorパターン

複数の部品を相互作用させたい場合に使用します。

複数のクラス間で相互にやり取りするためのメソッドを作ってしまうと相互参照になってしまい依存関係が壊れちゃうので、バスになるMediatorクラスを作ってあげるイメージです。ロボットの首と上半身それぞれのパーツが互いの情報を用いて角度を調整できるような仕組みを作ってみようと思います。

とはいえ、お互いに外部のクラスが何かしたらそれをフックしてメソッドを呼んでもらう形にはなるので、直接依存関係は持たないまでも外部のクラスを前提としたメソッド名はつけることになります。引数が相互参照にならないような依存関係になるのがGoodですね。

Mementoパターン

オブジェクトの状態をいつでも戻せるようにしたい場合に使用します。

端的に例を表すと、エディタのUndoですかね、内部的にはリストをStackらしい使い方をする形にして、複数回のUndoに対応できるようにしているみたいです。Stack"らしい"と記載したのは、n回前の値を直接取りたいケースに対応する為です。内容としてはListに突っ込んでインデックス指定で取り出しているだけですが、各値をMementoクラスに格納してMementoTakerがそれらの機能を包んでくれる感じです。結構愚直に書きがちなのでこういった専用クラスのパターンを覚えておくのは良い気がします。

Observerパターン

自分以外のオブジェクトの状態が変化したことをチェックしたい場合に使用します。

Decoratorにロギング機能を追加するのとイメージは似ているのですが、今回は状態変化の通知ということでオブジェクトの実行結果が必要になってきます。その為、オブザーバメソッドを用意したうえで、状態を持ったオブジェクトの側にも通知用のメソッドを用意する形になります。Proxy挟むとか悩んだんですが、どうしてもメソッドの途中で状態変化を起こす関係上通知するオブジェクト側にオブザーバに通知しに行く方法が必要そうですが、オブザーバをインターフェース経由にすることで複数のオブザーバに通知できるようになります。多対多なので、状況によってはバス経由して一元管理もありかなという気持ちもあります。

ObserverパターンはPub/Subモデルのイメージになるので、ROSを思い浮かべますが、ROSのPub/Subにも応用できて、Publisher側はリングバッファに値を詰めた上で一旦別スレッドにSubscriberの数分用意されたバッファに値を詰めなおして、各SubscriberにNotifyしています。ちなみにROSのSubscriberの実装は結構興味深くて、先のバッファからリングバッファに詰められた後に、各値が入ったアドレスをメインスレッド上に存在する一つのバッファに格納し、そこから各Subscriberが受け取って処理するようです。モータの角度値とか距離センサのセンサ値を読み込むときも、一度同じバッファに入れているんですね(メインスレッドで全Subscriberに届くメッセージを処理するために、一か所にキューイングしないと届いた順に処理できないんですね)。ROSのトピック通信の内部動作を眺めていると頭が混乱してきますが、こういったパターンの採用で知っている部分はパターン化してより重要なロジックに集中できそうな感触があります。

qiita.com

Stateパターン

オブジェクトの処理内容を状況に応じて切り替えたい時に使用します。

ロボットでも自律操作とリモコンによるマニュアル操作を切り替えたり、セーフモードへの切り替えといったStateを管理したい場面があります。実装としてはシンプルで、

ただ、自律操作とマニュアル操作では仕様が大きく異なり、共通のインターフェースで括るのは難しそうです(自律操作だと引数なしで勝手に動かして、マニュアル操作は引数で動作のパラメータを受け取りたい等)。あくまで状態管理の見通しが立ちやすくなるメリットを享受するに留めるか、Stateごとに処理がほとんど変わらず共通化できる場合に活用するのが良さそうと感じます。

この例だと、GetCurrentStateメソッドがシンプルにStateを返しているだけなので良いですが、Stateによって引数が変わってくると厄介です。アプリケーションサービスからの呼び出しを考えると、そもそもAutoモードの時にマニュアル操作用のメッセージを送っても無視するのが正しいので、Null可のCommandオブジェクトを各State共通の引数としてやると丸く収まりそうですが、Stateごとに引数が大きく異なる、という場合には結構困ります。今実装中のロボットでは各Stateごとにロボットの姿勢値を更新するメソッドを別で用意して、それぞれ値を受け取った時にStateが正しくなければ破棄する実装をしているので、もうちょっと良い方法を考えたいですね。

Pythonで書いてるコードだと、Autoモードの時は別スレッドで生成しているのでstart/stopを切り替え時に行っていますが、自動で姿勢を生成するので引数はなし、逆にManualモードで動かすときはposeクラスのインスタンスを受け取っているので、ここの共通化は難しいですね。実際首だけ動かす、及び上半身だけ動かす、を共通のPoseで持ってますが実際Poseの子クラスでChest, Neckクラス持たせて渡してあげた方が良いかな…とか思いつつ、ここに別のパラメータが混じった場合に一つのクラスにまとめるのか?とか考えないといけないので、アプリケーションサービスとして一度に呼ぶメソッドとして渡したいパラメータ以上のものを共通パラメータとしてCommandオブジェクトにまとめるのは筋が良くなさそうです。

Strategyパターン

いろんなアルゴリズムを好感しながらプログラムを実行したい場合に使用します。

実際研究でもロボットの動作生成のアルゴリズムやパラメータを変えたバージョンをいくつも用意しながら試すときに、それぞれメソッドを作成して書き換えながらやっていました。しかし、これをプログラムの実行中に切り替えることを考えるとインターフェースを使って共通化したほうが良さそうだなという感じがします。試しに、人の方向を見る戦略と見ない戦略をそれぞれ切り替えられるよう作ってみます。

Template Methodパターン

大きな処理を部分的に変更できるようにしたい場合に使用します。

ロボットが動作するまでの処理を考えてみると、カメラが人を感知して人の座標を算出→ロボットの動作の意思決定→眼球のモータに値をセットといった流れを試しに考えてみます。ここで、半自律アルゴリズムの為にロボットの動作の意思決定の前に外部から受け取った指令値を意思決定に反映する処理を追加してやりたいと考えます。この場合、共通で行われる元々の処理をテンプレートとして用意したうえで、追加及び切り替えたい処理を切り出したメソッド内に記述していく形になります。また、テンプレートをabstractクラスとしたうえで各メソッドをprotectedに制限することでテンプレートメソッド以外から各メソッドを呼び出さないようにする効果があります。

Visitorパターン

たくさんのオブジェクトの集合に処理を追加したい場合に使用します。

Visitorパターンでは、拡張対象のオブジェクトそれぞれに対して、追加処理のオブジェクトを相互参照する形で関連付けて、追加対象オブジェクトからはそのオブジェクトを呼び出します。各追加対象オブジェクトごとに相互参照になるメソッドを追加しないといけないので、比較的疎結合になるとはいえなぜ使うんだろうか…と思い調べたところ、こういった記述がありました。

Visitorパターンでは、データを保持するクラスとアルゴリズムを実装するクラス(Visitorクラス)に分けます。

もし、アプリケーション内にデータ構造があり、いくつかのアルゴリズムがそのデータ構造にアクセスする場合には、

Visitorパターンを利用することで、データ構造はデータの保持とアクセスに集中することができます。

既存のクラスを修正することなく、機能を拡張できます。(オープン・クローズドの原則)

qiita.com

ということで、オブジェクトのデータ構造と処理の分離をイメージしながら記述していきます。相互参照なのが気になりますが、元々一クラスで記述していたものを分けるという前提の元MoveNeckメソッドをRobotDataから分離しています。実務的な嬉しさにまだピンと来ていないですが、とりあえずヨシ!

おわりに

一通り書いてみて、丸一日かかりました(夕方から始めたら深夜になったので日をまたぎました)。正直各パターンに対する理解の度合いとしてはまだ浅いですが、会話の中で用語が出てきたら頭に思い浮かべつつ詳細を調べてキャッチアップできる程度のインデックスは頭に貼れた気がします。書籍で実装例を眺めるだけだと妥当なパターンにしか見えないですが、特定のユースケースでのみ役に立つものに対して実際にユースケースを考えて実装してみると、変なところで納得がいかなかったりデザインパターンの使用目的が微妙にずれていて実装に難があったり、困難に遭遇します。実際銀の弾丸なんてことはなく適切な用途で使用しないとただ難読化したりパフォーマンスを落としかねない為、そういった鍛錬は日々積んでいく必要があると改めて実感しました。

あとブログ形式で文字を連ねていたら初めて10000文字に到達しました。以前結構長めの考察とかマッチングアプリの体験談を書いた時も2,3時間かけて勢いでバッと出力しても5000~6000文字程度だったので、まとまった分量のものを書くには頭を整理する時間と休憩が必要だと思いました。あとスクショで貼ったコード片はusing箇所も含めれば結構な行数になるので、タイプした文字としては10000を大幅に超過していますね。

結構疲れましたが、グラップラー刃牙愚地独歩が最大トーナメント三回戦の渋川戦において披露した菩薩の拳に至るまでにも、0.99999…と9の字をひたすらに半紙に書き続けてついに1に至ったという逸話もあるので、日々精進していきたいですね。