ブログ「サイバー少年」

ブログ「サイバー少年」へようこそ!
小学六年生ごろからプログラミングを趣味にしている高校生のブログです。
勉強したことについての記事などを書いています。フリーソフトも制作、公開しています。
(当ブログについて詳しくは「ブログ概要紹介」を参照)

サイバー少年が作ったフリーソフトは「サイバー少年の作品展示場」へ

[GoF] クラスで表すパターン解説

ネタがないときはGoFデザインパターンの記事を書くのが鉄板になりそうですが、

GoFデザインパターンは23種あるようにみえて、実は記事にするほどでもないクソみたいなパターンも多いので、わりと弾薬は減っています。


そういえば、そもそも私がGoFデザインパターンの勉強に使った本は、「Java言語で学ぶデザインパターン入門」という本で、
Java使えませんが、ほとんどC#と同じだし、書いてある内容は言語に依存しないものなので、読んでいたんですが

この本の著者の結城浩という方、数学にも精通していて「数学ガール」という本で、すごく有名なんですね。
読んだことはないですが、私でもたまに耳にしていた本です。

数学の色々なテーマについてのシリーズ本になっているみたいで、なんかよく分かりませんが高校生の女の子とかがなぜか数学詳しくて、取り巻きと一緒に数学について考察するストーリーものなんでしょうか。

私が現在勉強している論理学の「ゲーテルの不完全性定理」について扱っている巻もあるようなので、ぜひ機会があれば読んでみたいですね。


さて今回は、あまりクラスで表現しようと思わないものをクラスで表現してみました、というコンセプトのデザインパターンがいくつかあるので、それを紹介します。


具体的には、Stateパターン、Mementoパターン、Commandパターンです。

クラスで表すというカテゴライズでやるなら、StrategyパターンとInterpreterパターンも取り上げるべきだと思うのですが、
Strategyパターンは単純すぎるのと、Interpreterパターンは需要がニッチすぎるので、やめておきます。

まあ正直に言うと面倒臭いだけなんだがな!





State

State、ステートとは、状態のことで、プログラムではよくある概念です。

ようするに、今は入力受付中とか、計算中とか、エラー中とか、プログラムが複数の状態になりうるわけで、現在の状態に応じて異なる処理をしたくなることがあるのです。

C言語のプログラムではよく、整数型の変数で1がステートA、2がステートB、3がステートCみたいに対応させて、現在の状態をデータで表しているのを見かけますが、それををクラスで表そうということです。


なぜ、クラスで表すのかというと、C言語時代のやり方だと非常に汚いのです。

なぜかというと、整数型の変数でステートを保持していたとしたら、ステートに応じた処理をしたいときは、if文なりswitch文なりで、ステートの値と処理を対応付けないといけません。

しかし色々な場面で、ステートに応じた処理をすることがあると思うので、つまりは分岐のための同じようなif文やswitch文がたくさんの場所に存在してしまいますよね。


このとき、あらたにステートDという新しいステートを追加することになったら、どうなるでしょうか。

たくさん存在しているif文なりswitch文なりを、すべて編集しないといけません。

しかし、第一に編集しなければならないというのが負担なのと、たくさんのif文やswitch文をすべて把握しきれない可能性があります。


上のようなC言語時代の方法では、ようするに

・クリックしたとき
-ステートAの処理
-ステートBの処理
-ステートCの処理
・キー入力のとき
-ステートAの処理
-ステートBの処理
-ステートCの処理

というまとめ方をしていることになります。
クリックしたとき関数内にif文、キー入力のとき関数にif文みたいなことです。
これを

・ステートA
-クリックしたとき
-キー入力のとき
・ステートB
-クリックしたとき
-キー入力のとき
・ステートC
-クリックしたとき
-キー入力のとき


というふうに、ステート単位でまとめるのがStateパターンであるともいえます。

つまり、現在のステートを表す変数をひとつ持っておいて、そこに特定のステートを表すオブジェクトをひとつ入れるのです。

もちろん、ステートのクラスに継承関係を持たせて、ステートを入れる変数の型はどのステートでも入るようにしなければなりません。


そして、それぞれのステートのクラス内に、〇〇なとき、このステートではどうするかという処理を書きます。

“〇〇なとき”が複数ある場合は、それをメソッド単位でまとめます。

これで、メインのプログラムからは、たとえばクリックされたら、現在のステートとなるクラスのクリックされたときメソッドを呼ぶことで、あとはそちらでステート固有の処理を行ってくれるということです。

これならポリモーフィズムになっているので、if文やswitch文で分岐する必要がありません。


分岐がないので、新しいステートを増やしたいときも、そのステートを表すクラスを作るだけで、とくに他の部分をいじる必要はありません。

“〇〇なとき”という、メソッドに相当するものが増えたり減ったりする場合は、それぞれのステートのクラスを編集しなければならないので大変ですが、

文法上コンパイラが、それぞれのステートのクラスにちゃんと対応するメソッドを設けたかチェックしてくれるので、少なくともそれを編集しておくのを忘れるということはありえません。
これも利点です。


また、C言語時代において、整数でステートを管理するのではなく、フラグ変数というのがあります。

これは、複数の注目の対象があって、それぞれの対象だけを語るステートが複数ある管理方法といえます。

Stateパターンでは、基本ひとつのステートの変数を持ちますが、やろうと思えば注目の対象を複数に分割させることもできるでしょう。

ただ、ONとOFFの2パターンだけとかでも、いちいち注目の対象ごとにク2つずつクラスを定義してやらないといけないので、相当面倒です。


なので基本、ステートを表す変数はひとつだと考えたほうがいいですね。

チューリングマシンとかも、現在のステートというものを単一で持つ様式になっているので、計算機科学においてステートという概念があるのかもしれないですが、よく知りません。


ところで、あるステートをクラス(オブジェクト)で表すにあたって、そのステートとなるクラスが内部に変数などのステートを持っていたら、ステートが決まったのにステートの中にステートがあって、実はステートが決まったわけではない…わけわからないですよね。

そのため、ステートが決まれば、全体の状態は決まりきっていないといけない、
それには、ステートとなるオブジェクトは自身の状態を持たない不変オブジェクトでなければなりません。

また、不変オブジェクトにするということは、ステートとなるクラスはインスタンスをひとつだけ作ればよくて、あとはそれを使い回せばいいということになりますね。

そういうときにSingletonパターンというGoFデザインパターンのひとつを適用することもできますが、ここでは省略します。




Memento

MementoパターンもStateパターンと同じく、状態をクラスで表すパターンですが、“状態”という言葉の意味がまったく別物です。

Stateパターンは、今ならどういう処理をするかという状態を表すのに対して、

Mementoパターンは計算した結果とか、テキストエディタの現在の編集内容とか、データをクラスにカプセル化して保存するためのものです。

このデータを保持する役のクラスをMemento役といいます。


つまりはメチャクチャ簡単に言ってしまうと、あるアプリケーションがstring CurrentTextを持っていて、今のCurrentTextをどこかに保存したいというときに、

string Textというフィールドを持っているMemento役のクラスをひとつインスタンス化して、TextフィールドにCurrentTextを設定して保存しておくというだけのことです。


ただし大事なのは、Textフィールドは外部からアクセスできないようにしておくということです。

Memento役のオブジェクトは、どういうフィールドを持っているのか、基本的には外部からわからないようにしておくのです。

外部からは、どうなってんのかよく分からないけど、とりあえず何かを保存しているらしいクラス、という見え方になります。


しかしながら、保存したいデータを元にMementoを作ったり、Mementoの中身を現在のデータに反映させる処理を行うクラスからは、Memenoの内部にアクセスできるようにしておく必要があります。

そのような処理をするクラスは、基本的にはデータを持つクラスが演じることになります。
Originator役といいます。


作ったMementoのオブジェクトをリストにして保存しておく処理まで、そのクラスに書いてしまうのは冗長なので、その役目のクラスもあります。

Caretaker役というクラスで、このクラスからはMementoの内部は見えません。

Originatorは作ったクラスをCaretakerに保存してもらったり、Caretakerから昔のMementoを引っ張り出してきたりするということです。


ところで、Originator役のクラスからだけはMementoの内部が見えるようにするというのは、なかなか一般的な言語ではどのように書けばいいのかピンときませんが、

JavaやC#では、同じ名前空間のクラスだけからはアクセスできるメンバというのを設定することができるので、それを使ってMementoとOriginatorだけを同じ名前空間に定義します。


オブジェクト指向の機能を飛び出しているので、あまり美しくないと思いますが…。

しかも、そのような機能がない言語もあると思いますが、そういうときは、
「Originator役からしかMementoの内部にはアクセスできないものとする」と念じるしかないですね。




Command

Commandパターンは、ひとつの処理動作をクラスで表現してしまおうというパターンです。

たとえば、クリックするとか、キー入力とか、はたまたファイル作成、ファイル削除などです。

そして、それを実行する機能も持っていて、たとえば「ファイル作成」を表すクラスに実行を命令したら、ファイル作成をするということです。


多くの言語に関数オブジェクトという機能があって、これはある関数をオブジェクト化してしまう機能なので、この機能で処理をオブジェクト化することができますが、

それに比べて、Commandパターンではわざわざ処理に対応するクラスを定義するという面倒っぷりで、しかしそのおかげで便利になるものもあります。
(このクラスをCommand役といいます。)

関数オブジェクトでやると、どうしても単一のクラスに集まってしまいがちですからね。


さて、処理を表すクラスにどんな種類があるのかは、アプリケーションによりけりなわけですが、
抑えておくべきは、処理の対象となるオブジェクトが存在しているということです。

ここでいう処理というのは、ようするに何かにアクセスして状態をいじる行為なので、アクセス対象のオブジェクトもまた別にあるわけです。


そのCommand役のクラスから別のオブジェクトへのアクセスは誰がやるのかといえば、Mementoパターンでは外部からMemento内のデータを取り出しましたが、
Commandパターンでは処理を取り出すというのは無理で、Command自身にやってもらうしかないので、

Command役のクラスは、そのアクセス対象のオブジェクトへの参照を保持していることになります。


そして、Commandは実行のためのメソッドも持っていて、そこがまさに処理内容、コマンドになっています。

これによって何ができるかというと、関数オブジェクトと同じことですが、
ずーっと何をするのかという情報だけ保持しておいて実行はせず、実行するべきときがきたら実行させるという芸当ができるわけですね。


さらに、処理における引数的なものがあったときに、関数オブジェクトでは実行時に引数を渡さなければなりませんが、

Commandパターンを用いると必要に応じて、Commandのインスタンスを作るときに引数を設定しておいて、呼び出すときは何も与えず呼び出すということも可能です。


これが何を意味するかというと、様々なコマンドが存在すると引数の仕様もバラバラになってくるわけですが、その差はインスタンスを生成するときの差におさめられるので、

コマンドを実行するところでは、各種のコマンドを統一的に扱えるということです。

まあ関数オブジェクトでも、それがクロージャになっていることも多いので、
上手いこと使えばクロージャを生成する段階で引数となる情報を入れることができるかもしれないですが、ちょっと本来の用途とは違うと思います。


ところで、どんなコマンドであれ統一的に扱えるなら、複数のコマンドを一気にやる、いわゆるマクロのようなものを、これまたひとつのコマンドにしてしまうこともできます。

つまり、そのマクロコマンドを実行させたら、その内部に保持してある複数のコマンドを全部実行させるようにするということです。

このマクロコマンドと、その中のコマンドのクラスの関係は、このまえ記事「[GoF] データ構造に関するパターン解説」で書いた、
GoFデザインパターンのひとつのCompositeパターンであるといえます。





今回は全部、比較的簡単なパターンだったので、サンプルコード無しで抽象的な話ばっかりだったのですが、

さすがにこれでは説明不足だったでしょうか…。


今回のパターンは、オブジェクトの構造を示すデザインパターンという立派な名前はあまり似合わないような、

小さな問題に対する小さなアイデアを、わざわざデザインパターンに書き起こしたような強引な感じは少しあります。


なので、別にクラスの関係がどうなってるのかを把握する必要はないと思いますが、

状態やデータや処理をオブジェクトにしてしまうんだという発想を頭のどこかにしまっておくのは、ためになると思います。


さて今後、またGoFデザインパターンの記事を書くとなったときに、もういいパターンが残り少ないんですが、もう限界でしょうか…。

Builderパターン、Bridgeパターン、ChainOfResponsibilityパターン、Proxyパターンあたりは、かろうじて面白いですけどね~。


完全に次回のGoFの記事で扱うパターンを名指ししましたが。

この4つをひとつの記事に書くのは大きすぎますが、2つずつにわけるのも若干小さすぎるのは悩むところです。

tag: プログラミング オブジェクト指向 GoF デザインパターン クラス ステート 処理 カプセル化

コメント

コメントの投稿

トラックバック

トラックバック URL
http://cyberboy6.blog.fc2.com/tb.php/448-861c7655
この記事にトラックバックする(FC2ブログユーザー)

当ブログをご利用(閲覧等)になる場合は必ず「当ブログの利用規定」をお守りください。