AWS Lambda の同時実行プロセス数が制御したくて

yukki99 と申します。ブログを書くのが久しぶりすぎて。

久しぶりのテーマとしてとりあげるのが、最近、・・・って、前回からやたらと時代が進化しているのですが、話題になってる AWS Lambda です。サーバレスアーキテクチャだなんて言葉も作られてるわけで。なんか、世の中では AI だのディープラーニングだのがやたらと持て囃されるのですが、あえて時代に逆行して。

で、まぁ、これらのまともな使い方は、そこらへんのブログ記事を Google 先生に拾ってもらえればいいと思うので、捻くれた記事を書こうかと。

そもそも、Lambda は、API Gateway を始めとして、何か別の AWS のサービスのバックエンドとして、EC2 とかのクラウドインスタンスを使うことなく、処理だけを実行しようというものなわけですが、今回は、その中でも一番最初か2番目には触れるであろう、Amazon S3 のバックエンドとして使おうというケースを中心に。

この S3 上での操作をトリガとして、Lambda 関数を起動できるわけですが、今回、その中で、最も謎なトリガである、Delete Event トリガとはなんぞ?というのが、主題です。

で、まず、Lambda には実行上制約がありまして、それが、

  • 最大実行プロセス 100 (緩和可能)
  • 最大メモリ容量 1.5 GB
  • 最大実行時間 5分

というのがあります。まぁ、他にもいろいろありますが、今回問題にしようとしているのが、この最大実行プロセス数。実際緩和可能なわけですが、よっぽど大きなサービスにでもしようとしない限り、そんなバカみたいにたくさんプロセスを走らせる必要性は、多分ないはずです、お金もかかりますし。で、仮に、1000とか10000とか突っ込んだとしても、Lambda 側で勝手に Throttling してくれるわけで、そもそも実行数自体は勝手に制御してくれるわけです。

ですが、まぁ、一種の病気であるのですが、これを、どうにか、自前で制御したくなるわけです。

さて、話が二転三転して申し訳ないですが、そもそも、Delete Event トリガ。これが謎なのは、そもそも、Post(Put) Event をトリガにするのが、本来は自然なわけです。イベントで飛んできたキーを使って、その S3 上のファイルを対象に処理を行うわけですから。

Delete Event ということは、言うまでもないのですが、既に、S3 上にそのファイルは存在しません。存在しないファイル名が飛んできて、何をするのか?という話なのですが、そもそも、発想を変えてみるわけですね。

その指定されたファイルが「存在しているかしていないか」に意味を持たせられないものかと。

まぁ、ここまで来るとだいたいどんな話になるかはご想像通りかと思うのですが、要するに、出来損ないの、バイナリセマフォ、というか、ミューテックスのようなものを作ろうという話なのですね。

要するに、このファイルを作成し、作成したファイルのキー名を持っていないと、Lambda 関数を実行することは出来ないという制約を付けようというのが、基本思想です。このファイルが作れなければ、要するに、ファイルが既にあって、そこにあるファイルを持って実行している Lambda 関数がある場合、そのファイル名をキーとした Lambda 関数を実行できない、というルールを作ってやるわけです。

当然ながら、Lambda 関数はそのプロセス終了時に、きっちり、このファイルを削除しなくてはならない。これが、Delete Event トリガを引くというお話です。

で、この話を続ける前に、ちょっと断っておかないといけないのが、必ずしも、このファイルを削除するという処理を保証することが難しいという点。メモリオーバー、タイムアウト、あるいは、キャッチしないまま例外を投げっぱなしにするなどすれば、あっという間に、S3 削除プロセスを実行することなく Lambda 関数は死んでしまいます。

そうすると、当然、ファイルは残ったまま、Delete Event トリガが引かれることもないので、一応、ここでは、都合よく、最大実行時間 5分というのを建て前として、ファイルを作って5分以上経過しても、放置している場合には、そのファイルは無効となる、というルールを作っておくことで、対処できるものとします。

で、話を戻すわけですが、当然ながら、ここまでの話を総合しても、結局、ファイルの Put をトリガとしてしまうと、実行が完了してしまい、クリティカルセクションに入れずに死んでしまうことになるので、登場人物を何人か増やすことにします。

S3 に置くディレクトリとして、4種類を考えます。

まず、Lambda 関数を Put Event で起動するためのトリガディレクトリ、先ほどの制御を行うための、ミューテックスディレクトリ、実行結果を保存するそれ以外のデータディレクトリと、もう1つ、Put Event トリガを引かない一次的な置き場であるキューディレクトリです。

要するに、いきなり、トリガディレクトリに入れずに、キューディレクトリに入れてしまおうという発想ですね。で、キューディレクトリから、トリガディレクトリにファイルを移動すると、Lambda 関数が実行されるというのが、基本的な同時実行数制御のルールになります。

さて、ここで問題になるのが、誰がじゃあこの移動をやるのか?ということになります。

当然、分かりやすく、その処理自体を、トリガディレクトリにぶちこんだら実行されるというのを1つ置きます。これがないと、当然、キューディレクトリにいくらおいても、最初の Lambda 関数を実行することができませんので。で、この時には、先ほどのルールに従い、必ず、ミューテックスディレクトリに指定されたキーのファイルを Put し、そのキーを保持しておく必要があります。先ほど書いたように、後ほど、この Put Event トリガで起動された Lambda 関数は、あとで、このキーで指定されたファイルを Delete しなくてはなりません。

もう、ここまで来れば、大体やりたいことはご理解いただけたかと思いますが、この、Delete Event を、先ほど言った、キューディレクトリからトリガディレクトリへの移動という Lambda 関数のトリガとして登録しようという話です。要するに、この Lambda 関数には、トリガディレクトリへの Put Event と、ミューテックスディレクトリからの Delete Event の双方を登録しておこうという話です。

Delete Event をトリガとする場合は、当然ながら条件を満たすわけですが、それ以外のケースの場合でも、ミューテックスディレクトリに存在していればそこで実行が遮断され、存在しなければ、晴れてクリティカルセクションに入ることができるわけです。

さて、ここまでの話で終われば簡単なのですが、じゃあ、これでうまく同時実行が本当に制御できるのか?というのが話として続きます。何か、そこには問題があるのではないか、いや、きっとあるはず。

続く

広告
カテゴリー: コンピュータとインターネット | コメントをどうぞ

Hello world!

Welcome to WordPress.com. This is your first post. Edit or delete it and start blogging!

カテゴリー: 未分類 | 1件のコメント

続、Getter 不要論

というわけで、yukki-ts@かわさきでございます。
まぁ、まだこの話続けるのって感じですが、まだ続けます。
くれぐれも言っておきますが、誰も、アクセッサがいらないとは一言も言っていません。
Setter がないクラスなんて不便で不便で仕方がないと思います。
で、Getter はいりません。結論です。
いろんな意味で、Getter が必要な状況っていうのがどうしても、想定できないわけです。
じゃ、まず、分かりやすい所から言ってみます。
まず、基本的に Java で言うところのフィールド、まぁ、とにもかくにも private なところにある
ありとあらゆるもので構いません。
C++ で言うところのメンバ変数を private にしてしまったのでアクセスできない。
だから、Getter を作ろう。
まず、この発想からしておかしいんですね。
[1] アクセスする必要がある属性があるなら、そのクラスのプロパティとすればいい
まぁ、これに尽きるわけです。どうして、それを Getter で必要としているのなら、最初から自分で持っていないのかと。
よくよくまぁ、考えてみれば、自分がその値を持っていて、その値を、書き換えてもらうようなことを、
そのクラスにしてもらっているはずなんですが、
それならそれで、Getter はいらないんです。値を書き換える処理をそのクラスに委譲すれば済む話です。
もちろん、そのクラスに同様のプロパティが必要ないとは言いませんが、
そもそも、この2つの違うクラスに属するプロパティが、どうして、全く同じものでないといけないのでしょうか?
それこそ、全く同じであるならば、必然的に、それが属性である必要性がなく、
それこそ、別のクラスとして分掌すればいいでしょうし。
ちなみに、そのように分掌したクラスであれば、初めて Getter としての価値が出てきます。
まぁ、そこまでしたのなら、構造体でもいい気もしますが。
[2] Getter としてメソッド化しておけば、多態性が・・・
まず、多態性を「構造の変化に対応させるため」と解釈しているならば、根本的に勘違いしてる可能性があります。
多態性のどこに「構造」なんてニュアンスがあるってんでしょうか・・・。
本来、多態性とは、「異なる固有の振る舞いを示すクラスに対して同一のメッセージを通知すると、
異なるサービスが提供される」というまぁ、書いてみれば、実に当たり前の事を表します。
このどこにも、「構造」の変化がどうのこうのとは記述されていません。
Getter は、どこからどうみても、固有の振る舞いとはいえ、構造であるところのフィールドに大きく依存したもので、
もし、構造が動的にでも静的にでも変わることが前提であるならば、そもそもにして、
Getter そのものが多態性の考え方から随分と横道にそれたメソッドと言えます。
まぁ、実装すれば必然的に分かることでもあります。Getter は何かを戻すわけですから、
引数でどうのこうのしたり、オーバーライドでどうのこうの実装するシステムでは、太刀打ちできません。
Covariant がどうたらって難しい言葉を知っていたところで、結論から言えば同じことです。
構造の変化には対応できません。もう、3年以上この問題で悩んできて解決できないんで、
これは、そもそも、無理なんです。
じゃあ、どうすればよいか。何度も言ってるような気がしますが、
相手に、何をさせるか判断を委譲すればいいわけです。書き換えたいものを渡して、書き換えるようにメッセージを
通知すれば、あとは、多態性でなんとでも出来るわけです。
[3] そもそもカプセル化したものをそのまま取得できるという前提がおかしい
カプセル化した属性は、固有の振る舞いに対する「状態」を表すものとして定義することもできるわけで、
そんなものを、全く関係ないクラスが関知する必要はありません。
できたとして、「状態」を「遷移させる」メッセージを通知するところまでです。
何度も言うようですが、状態を知る必要があるならば、そこで用いるものは、これから知ろうとする側が
持つべき情報であり、決して、対象のクラスが持つものと完全に合致するはずはありません。
仮に、ありとあらゆる対象インスタンスの状態を全て表示したいとして、
その段階で、対象とするクラスの状態であるところの属性と、
状態の表示という目的を果たすためのものとでは、この意味が違います。だからこそ、クラスを分掌しているはずですし、
そうでないならば、そもそも、クラスを分掌していることに意味はありません。
すぐにでもくっつけてください。それでどう実装しづらくなっても知りませんが。
[4] 結果からすれば、プロパティをそのまま返すだけのメソッドになってしまう場合
問題は、結果的に return でプロパティをそのまま返すメソッドが、じゃあ本当に必要ないかどうかという話です。
これまでの話から言ってみれば、要するに、private にしてアクセスできないからとかいう理由や、
それにこじつけて、構造が変わるかもしれないからとかそういうよく分からない理由で、
やみくもに全プロパティに対して Getter を作ったり、あるいは、状態が知りたいとかそういう理由で、
そのまま Getter を作ったりするってのはよくないんじゃないかって話をしてました。
当然と言えば当然ですが、関連を表す別クラスへのインスタンスを返すような Getter はそれ以前の
問題です。必要ない以前に存在は許されません。もちろん、それが、集約だろうが、合成だろうが同じことです。
ついでに言ってしまえば、属性だって、合成のようなものなわけですから、同じ事です。
だからこそ、これらをフィールドって言う形で、わざわざ private で閉じるわけですから。
では、本題に戻ります。本当に、いらないのかどうか。
考えられるケースとして、まぁ、すぐに思いつくものが2つあります。
(1) 派生属性を表すもの
 たとえば、属性が2つあり、その2つがあることで、1つの派生属性が導けるのならば、
これは、属性であると同時に、メソッドでもあります。というより、メソッドという形をもって派生属性というものを
定義します。まぁ、もっとも、この場合、確実に const なメソッドになるので、振る舞いとしては、
Getter に近くなります。これと、通常の属性の Getter がどう違うのかと訊かれると一瞬困ってしまうのですが、
もしそれが必要だったとして、それを、public にする必要はないと言ってしまえばそれまでです。
属性であることには変わりないわけですから。
(2) 多態性の結果、プロパティをそのまま返すもの
 上に挙げた多態性がどうのこうのということの本来の意図を整理すれば、結局、
こういう考え方に落ち着きます。すなわち、取得しようとするものが、どういう結果から導かれるのかは全くわからないが、
とりあえず、それを属性として持っているクラスであれば、安直にそれを返してもらおう。
ただ、もし、それを属性としてもっていなくとも、メソッドとしていれば、まぁ、なんとかクラスがやってくれるだろう。
・・・ まぁ、実際、上の [2] で書いていたのも、そんなことを思ってはいなくて、本質はこっちにあると信じたいと思います。
と、ここまで書いて気付くはずです。そもそも、これって属性が private だからアクセスできないので、
メソッドで return しようという Getter じゃあないんです。
この (2) の形で const なメソッドを組むものはたくさんこの世に溢れかえっています。
配列の長さ、文字列の長さ、その他いろいろとあることでしょう。それを、一から計算するのか、
属性として持っているのかは、クラス次第でしょうが、いずれにせよ、メッセージとして、
配列の長さを取得する、文字列の長さを取得するというものを通知すれば、それが返って来ます。
それを、配列、あるいは文字列を管理するという責務を持つクラスに対して通知していたとするならば、
実に普通の固有の振る舞いであると言って問題ないと思います。
そして、そこまで考えた上で、仮にそれが、 int GetLength()const; なんて名前で、int Length; というプロパティが
あったとして、それが Getter かと問われれば、違うはずです。
というわけで、まとめてみます。
クラスに属性を記述したあとで、アクセッサを書くのは順序として間違っています。
構造を設計した上で、それぞれのクラスの責務において必要と思われる、関連、集約のほか、
そのクラスの本質を表す合成、属性を記述し、
その上で、クラス相互の振る舞いを設計する上で、関連先にメッセージを通知する必要がでて、
初めて、メッセージを処理するものと、固有の振る舞いであるところの操作をくくりつけます。
もし、そこで必要なものが、既に属性としてあるのならば、それを返すように固有の振る舞いを
メッセージに対して行えばいいわけです。このときの操作は、決して属性にアクセスするためのものではなく、
メッセージに対して応答するための振る舞いの1つに過ぎません。
担当者に情報を訊くのは、その情報を管理することを責務としているならば全うなメッセージであると言えます。
で、その人に名前やら年齢やらを訊くのは、メッセージとして可笑しいわけです。
ま、名前くらいなら、名刺を交換すればすむ話です。メッセージとしても十分有り得るでしょう。
年齢が知りたいなら、担当者としてではなく、別の立場のクラスと認識して、
その人にメッセージを通知した方がいいでしょうね。
それが、友人だか、同僚だか、恋人だかどうだかは知りませんが。
では
カテゴリー: コンピューターとインターネット | コメントをどうぞ

多態性について本気だして考えてみる

というわけで、yukki-ts@かわさきでございます。
で、いきなり本題ですが、多態性は異なる振る舞いに対しては有効に働きますが、
異なる「構造」に対しては無力です。
とまぁ、ここから今回のお話はスタートします。
まぁ、たとえば、構造は構造でも、構造の「生成」であれば、多態性はききます。
デザインパターンでググってもらえばすぐにわかることでしょう。
・・・デザインパターンって言葉が出るんなら、別に構造だって、多態性がきくじゃん。
まぁ、そういうわけですよね。結論から言えば、うまいことやれば、多態性のきく構造を
考える事は可能です。
まぁ、構造に関するデザインパターンを列挙すればすむ話なんですが、
実際のところ、デザインパターンにおいて「構造」と名の付くものについては、通常、
構造が異なるのではなく、同一視可能な構造にしてやることがその本質なんですね。
Composite や Decorator なんてのはあの特徴的なクラス図であるからこそ実現できることなんですよね。
そうなってくると、結局、残るは、Proxy やら Facade やら Flyweight やらっていう、
多態性という意味から言えば、まぁ、そうと言えなくもないけど、あんまり・・・って感じのばっかりが
並んでるわけですよ。
残るものといえば、クラスにインターフェースを適合させる手法であるところの Adapter と、
異なる継承系統をくっつける Bridge っていう手法ですね。
まぁ、Bridge はともかく、結局、構造がどうのこうのということと、多態性がどうのこうのと言い出すと、
結局、それは、Adapter 的手法にならざるを得ないわけですね。そして、これが、結局のところ、
構造が異なる場合における多態性を適用させる場合の限界と言えます。
Adapter の適用方法を見れば分かるように、基本的には、インターフェースがその多態性の限界となります。
ってなわけで、もともとあったデータ構造については、全く同一なものでない限り、インターフェースの対象には出来ないこととなります。
まぁ、そういう事を書き出してしまえば、やはり、異なる構造に対して多態性を適用させるという考え方そのものが
まぁ、間違っているというわけです。
多態性は純粋に同一メッセージに対する固有の振る舞いの違いを指すわけですから、
構造とは全く関係ない概念なわけです。
まぁ、残念ですが、もし、本当に構造がどうのこうのと言うのであれば、
型推論可能なスクリプト言語などにするべきかと思います。
本質的に多態性で型システムが組まれてるわけですから、きっと、要求に答えられると思いますよ?
では
カテゴリー: コンピューターとインターネット | コメントをどうぞ

キャッシングについて

はい、yukki-ts@茶色の県でございます。
今回は、ちょっと真面目にキャッシングについて考えて見ます。
って、お金を借りるわけじゃないですよ?
ものすごく問題を単純化するので、こんな問題を考えてみます。
ものすごく問題を単純にするために、ここで、最終的な目標は、
int 型整数 100 個を 1000msec 間隔で出力することを考えます。
この 1000msec 間隔で int 型整数を出力するスレッドでは、
出力するたびに、常に 1 つずつしか int 型整数をとることは出来ません。
もちろん、このときに、100個既に取り出せる状態にあるならば問題は
全くないわけですが、ここでは、最大値が M | 0< M < 100 個であるとします。
つまり、何も考えずに M 回取り出してしまうと、そのまま1つも取り出せなくなってしまいます。
当然、最終的に 100 個取り出さなくてはならないため、
別のスレッドで、この入れ物に 1個ずつ追加していきます。
ま、当然、必ず中に1個以上入っているように中に1個ずつぶちこんでいけば、
全く問題は発生しないはずなのですが、
こまったことに、入れる側は、t | l < t < u[msec] の範囲でその入れるタイミングに
ムラがあるという状態を想定します。
一応、ここまでを整理すれば、なるべく、取り出し側は中身が 0 にならないようにしなくてはなりませんし、
入れる側は、中身が M を超えないようにしなくてはならないということですね。
というわけで、この段階で、未知変数が、M, l, u と3つあるわけで
(t は一応確率変数ですが、まぁ、l と u から決定できるのでカウントしません)、
こいつが決定されたとするときに、
さて、問題です。
入れる側が 100 個入れるまでの間、入れ物の中身が 0 個になったり、
M 個になったりする回数を最小限にしようとします。
さて、入れたり出したりしようとしたときに、そのような事態になっていたならば、
それぞれ何 msec 待機すればよいでしょうか?
 
うーん、書いてて分からなくなってきた・・・。
では
カテゴリー: コンピューターとインターネット | コメントをどうぞ

メッセージとパラメータ

yukki-ts@茶色の県でございます。
前回、さんざん議論をした結果、導かれた結論として、
狭義依存関係をメッセージとして用いる場合、それが、
委譲を表すものであり、双方のクラスにとって同レベルな依存関係を持つならば、
インターフェース化可能であるという結論ですね。
では、もし、同レベルでないならば、という問題に変わってしまったので、
こちらに移ってみましょう。
この問題を、解決するもっともらしい方法は、
狭義依存関係で結論付けられそうなメッセージと、そうでないものに
分割するという方法ですね。
責務分掌の考え方からすれば、特殊なパラメータは、
それを必要とするクラス側に属するように定義するのが基本となります。
問題は、それがどう属するかという話ですが、
合成であれば、コンストラクタ時に初期化するか、操作で変更するか、
あるいは、委譲するかという選択肢がありますが、いずれにせよ、
メッセージでは対応しきれない可能性が高いですね。
集約であれば、独立した提供インターフェース経由で別のメッセージとして通知することになります。
関連であれば、それに対する要求インターフェース経由で変更することになります。
なんか、言ってることがわかりにくいですが、
もし、合成であるならば、異なるインスタンスごとに異なるパラメータを取り付けることが
可能であり、同時に、常にパラメータを取り付ける手間がかかるということですね。
一方、関連であれば、常に、同一のパラメータを取り付け、一括に変更することが
要求インターフェース経由で可能ということを意味します。
一方で、集約はその中間的な性質を帯びており、提供インターフェースとしてパラメータを
各々に対して通知することで変更ができることを意味します。
まぁ、いずれにせよ、ここで2つの結論が導けるわけですね。
あるクラス A から責務分掌されるクラス B があるとします。
で、クラス C というものと、クラス D というものを考え、このいずれもが
メッセージとして通知可能なものであるとするとき、クラス C と クラス D に2つの
異なる性質を見出すことができるということです。
1つ目が、前回から言ってるように、委譲を行う場合のもので、
A 側から C には広義な依存関係を、B 側から C には狭義な依存関係を引っ張ります。
一方で、D は、A 側からは狭義な依存関係を、B 側からは広義な依存関係を引っ張ります。
これは、今回の話であるところの、B 側にとっての特殊なパラメータを意味します。
一見すると、ただ逆にしているだけにも見えます。
下手をすると、こっちの方が全うなことをしているようにすら見えます。
なぜでしょうか?
極単純な結論を導けば、これは、アクセッサ に他ならないからです。
さて、どうやら、いつかした議論と同じような結論を導いてしまいそうですね。
これまでの話を総合するとこうなりそうです。
・ 委譲を行うメッセージによって与えられたパラメータでそのメッセージ通知結果が返却される
・ アクセッサは、委譲を行うメッセージに対して委譲先に特殊化されるパラメータを通知するために使用される
・ 委譲を行うメッセージとアクセッサのメッセージは異なるインターフェースで提供されるべき
というわけです。というわけですと言いながら、実に困難な道しか残されて無いですね。
それが、難しいから困ってるっちゅうのに。
属性として持ってしまっているならば、こうするしかありません。
一方で、集約として持っている場合は、提供インターフェースに適合する形でパラメータを渡せます。
まぁ、柔軟性を考えればそれが一番ですが、最初の前提を崩すことになります。
そのパラメータは、委譲先側にとって特殊化されているのですから。
関連として引っ張る場合は、もっと深刻で、それが共通パラメータである必要があります。
そうでなければ、実に単純な結論ですが、それが、属性であるべきという結論を導かざるを得ません。
うーん、やっぱり、難しいぞ、これ。
結局、非常に嫌な結論ですが、一番もっともらしいのは、
非オブジェクト的なパラメータを返す共通ファンクションをインターフェースとして持たせ、
それを元に、アクセッサインターフェースにキャストしてやるしかないのでしょうか?
これが嫌な結論だと思う理由が、これが美しくないのと同時に、
結局何も解決できてないからですね。前提は、それが、委譲先に特殊であることなのですから。
やっぱり、解決できそうにないですね、これ。
どうしても、その枠組みを統一できない範囲で、別の枠組みに分割しなくてはならないのでしょう。
うーん、わかんなくなってきた・・・。
では
カテゴリー: コンピューターとインターネット | コメントをどうぞ

狭義依存関係についての考察

はい、寄生虫、もとい、帰省中の yukki-ts@茶色の県でございます。
ところで、オブジェクト指向とはメッセージパッシングであるというのは、
実に、C++ 的でない発想なわけですが、
まぁ、突き詰めていくと、オブジェクト指向という考え方の限界というものを
どうしても見つけてしまうことになります。
それが、どうにも、この、狭義依存関係にあると言えるのではないかと、
俺は思うわけです。
さて、一応、わざわざ「狭義」という風につけているからには、
広義な依存関係というものが存在するわけで、
もちろん、それは、関連・集約・合成(コンポジション)・汎化/実現といった
UML でいうところの矢印を使ってかける奴全てを包含したものを指すと
ここでは定義しておきます。
まぁ、通常、矢印の向きは、メッセージ通知の方向性を意味するわけですが、
同時に、矢印の先のクラスに依存するということ、
まぁ、あらためて、一応書いておくと、要するに、そのメッセージを処理するために関わる
要素のいずれかの変更によってその挙動が変化してしまうという意味ですね。
まぁ、よく、汎化の矢印の向きが継承の矢印の向きと逆になっているのが
ん?と思われる最たるものだと思いますが、
メッセージ通知性をみても、広義依存性をみても、この矢印の向きは全うだと言えます。
っていうか、矢印の向きさえ一致していれば、広義依存性とメッセージ通知方向性は
同値であると言い切ってもいいのではないかと思います。
さて、これらを前提とした上で、あらためて、「狭義」依存性について考えていきます。
もちろん、狭義をつけない依存関係とは、UML クラス図で言うところの、点線矢印に
相当する概念ですね。結論から言えば、広義依存関係から、関連・集約・合成(コンポジション)、
ついでに汎化と実現を取り除いたものなわけですが。
そもそも、狭義依存関係とは何か?
分類すれば、グローバルアクセス可能性と一時アクセス可能性、及び引数によるアクセス性の
3つを指すわけですが、さて、果たして、これらと、狭義でない各種の関係とは、
何がどう違うというのでしょうか?そもそも、狭義依存関係という考え方そのものが、
オブジェクト指向と相容れるものなのでしょうか?
まず、結論から言えば、狭義依存関係なくしてプログラミングするのは無謀です。
不可能と言ってもいいでしょう。
そして、このことから、オブジェクト指向と狭義依存関係が、他の各種関係と明確に
オブジェクト指向的な意味ですみわけが行われない限り、それが、
オブジェクト指向の限界に一致すると言えます。
さて、そうは言いたくないという、とりあえずオブジェクト指向寄りの信者として、どうにかこうにか、
この問題に一応の決着をつけてみたいと思います。出来なければ、
残念ながら、オブジェクト指向の限界というどうしようもない壁の前に、ひざをつくことになります。
さて、いろんな側面から、いったい何が問題なのかを、取り上げてみましょう。
何に起因するかといえば、最初にも言いました。メッセージパッシングそのものですね。
オブジェクトに対してメッセージを通知することで、そのオブジェクトの責務を遂行させるのが、
まぁ、基本的なメッセージパッシングのあり方なわけですが、ここに大事な問題点があるんですね。
いったい、オブジェクトの責務の遂行とは、何をもって評価されるものであるのか?
ここで、全うな判断をするならば、当然、引数と戻り値で評価するのが当然といえます。
では、引数と戻り値とはいったい、何なのでしょうか?
オブジェクトと言ってしまえば、当然、これが、先程の例にあるように、
クラス図における狭義依存関係に相当するわけで、一見何も問題ないように思えます。
そして、ここで問題点が露呈しますね。もういちど書きます。
狭義依存関係とそれ以外の関係は相容れるものなのでしょうか?
まず、直接の解決にはならない1つの妥協点を見つけてみます。
つまり、狭義依存関係によって定められるものが関連でないならば、
それは、集約の関係によって代行できるというものです。
これは、言い換えると、集約の状態を変更させうるメッセージのみが、
狭義依存関係を伸ばすことが出来るという妥協点ですね。
そもそも、集約の関係性は、極めて疎な集合を表現するもので、
それは、全体側の責務を表現するものでも、代行するものでもないわけです。
あ、一応、念のためですが、通常、責務を表現するものとは、合成(コンポジション) か
汎化/実現 という関係であり、代行するものを表現するものは、関連として記述されます。
強いて言えば、それは、「責務として」通知する集合を表現するものと言い換えることが
できるとも言えるでしょう。
さて、こう仮定する場合、妥協点でありながら、命題に対しては
あまり良好な結果をもたらさないことが分かります。
すなわち、この場合、狭義依存関係とは、集約に他ならないわけですから、
定義上、狭義依存関係足りえないわけです。
さらに言ってしまえば、この場合の引数そのものが、狭義依存関係として
定義し得ないと言っても過言ではないことになります。
ところで、狭義依存関係の定義には、既に述べたように、まだ2つほどあります。
グローバルなアクセス性については、例えば、クラスメソッドへのアクセスという
非常に分かりやすい例があり、これが代表的な柱を成します。
また、一時アクセス可能性についても、もっとも単純な例を言えば、
それが、例えば変換性を提供するものであると言った、ユーティリティ性を持つもので
あれば、これもまた代表的な柱を成します。
いずれにせよ、言えることは、この2つは、ユーティリティ性というものを持っていることを指し、
これが同時に、それが、オブジェクトである必要性の希薄さを物語っています。
そう考えてみると、命題に対する残されたアタックの仕方は2つしかないようです。
つまり、それが、ユーティリティ的、すなわち、オブジェクト指向としての枠組みの意味合いの
希薄さを物語るものとしてしか表現できない代物であるか、否かということですね。
さて、もし、否と考える場合には、それが、広義依存関係として表現するに足りないという
何かもっともらしい説得力のある必要性についてを論じる必要があります。
違う側面から2つの例を挙げてみます。
たとえば、多態性の意味からメッセージの引数をなるべく汎用性のあるもの、
可能であるならば1つも引数のないものに限定したいという要求があるとしましょう。
この場合、当然、引数を渡さなくてもすむようにするためには、
それを、狭義でない依存関係でひっぱる必要があり、それが、もし、
集約関係であるならば、先程のように、狭義依存関係ではない形で
別の枠組みとして実装せざるを得ないことになります。
そして、あるいはこうとも考えられます。
先程の例にもあったように、それは、メッセージパッシングによる結果論を表すもので
あるという仮定をする場合です。
問題は、それが、なぜ、広義依存関係ではなく、「狭義」依存関係で表現するべきなのか
ということですね。
そもそも、なぜ、メッセージパッシングによる結果が必要なのでしょうか?
それはすなわち、メッセージパッシングの目的が、集約性を持つクラスから、
操作性を持つクラスに対して、その処理を委譲しようとするところに起因することになります。
クラスの責務を考える上での原則として、もちろん、よく言われていることでもあるわけですが、
データとそれに関わる手続きをカプセル化することというものがあります。
集約性と操作性をクラスとして分離している段階で、実にこの原則に大きく反しているという
ことが出来る可能性は高いわけですね。
この考え方を一言で言い切ってしまうと、こういうことが出来ます。
すなわち、オブジェクトでない「データ」というものが、混じってしまっているということです。
もし、データが本質であるならば、それを、オブジェクト指向の枠組みから
切り離すか融合させるかのいずれかの道をとるしかなく、そのいずれも、
オブジェクト指向という枠組みから見た場合には、茨の道であるという結論を
この命題に対する、有力な解を見出せない限り、出さざるを得ないわけです。
そこで、もう1つの妥協点を見出してみたいと思います。
つまり、メッセージとは、オブジェクトであるという妥協点ですね。
実のところ、こう結論をつけると、何もかもが解決するかのように見えますし、
現実的にみても、それが実際のところの解決案としか正直思えません。
そして、このようにモデル化した瞬間、メッセージとは、常に狭義依存関係を満たすクラスの
インスタンスにならざるを得ないことになります。
こう考えると、結局、次の2つのいずれかの結論を導くしかありません。
すなわち、これが、オブジェクト指向という枠組みで説明できる事項であるか、
はたまた、これをオブジェクト指向という枠組みで説明できる別のアーキテクチャで
表現できるかという2点です。
当然、後者が表現できるならば、この問題はたちどころに解決したことになります。
まぁ、それが出来ないからこうして議論をしているわけで、
ここでは、合理的に、前者の命題に対して真であるという結論を導きたいわけでございます。
メッセージとはオブジェクトであり、それでいて、オブジェクト指向的な枠組みにおける
いかなるオブジェクトとも違うという結論を導くのは、それはそれでなかなか愉快なことだと思います。
すなわち、それは、オブジェクト指向的という枠組みにおけるオブジェクトではなく、
メッセージという枠組みにおけるオブジェクトであるという発想で、
それは常に狭義依存関係によって成立するものであるとする考え方、
もっと言ってしまえば、狭義依存関係そのものが、他の関係とは全く異質なものであるという発想ですね。
こうすると、オブジェクト指向的な発想の中に全くオブジェクト指向的な発想でないものを
取り込んでいるという考え方のもと、大部分をオブジェクト指向の枠組みで定義することになります。
実に変な話ですね。メッセージパッシングがオブジェクト指向の本質であると言っているのに、
肝心のメッセージそのものは、オブジェクト指向の枠組みでないと言っているわけですから。
うーん、そんな難しい考え方をしなくても、これは、要するに、委譲という考え方であって、
実にオブジェクト指向的な発想ではないかと考えるのが普通だと、俺は思います。
そもそも、委譲とは何っていう話です。
結論から言えば、1つのクラスに対して複数のクラスから同時に広義依存関係と狭義依存関係を
ひっぱることと言ってしまうことと同値です。
なぜ、そんなことをするかと言えば、簡単に言えば、責務分掌がしたいからに他なりません。
単一責務の原則とは抽象化におけるもっとも頼りになる指針のひとつであるわけですが、
何も、1つの責務が2つの責務に分かれる可能性を否定しているわけではありません。
もし、分割できるなら、委譲すればいいだけの話です。
ものごっそい単純に言ってしまえば、結局、委譲先のクラスからは、必ず狭義依存関係を引っ張ることになるわけです。
ん?だったら、これでいいんじゃないの?結局。
もし、この方針からこの命題を読み返すとこうなります。
すなわち、メッセージパッシングとは、委譲に他ならないと定義付けられるならば、
それをオブジェクト指向の枠組みで説明できるということになります。
さて、ここまでの結論をまとめると、メッセージパッシングには次の2種類+αしかないことになります。
・ 集約の状態を変化させる
・ 処理を委譲させる
わざわざ+αと言ったのには理由があり、生成メッセージと消滅メッセージが常に含まれることから、
メッセージパッシングに4種類はあるという考え方があるからです。
さて、ここでだまされないようにしたいものです。
ここで挙げられた4つはすべて、プロシージャ的性質を帯びるものであるからして、
ファンクション的性質を帯びるものが、まるで、メッセージパッシングではないかのように
見えるからですね。
まぁ、その場合、そもそも今回の議題であるところの、狭義依存関係とはまるで関係ない問題になるわけで、
別に今回の問題とは関係ないように思えます。
それが、もし、別のドメインにあると仮定するならば、1つの結論を導けたことになります。
すなわち、狭義依存関係は、委譲という考え方を用いる場合に限っては、
どうやら、オブジェクト指向という枠組みで説明できそうであり、
それ以外のあらゆる文脈でその使い道は、ユーティリティ的な、すなわち、手続き的な意味であり、
オブジェクト指向の枠組みとして説明し辛いものである可能性が高いという結論ですね。
それなら、まぁ、安心しますが。
うーん・・・、なんか、釈然としない。
あ、そうか、議論の流れが変な方向に行っちゃったせいだ。
つまり、狭義依存関係を持つようなメッセージは、インターフェース化可能であるかという話です。
うーん、これもまた、実に複雑な問題です。
どんな問題かを言ってしまえば、早い話が、依存関係にほかなりません。
矢印の先が依存性の向きという話をしたことから分かるとおり、
メッセージで狭義依存関係をもつならば、その狭義依存関係先は、
それに関わるいずれのクラスからも依存することになります。
これは、当然、どちらか一方、たとえば、それが、狭義でない依存関係を持つ側にとって
特別なものでは有り得ないということを意味します。
これに忠実にモデル化するならば、それが合成であるならば、
狭義依存関係側は、それを常に要求インターフェースとしてとらなくてはならないという話で結論付けられます。
こう考えると、狭義依存関係を持つインターフェースへのメッセージパッシングは、
常に、引数として要求インターフェースを要求することになるわけです。
どうやら、だんだんと問題領域が明らかになってきました。
つまり、狭義依存関係でメッセージパッシングを説明しようとすると、
メッセージ通知先にとって、そのメッセージが特殊である場合に、
これまで話してきた原則が崩れることを意味し、同時に、それを、
インターフェース化できない可能性が出てくるということを意味します。
どうやら、今回の問題は、ここに集約できそうですね。
ドメインが変わりそうなので、一度、ここで、この問題の考察を中断してみましょう。
では
カテゴリー: コンピューターとインターネット | コメントをどうぞ