Fig. 2 スレッドセーフではない場合に起こりうるシナリオ ・検査と登録の間で切り替わりが起こると , 本来 1 度しか呼ばれるはずのない生成 ( new ) が 2 度呼び 出される。これだけでも悪いが , さらにもし次の切り替わりが「 eturn が終わるまで起こらなけれ ば各スレッドはそれぞれが作ったインスタンスを返す。これは一意なインスタンスを返すという 仕様に反する。 スレッド 1 スレッド 2 検査 巛《 スレッドの切り替わり 検査 生成 , 登録 retu 「 n スレッドの切り替わり 生成 , 登録 「 etu 「 n ・ただし , 検査などは List 4 の次の位置 8 : un ー ess @__instance_ @__instance— new # 10 : 15 : return @——instance— # 検査 生成 , 登録 # return 的な結合を目指した建築パターンが数多く 載っていて素人にはなかなか楽しいもので す。建築が塀のような小さなものから都市 のような大きなものまで扱い , さまざまな レベルに複雑さがあるという点では案外プ ログラミングに似ているのかもしれません。 もし一般的な設計論というものが存在しう るのならばソフトウェア設計が建築設計に 学ぶところも少なくないはずです。 さて , プログラミングにおけるパターン は , ライプラリとしては再利用できないよ うな抽象レベルをまとめたものですが , C + + や Java ではライプラリ化が困難なパターン も Ruby では可能になる場合があります。 Sin gleton のほかに標準で用意されているもの には , observer. rb があります。その用法は Ru by 本に詳しく解説されているので , ここて 繰り返すことは避けましよう。それにして も , 高次の抽象と思われているパターンが 動的な言語である Ruby においてはライプラ ロ ass というクラス リに成り下がるのはおもしろいことです。 示しますが , 2. は A と表示します。 Module. 1. は # く CIass 0 ⅸ 8108 0 > のような文字列を表 2. p A = CIass. new 1. p a = CIass. new ましよう。 数がクラスの名前になります。例で見てみ つまり最初に定数に代入したときにその定 文字で始まる名前でなくてはなりません。 い出しましよう。ただしクラスの名前は大 代入が名前を付けることだということを思 最初に付けられた名前になります。 Ruby の ラスを生成して返します。クラスの名前は スが与えられた場合はそのクラスのサプク 生成して返します。 CIass. new の引数にクラ ラスメソッド new は新しい無名のクラスを Class は ModuIe のサプクラスです。そのク new も同様の動作をします。クラスにはモジ ュールと同様に名前空間としての機能もあ りますから。その名前は重要です。そこで , Module と CIass には名前を返す name という パラメータを持つクラスは常に同一のもの を満たすようにしてあります。つまり , 同じ Array0f (x) . e 引 ? Array0f (Y) ならば , X. eql ? Y この ArrayOf() という関数は念のために ているわけではありません。 す。ただしすべての追加メソッドに対応し 要があるわけです。 List5 に実装例を示しま スを返すので , 動的にクラスを生成する必 すいでしよう。そしてこの rayOf ( ) はクラ を表すクラスならば読みやすいし , 使いや rray()f(lnteger) という関数が lnteger の配列 トライズされたクラスを考えるわけです。 A こともあるかもしれません。そこでパラメ です。また配列の要素を文字列に限りたい から , そこでチェックを設ければよさそう は配列を生成するときと追加するときです あるでしよう。配列の要素が格納されるの 合によっては要素を整数に限りたいことも 格納できます。これはこれで便利ですが , 場 たとえば , Ruby の配列はどんなものでも えてみます。 ラメトライズされたクラスというものを考 しなければならない場面です。例としてパ う ? 考えられるのはクラスを動的に生成 その必要なときとはどんなときでしょ 動的なクラス生成 る必要があります。 ら , わかりづらさが増大することを覚悟す しコーディングの手間を省く目的で使うな 要に応じて使っていきたいところです。も から使うものではないでしよう。やはり必 こういった機能があるにはありますが普段 脈でメソッドを定義することもできます。 ある class ー eval を使えば生成したクラスの文 また ModuIe#module—eval やその別名で メソッドがあります。 96 C MAGAZINE 2001 4
、。ーコラム 特異メソッドと特異クラス Ruby プログラミングにおいては , ちっとも重要ではないのです Fig. A オプジェクトと特異クラスの関係 ( object ℃より抜粋 ) が , 特異メソッドの実現は特異クラスを用いて行われます。 Ruby の オブジェクトは , そのオブジェクトだけをインスタンスとして持つ * Ruby's CIass Hierarchy Chart メタなクラスに属しています。このメタなクラスを特異クラスとい います。 Ruby の構文においては「 class < く」で始まる特異クラス定義 における self としてのみ , その特別なクラスにアクセスすることが できます。 ListA の実験では , 特異クラス定義における se けがそのオ ブジェクトの通常の意味でのクラスと異なることを示しています。 特異メソッドはこの特異クラスへのメソッドの追加にほかなりま せん。この関係は Ruby でプログラミングをするときは知らなくて OtherClass-->(OtherClass) もかまいませんが , 特別なオブジェクトを定数として持つような拡 張ライブラリを作る場合は自分が何をやっているのかわかるので知 っておいたほうがよいでしよう。 Ruby が提供する C の A 円である rb define—singleton_method() は , まさにメソッドを特異クラスに追加 十 AII metaclasses are instances Of the class 、 Class するものです。その定義は class. c にあります。 ※カッコ付きのクラス名は特異クラス ちなみに Ruby のソースコードの 1 つ object. c には , この関係を表 ※横向きの矢印は is ー a ? 関係 すたいへん興味深い図が含まれています ( Fig. A ) 。 ※上向きの矢印は継承関係 特異クラスへのアクセス (singletonclass. 「 b) Copyright ⑥ 1993-2000 Yukihiro Matsumoto copy 「 ight ◎ 2000 NetWO 「 k Applied Communication LabO 「 atory,lnc. cl ass Object Copyright ⑥ 2000 旧f0 「 mation-technology promotion Agency. Japan def singleton—class class くく se げ # この self はレシーバ # この self は特異クラス se げ -->(Object) Object- -->(Class) Class- Str = "a".singleton—class p [Str, string, str. eql?(string)) # っ [String, String, falsel いなければどういうマズいことがあるかを のは , オプジェクト発見のためのカタログ 観点はなかなか思い付きませんが , Ruby の です。オプジェクト指向プログラミングに スレッド機構を使う可能性がある場合は対 g. 2 に示します。ここでいう「スレッドセー おいてもっとも難しい問題であるクラスの フでない」とは List4 で 7 行目と 12 行目をなく 処する必要があります。 Ruby でスレッドセ 設計 , いい替えれば何がオプジェクトかと した場合です。 ーフにするのはたいしてめんどうでもない いう問題に対し , すでに有用であることが ふと思ったのですが , Singleton のインス ので日ごろから気をつけたいものです。 知られた多くのクラス構成をデザインパタ タンス生成はⅲ clude のときに行ったほうが 8 行目で@__instance__ にインスタンスが ーンとして Gamma らがまとめました [GHJV] 排他制御の機会が少ないので効率がよいか まだ登録されていないか調べます。もし登 ある程度大規模なソフトウェアを開発する もしれません。 録されていなければ 10 行目で new を使って 場合は知っておくべき話題でしよう。 インスタンスを生成し , @__instance__ に代 モジュールとバターン 蛇足ですが , パターンという用語は建築 入します。 9 行目から begin が始まっていま で使われていたものに由来します。文献と ところで , s ⅲ gleton という言葉はデザイン すが , これは確実に Thread. critical を解除す パターンの用語で , 「あるクラスに対してイ して C. アレグザンダーの「パタン・ランゲー るために 11 行目で ensure を使いたいからで ジ』囚をあげておきます。専門家の間では ンスタンスが 1 つしかないことを保証し , そ す。そして 15 行目で@ーーⅲ s 面 lce ーーを返して アレグザンダーの考え方に対する批判も多 れをアクセスするためのグローバルな方法 クラスメソッドの定義が終わります。 くあるようですが , この本をめくると , 有機 を提供する」パターンです。パターンという 参考までに , もしスレッドセーフにして 95 極めよ Ruby 道 伝授 !
5 def ArrayOf ( type ) ArrayOf. get—c lass ( ) class ArrayOf く Array CLASS-POOL ( } ELEMENT—TYPE = Object class くく self def [ ] (*arg) new. replace(arg) end def get-c lass ( type ) Thread. critical = true begin CLASS-POOL[type] Ⅱ = CIass. new(self). set—element-type(type) 要素の型が固定された配列クラス (array_of. rb) = 2 & & 矼 g [ 0 ] . is-a?(Range)) List 5 0 if (arg. size type-check ( *arg [ 2 ] ) type-check(arg[2] ) end super def push ( *arg ) type—check ( *arg ) Super end def replace(arg) type—check ( *arg ) super end def fill(*arg) type-check ( arg [ 0 ] ) super end def 引 ement—type type ・ e lement—type end private def type—check(*elm) arg. Size ensure Thread. critical = false end end def set-element—type(klass) se ー f. const—set ("ELEMENT—TYPE" se げ end def ement—type k lass ) defined?(self: :ELEMENT—TYPE) & & self: :ELEMENT—TYPE end def new(size = 0 , filling nil) if fil ling type—check fi lling end super # Not all method is override; # They should be redefine (). g, 十 , 卩 . def [ l=(*arg) if e = e . f 土 nd 日幻 ! 土 . 土 s ー a ? element—type} TypeError ′ unexpected type Of e ー ement 、 # { e. inspect } ”十 ” (#{e. type} for #{element—type) ) ” end end end if ーーを跖 E ーー p ArrayOf ( lnteger ) p a = ArrayOf(Integer)[1,2,3] p ArrayOf( lnteger) . equal? ArrayOf( lnteger) p a. push 1.0 # ! = > TypeError end です。その実装は定数のハッシュに登録す るというものです。ただ定数ハッシュに登 録すると GC されませんから , もし GC の対象 にしたい場合は We Ref を使う必要があり ます。これに関しては咳さんの日 yaway &k:mya ] というライプラリが参考になるでしよう。 ArrayOf ( ) は名前を付けるべき ? さて , 作った rayOf ( ) ですが , このクラ スが返すクラスは無名になります。クラス に名前がないといちばん困るのは , クラス 定義構文を適用することができないことで す。たとえば , class ArrayOf (string); end といった文は構文工ラーになります。これ は , クラス定義構文の class というキーワー ドの次には定数名になりうるものがこなけ ればならないからです。では , ArrayOf() は クラスに名前を付けてから返すべきでしよ うか ? 一概にはいえませんが , 筆者は付 けるべきとは思いません。なぜなら , どの名 前空間で名前を付けるかはこのライプラリ のユーザに選ぶ権利があると思うからです。 また , 以前筆者の友人が , クラスの名前を 引数として渡すようなクラス生成関数を書 いていましたが , これはわかりにくいと思 いました。たとえば , ArrayOfString = ArrayOf (string) は誰が見ても・ rayOf(String) に・ rayOfSt ring という名前を付けていることだとわか りますが仮に第 2 引数にクラス名を渡すよ ArrayOfString (string, ArrayOfString としても , これが S ⅲ ng を要素とする配列を 定義してキ rayO 爲ⅲ ng という名前を付けて いることは , 一見してもわかりません。 動的なプログラミングの落し穴の 1 つは , このへんにあるでしよう。 Ruby に備わった 機能を使えば Ruby の「自然な」書き方とは全 然違う書き方ができます。しかしそうする とプログラムの読みやすさは低下してしま います。それは慣習から逸脱しているから です。クラス定義やインクルードのような せん。これは説明したような信念に基づい ジュールが定義されるだけで何も起こりま です。 require"statistics" しただけでは単にモ 本稿の例では , List 1 の statistics. rb がそう うがよいと思います。 ドの再定義をするような使い方はやめたほ するだけで明示的なⅲ clude を抜きにメソッ たほうが混乱が少なそうです。逆に require e は新たなクラスと関数を定義するにとどめ なので今のままでよいと思いますが , requ さら変更するのはかえって混乱を招きそう です。 jcode は PerI 由来で , これはこれでいま 変更されるので , その利用には注意が必要 ire " jcode " すると既存のクラスのメソッドが ラスを変更することがあげられます。 requ 別の例としてはインクルードが既存のク ことができるのです。 とで , プログラムの読みやすさを保持する ーザに行ってもらうべきです。そうするこ 機能を変更するときは , それを明示的にユ 伝授 ! 極めよ Ruby 道 9 /
List 2 の使用例 * stepl : 単純な TaIker/Listener if ( listener— ) { void changed( ) { listener— = listener; VOid setListener ( Listener* listener—(0) ( } Talker() ・ public: Listener* listener— private: class TaIker { v 辷セ u 引 void update(TaIker* talker) = virtual -Listener( ) { } public: c ー ass Listener { class Talker; using namespace std ー # inc lude <iostream> listene て ) ー→ update ( this public: 土 n セ va ー ue— private : * お試しコード listener 80 c lass Counter : pub lic Tal ker { counter( ) : value 」 0) { } class DigitPrinter : public Listener { changed( 十十 value— void increment( ) { int getValue( ) const { return value— cout < く static—cast<Counter*>(talker)—>getValue( ) くく endl; v 土てセ u 引 void update(TaIker* talker) ( List 4 class Listener ( public: v 土て加引 -Listener( ) { ) v 土てし u 引 void update(TaIker* talker) = の class TaIker ( private : std : : vector<Listener*> ー isteners— public: TaIker() { } if ( std: :find( listeners-.begin( ) , listeners— void addListener(Listener* listener) { void changed( ) { listeners—.clear( void removeListeners ( ) { listeners— . erase(it); if ( ! = listeners-. end( ) ) { ー isteners— std: :find( listeners— . begin( ) , std: :vector く Listener*>.• :iterator 土し = void removeListener(Listener* listener) ( listeners-.push—back( listener); listeners-. end( ) ) { (*辻) → update(this); while ( ! = listeners— . end( ) ) { std: :vector く Listener*>: :iterator it = listeners— listener) listener); . begin ( 十十土 t ー * お試しコード C ー ass Counter private : int value— public: : public TaIker { int main( ) ( Counter c ー DigitPrinter d ー C. setListener(&d); fo て ( 土北土 = 土く c. increment( return 0 ー 十十 i vecto 「を使って 1 つの Ta ⅸ e 「に複数の Listene 「を対応させる * Step2 #include #include : ひとつの T 引 ke てに複数の L 土 s セ ener # inc ー ude く iOSt て eam> く vector> #include く algorithm> く string> counter() : vaIue-(0) { } int getVaIue( ) const { return value—; } void increment( ) { 十十 value— changed( using namespace std; class DigitPrinter : public Listener ( virtual void update(TaIker* talker) { cout くく static—cast<Counter*>(talker)—>getValue( ) くく endl; cout くく string(static—cast く Counter*>(talker)->getValue( ) , ' * りくく endl; virtual void update(TaIker* talker) { class Starprinter : public Listener { return 0 ー c. increment( fo て ( int i = の i く十十土 ) { c. addListener(&s); c. addListener(&d); starprinter s ー Digitprinter Counter C ー int main( ) ( class Talker; C MAGAZINE 2001 4
オプジェクト工房 List 7 template を用いた Talker/Listene 「 class Counter : public Talker<int> ( private: 土 n し value— public: : more generic! (using template' ) * Step4 #inc ー ude く iostream> #incl ude く vector> #incl ude く set> #inc ー ude く a ー gorithm> #inc lude く string> * ListenerBase C ー ass ListenerBase { private : static std : : set く ListenerBase*> va ー ids— public: Listene て Base( ) { valids— . insert(this); } virtual -ListenerBase ( ) { va lids— . erase ( this ); } static bo 引 isVaIid(ListenerBase* listener) { return valids— . find( listener) ! = valids— . end( counter( ) : value 」 0) 0 void increment( ) ( 十十 va lue— changed(value—); using namespace std; class DigitPrinter : public Listener く int> { virtual VOid update(const int& value) { cout くく value くく endl ー cl ass StarPrinter : pub lic Listener<int> { v 土てセ u 引 void 土れセ & value) ( cout くく string(value, ' * ' ) くく endl; std : : set<ListenerBase*> : : va ー ids— template く class Event> class Talker; / / forward decl . * Listener<Event> temp ー ate<C ー ass Event> class Listener : public ListenerBase public: virtua ー VOid update ( const Event& ) * Ta lker<Event> template«class Event> class TaIker { private: std : : vector く Li stener<Event>*> ー isteners— public: Talker() 0 void addListener ( Li stener<Event>* listener ) { if ( std: :find( listeners-. begin( ) , ー isteners— listeners— . end( ) ) { listeners— . push—back(listener); int main( ) { Counter c ー DigitPrinter d; StarPrinter* S = new StarPrinter ー c. addListener(&d); 0. addListener(s); c. increment ( / / ここで不慮の死を遂げても delete 町 c. increment(); / / 迷惑をかけない . return 0 ー Ta ⅸ e 「から導出しない Counte 「 * Step4' : T 引 ke てから導出しない coun セ e て 〃 Talker, Listener, DigitPrinter, starp て inter はほかのコードと同じ ass Counter { private : int value— Tal ker く int> tal ker public: listener ) VOid removeListener ( Listener<Event>* ー istener ) std : : vector<Listener<Event>*> : : 土し era と 0 て it ー istener ); std : : find ( listeners—. begin ( ) , listeners- if ( it ! = listeners-. end( ) ) { listeners— . erase(it); counter() : vaIue-(0) { } TaIker<int>& talker( ) { return talker—; ) void increment( ) { 十十 va ー ue— talker( ) . changed(value—); void removeListeners( ) { listeners-.clear( VOid changed ( const Event& event ) { . beg 土 n ( Std : : vector<Listener<Event>*> : : 土セ e て atO て listeners— while ( 土セ ! = listeners— . end( ) ) { if ( ListenerBase: :isValid(*it) ) ( (*it)->update(event); 十十 it; } else ( it = listeners-.erase(it); 加セ main( ) { Counter c ー DigitPrinter StarPrinter* s = new StarPrinter ー c. talker( ) . addListener(&d); c. talker( ) . addListener(s); c. increment( delete 町 0. increment( return 0 ー コ し 試 お 8 イ 2001 4 C MAGAZINE
オフジェクト工房 temp ー ate<C ー ass Event> class TaIker { Step4 がほしいのは というわけで , 複数の Listener にも存在 しない Listener にも対応できました。です が , ぼくにはこれでもまだ不満が残るんで す。それというのも , Listener::update() の 引数が気に入りませんム 。フ , Listener::upd ate ( ) の引数は TaIker * となっていて , イ べントの発行元である Talker へのポインタ が引き渡されます。ですから , たとえば dig itprinter::update (Talker * talker) では引数 として渡された TaIker * から Counter * に キャストし , さらに Counter のメソッド get VaIue ( ) でカウント値を取得し , プリント ・・カッコ悪いとは思いませ しています。 んか ? だってこんなコードを書いたとた ん , DigitPrinter は Counter からのイベント しか受理できないことになってしまうじゃ ないですか。ⅲ t 値をプリントするのが Digi tprinter の仕事であり , こいつを update ( ) するのが誰であろうが , プリントすべき int 値さえ手に入ればいいのであって , イ べントの発行元なんか知る必要はないはず です。 DigitPrinter::update() の引数として必 要なのは , プリントすべき int 値です。そ うでしよ ? だから DigitPrinter::update ( ) の望ましいインタフェイスは , void DigitPrinter : :update ( Java で書いた Counte 「 /DigitPrinter const int&) なんです。しかしそうすると基底クラスの 仮想メソッド Listener::update() も同じシグ ニチャ , すなわち , class Listener { virtua ー VOid update (const int&) = の て自由に選びたいのに , int 決め打ちでは 引数は Listener の導出クラスの求めに応じ でなくてはならなくなります。 update() の public: VOid VOid VOid addListener (Listener く Event * > * て emoveListener (Listener く Event * > * ) ー changed (const Event&) ー 致悪 ' でしかありません。 らんものでしようか。 ・ template を使う ・・なんとかな 解決策は存在します。 template を使うん です。 Listener を , update() に引き渡した い型を template 引数とした class template と します。 temp late く c ー ass Event> class Listener { virtua ー void update (const Event&) = 0 ー こうすれば DigitPrinter は Listener く int> か ら導出できます。 Listener の template 化に対応して , Talker にも同様の変更を施します。次のような感 じです。 最終的にできあがった template 版 Talker /Listener を List 7 に示します。 Talker から L istener への引数が Talker でなくなったこと で , イベント発行元は必ずしも Talker から 導出する必要がなくなり , より自由になり ました (List 8 ) 。 いかがでしようか。イベント駆動のきわ めて単純なからくりからここまで、凝る ' とができます。ばくのディスクの中にはさ らにもう一歩進めて・ update ' というメソッ ド名までも自由に取り替えることのできる TaIker/Listener が眠っています。いつか機 会があればお披露目しましよう。こうやっ ていじくり回して遊んでいるもんだからオ プジェクト工房は・試作品 ' ばっかりで , い つまでたっても・完成品 ' が生み出されてこ 終わりに ないんだよなあ・・ * stepl : 単純な TaIker/Listener - import java. util . ObservabIe; import java. util.Observer; ー Java version class Counter extends ObservabIe { private int value— public counter( ) ( value— public 土 n セ getvalue( ) { return value—; ) public void increment( ) ( 十十 value—; setChanged( notifyObservers( class DigitPrinter implements Observer ( public void update(ObservabIe from, Object arg) ( system. out. println( ( (counter)from) . getValue( ) public class stepl { public static void セ r 土記 (Counter c) { DigitPrinter d = new DigitPrinter ( c. addObserver(d); c. increment( public static void main(string[ ] args) { Counter c = new Counter( tria Ⅱ c c. increment( どびてククのオブジェクト工房 83
対し removeListener (this) すればいいでし よう。そうすれば changed ( ) のたびに生死 を確認する必要がなくなります。こちらは ぜひみなさん自身の手で挑戦してください。 ・ Java の場合 こでちょっと道草をします。これと同 じことを Java でやるとどうなるでしようか。 Java クラスライプラリが提供する java. util. Observable , java. util. Observer が TaIker , Listener と機能的にはまったく同じです。 Counter は ()bservable を extends , DigitPri nter は Observer を implements することで実 現できます。 Java ではオプジェクトを new 安全な方法にした Talker/Listene 「 く string> #include <algorithm> く set> く vector> く iostream> : 安全な Talker/Listener #include #inc lude # 土 n ude #incl ude * Step3 virtual ¯Listener( ) { valids— . erase(this) Listener( ) { valids— . insert(this); } public: static std; :set<Listener*> valids— private: friend class Talker; class Listener { listeners—.push—back( listener); listeners— . end( ) ) { if ( std: :find( listeners-.begin( ) , VOid addListener(Listener* listener) TaIker() { } public: std : : vector<Listener*> ー isteners— private: class Talker { std : : set く Listener*> Listener : : va ー ids— virtual void update(TaIker* セ引 ke て ) = の ー isteners— listene て s— std: :find( listeners—.begin( ) , std: :vector<Listener*>: :iterator it VOid removeListener(Listener* listener) ( することはあっても明示的に delete するこ とはありません。オプジェクトが使われて いないと Java 仮想マシンが判断した時点 で , 自動的にあと始末が行われます。 List 6 が Java で書いた Counter/DigitPrinte r です。メインルーチンは , Counter c = new Counter ( new DigitPrinter ( DigitPrinter d C. increment ( ) ー c. addObserver (d); d = nul 嵭 } 引 se { C. increment ( ) ー のように途中で DigitPrinter d がスコープ から外れています。これを実行すると・・ あら不思議 , 最後の increment で飛ぶこと なくカウント値がプリントされます。 時点で d はまだ死んでいないようです。 C. a この すかもしれないからです。 でも生き残り , じわじわとメモリを食い潰 い終わったつもりのオプジェクトがいつま 面 , ちょっと怖い気がします。自分では使 Java 仮想マシンって賢いなと感心する反 き残っているんですね。 ているので , あと始末の対象にならずに生 ddObserver(d) の時点で d は c から参照され it = listeners— . erase(it); * お試しコード c 1 ass Counter private : int value— public: : public TaIker ( listener) listener); if ( 辻 ! = listeners-. end( ) ) { listeners— . erase(it); void removeListeners ( ) { listeners-. clear( void changed ( ) { std: :vector<Listener*>: : 土 te て a セ 0 て it = while ( 土セ ! = listeners-.end( ) ) { if ( Listener: :valids— . find( *it) ! = Listener: :valids— . end( ) ( *it)->update(this); listeners—. begin ( counter() : value-(o) { } int getValue( ) const { return value— void increment( ) { 十十 v 引 ue changed( using namespace std ー class DigitPrinter : public Listener { cout くく static—cast<Counter*>(talker)—>getValue( ) くく endl; virtual void update(TaIker* talker) { return 0 ー c ・ increment(); / / 迷惑をかけない . delete 町 / / ここで不慮の死を遂げても 0. increment ( c. addListener(s); c. addListener(&d); StarPrinter* s = new starprinter ー Digitprinter d ー Counter c ー 土北 main( ) { cout くく string(static—cast<Counter*>(talker)->getValue( ) , ' * ' ) くく endl; v 土てに u 引 VOid update(TaIker* セ引 ke て ) { class S セ a て P て土 n し e て : public Listener { 82 十十 it; C MAGAZINE 2001 4
た方は少なくないと思います。標準入出力 によるオーソドックスなプログラム , いわ ゆるコンソールアプリケーションでは , ま ずメインルーチンからスタートし , そこか らサプルーチンを何重にも呼び出しながら 処理が進行しています。たくさんの関数が メインルーチンを頂点とする木構造をなし ていて , どの関数であってもメインルーチ ンを起点に呼び出しを追いかけていけば必 ずやたどり着くことでしよう。 ところが GUI アプリケーションは違いま す。 ・ボタンが押されたら XX する ・キー入力があったら 00 する のように , プログラムは " 何かが起こる ( イ べント ) " と " そのとき何をする ( アクション ) " の集合体であり , 木構造にはなっていませ ん。たくさんのアクションの断片が浮島の ごとくコード上に散らばっているだけで , 「こんなのが何で動くんだ ? 」とさえ思えて きます。 イベントとアクションを結び付けるから くりは , V1sual Basic や Borland Delphi , あ るいは Borland C + + Builder などの RAD ツー ルでは , 密かにどこかに埋め込まれ , さほ どおおっぴらにはコード上に姿を現しませ ん。 VisuaI C + + が提供するフレームワーク である MFC では , メッセージマップと呼 ばれるマクロ群がイベントとアクションと MFC に見るイベント駆動の仕組み public: MyDoc() : value-(0) { protected : int va ー ue— private: class MYDOC : public CDocument ( - Document-side * MFC's Document/View sample ー view-side UpdateAllViews(0); / / View を更新 ! 十十 value— void increment( ) { 〃 MyDoc 内のデータに変化を及ぼすメソッド 土 n セ getvalue( ) const { return value—; の対応表として機能しています。 ・イベント駆動の使われ方 アクションがイベントによって起動され る「イベント駆動」のからくりは , マウスや キー入力のようなハードウェアに起因する イベントだけでなく , ソフトウェアがイベ ントを起こしてアクションに火をつけると いう使われ方がされることもあります。例 としては , MFC の Document/View アーキ テクチャにこれを見ることができます。 アプリケーションが取り扱うデータをつ かさどる Document と , その表現 ( 見てくれ ) であるⅥ ew を用意して , その両者を関連付 けておきます。 Document は内包するデー タに変化が起こったときにイベントを発し , それによって関連付けられた View 側のア クションが起動されます。 MFC では CDocument のメソッド Update AllViews ( ) をコールすることがイベントの 発行であり , それをトリガとして CView の メソッド OnUpdate ( ) が呼び出されます。 OnUpdate ( ) 内の処理によって CDocument 内にあるデータの変化を見てくれに反映さ せるわけです (List 1 ) 。このからくりのお かげでデータの本体とその見てくれを分離 し , お互いの変更や拡張が他方に及ぼす影 響を小さく抑えることができます。 ・・要はコッチに何かが起こったことを オフジェクト工房 Talker クラスと Listener クラス アッチに伝えるということです。なんてこ とはないからくりなんだけど , いろんな応 用が考えられるし , さまざまな " ひねり " を 加えることができて , これがけっこうおも しろいんですよ。イベント駆動のきわめて 単純なからくりをこさえ , それに少しずつ " ひねり " を加えながら設計と実装の多様性 を楽しんでみようと思います。 class Talker; class Listener { public: virtual -Listener ( ) 0 virtual void talker) = の class TaIker { private: Listener* listener— public: Talker( ) void setListener(Listener* listener) { ー istener— = ー istener ー void changed( ) { i f ( listener— ) { listener—->update(this); Iistener-(0) { } stepl 単純な Talker/Listener では , まず元ネタとなる " 単純なからく class MyView : public CView { public: MyDOC* GetDocument ( / / たとえばマウスクリック時に MyDoc : : 土 nc て ement ( ) すると、 VOid OnLButtonDown(UINT, CPoint) { GetDocument ( ) —>increment ( / / MyDoc: :increment( ) 内での UpdateAIIViews( ) により . virtual VOid OnUpdate(CView* , LPARAM, CObject* ) { lnvalidate( / / 再描画 - ー OnD て aw ( ) が呼ばれる / / Doc の値 ( getv 引 ue ( ) ) を表示する virtual void OnDraw(CDC* pDC) { CString va 嵭 val . Format( d ” ,GetDocument( ) → getvalue( ) pDC->TextOut(0,0, val 嶹 ど 0 てククのオブジェクト工房 / 9
Object' (must be EnumerabIe) (RuntimeError) 0 特異メソッドとモジュール [ 注 3 ] 解答例は [RAA:Statistics] とがあります [ 両 7 ] from stat—err. てb:4 from stat-err. rb:4:in 、 include' 利用しています。とくに singleton. rb は短い 準ライプラリでは s ⅲ gleton. rb などがこれを ドもⅲ clude で追加することができます。標 ある append—features を使えばクラスメソッ もしれませんが , include のディスパッチで m ⅸ - in のそもそもの用法からは外れるか = 而 x - in でクラスメソッドを追加 ムをご覧ください。 ます。特異メソッドの詳細についてはコラ に , extend の実体は extend—object となってい include の実体が append_features であるよう と S ct : : Tms に sum を追加しています。なお , ッドを追加したい場合です。 l-ist 3 では :ray 異なるクラスの特定のオプジェクトにメソ する点が違います。 extend の使いどころは クラスではなくオプジェクトに機能を付加 返します。 extend は include に似ていますが ます。 extend は self, すなわちレシーバーを 複数のオプジェクトに追加することもでき Object#extend を使うことで特異メソッドを あまり使われているのを見かけませんが , にすればよいのです。 の総和を返すようにするには , List2 のよう にだけ sum というメソッドを追加して , 要素 メソッドと呼ばれます。たとえば , 配列 a Ⅳ 定義できます。このようなメソッドは特異 Ruby ではオプジェクトごとにメソッドを 宅 cat stat-err . rb require"math/ statistics. て b ″ class Object include Math: :Statistics end 宅 ruby stat-err. rb . /math/statistics. rb:7:in 、 append—features : unexpected destination List 1 の不適切な利用 19 : end ので , その定義を List4 に抜粋しました。説 明のために行番号を付けています。 singleton. rb はインスタンスがたかだか 1 つ しかないことを保証するクラスを作るため の m ⅸ - ⅲです。このモジュールがインクルー ドされたクラスはクラスメソッド new を使 うことができません。その代わりに常にユ ークなインスタンスを返すクラスメソッ ドⅲ s ね nce を使って , そのインスタンスを得 ることができます。その実現方法を逐一見 ていきましよう。 List4 の 2 行目からがその本体となる叩 pe nd-features の定義です。仮引数 klass には incl ude を行ったクラスが渡されます。 3 行目では new をプライベートなクラスメ ソッドにしています。ここで使われている private—class—method は Class のメソッドで す。 4 ~ 17 行目までが instance_eval です。その 引数は文字列として渡されています。 Obje ct#instance_eval は文字列引数もしくはプロ ックとして渡されたコードの self をレシーバ ーに変えて評価するものです。この instance eval のレシーバーは klass ですから , 5 ~ 16 行 目までの間での self は klass となります。なお , こで % { ・・・ } という形の文字列リテラルが 使われていますが , このリテラルを使うと E macs の ruby-mode でインデントが崩れない というメリットがあります。 5 行目で@ー instance という instance か らの利用を予感させるインスタンス変数に nil を代入しています。このインスタンス変 数を持っオプジェクトは , この文脈の self す 2 特異メソッド定義の例 (sum0. rb) ary = [ 10 广 2 , 31 广 8 ] def ・ y. ( zero = 0 ) 引ー = zero 田 ch ロ幻十 = 幻 end puts ary. sum # = > 31 Ⅵ extend を使って特異メソッドを定義 (suml . 「 b ) 3 module Sum def sun(zero = 0 ) Sum = ch ロ幻 sum 十 = i} array = [ 10 广 2 , 31 , -8 ] . ex ヒ end Sum times = Time. times. extend 加 puts a てて ay. puts t に s. 加 4 singleton. rb の抜粋 1 : module SingIeton def singleton. append-features(klass) klass. private—class—method( :new) klass. instance-eval @—instance— = nil def instance Thread. critical = true unless @—instance— @—instance— = new ensure Thread. critical = false return @—instance end 18 : 17 : 16 : 15 : 14 : 13 : 12 : 11 : 10 : 9 : 8 : 7 : 6 : 5 : 4 : 3 : 2 : できなくなります。スレッドセーフという それによってインスタンスの一意性が保証 ドでこの部分が実行される可能性があり , c ⅱⅱ翻を設定していなければ , 複数のスレッ nstance__ に代入するためで , もし Thread. スの唯一のインスタンスを生成して @__i り替わりを止めています。これは , このクラ で Thread. critical を true にしてスレッドの切 スメソッド instance の定義です。まず 7 行目 6 ~ 16 行目までは , このクラス klass のクラ ある klass となります。 なわちインクルードを呼び出したクラスで 94 C MAGAZINE 2001 4
五ロ なり次の行には続かない をコンパイルしましよう。コンパイルには プログラムの動作は次のようになります。 JDK に含まれる javac というコマンドを使い List 1 では , 1 行目の / * でコメントが始 最初にどれかのクラスのメソッドが押され ます。コマンドラインから , まって , 3 行目の * / で終わっています。ち る。押されたクラスは次に , 別のもしくは なみに 2 行目の * はなくてもいいのですが 自分のクラスのメソッドを押す。そのクラ javac Opinion. java と打ち込みます。もし正常にコンパイルで コメントとわかりやすいように入れていま スはさらに別のクラスのメソッドを押す。 きたなら何も表示せずに終了します。何も す。よく使われる手法です。 そうしていく過程で入出力とか数値計算と 表示しないけれど , ディレクトリの中を見 5 ~ 9 行目までが , Opinion というクラス かが行われ , 最終的に望む動作がなされる , の内容の記述です。 Java の場ム ' ' ということになります [ 注 3 ] 。 てみると , ロ , っー、う 記述を普通「宣言」といいます。 6 ~ 8 行目ま List 1 は , 1 つのクラスに 1 つのメソッドだ 0P土n土0n. Class というファイルができているはずです。そ でが main というメソッドの宣言です けという , 最小の Java プログラムです。ち Java プログラムは , クラスと ( それに含ま うすれば成功です。成功しなかったら失敗 ょっと大きいプログラムだと , 数十のクラ れる ) メソッドの宣言によって成り立って です。そういうときはコンパイラがエラー スにそれぞれ数個から数十個のメソッドが メッセージを出すので , それを見てソース います。 存在します。 6 行目の main というメソッドは , プログ を修正し , もう一度コンパイルしてくださ 先ほど , Java がオプジェクト指向プログ ラミング言語であると言ったことを思い出 ラムの中で最初に押されるスイッチの役目 無事に拡張子が . class のファイルーーー以後 してください。オプジェクト指向とは・・・ を果たします。 Java プログラムの中には最 クラスファイルと呼びます -- ーができたと と考え出すとめんどうになるのですが 低 1 個 main メソッドがあり , そこからすべ く単純な意味では「クラスを使ったプログ します。いよいよ実行です。実行には , や て始まります。その main の前後に付いてい る , ばぶりつく・すたていっく・ばいど・ はり JDK 中の , java コマンドを使います ( jav ラミング」ということになります。 a コマンドというのが JVM として働くプログ Fig. 3 に「オプジェクト指向プログラム」 すとりんぐ・あーぐすというのは , main メ ラムなのです ) 。 の概念図を示します。図の中のスイッチが ソッドに付きものの記述です。今は考えな 付いた機械のようなものがクラスで , プロ いでください。といっても気になるでしよ java Opinion うから , 一応意味を書いておきます。 というように起動します。このときはコン グラムはいくつかのクラスからなります。 パイルのときと違い , 拡張子「 . class 」を付 クラスの表面にあるスイッチはメソッドを メソッドが公開であること ・ public けてはいけません。拡張子を付けて起動す 表します。 を意味する ると , かえってエラーになります。 メソッドがインスタンスへ 「空地に屋根ができたね」「やーねー」 の参照を持たないことを意 味する ちゃんと表示することができました。 メソッドが返却値を持たな List 1 の解説 いことを意味する ・ String ロ 文字列型の配列という型を 表す String [ ] 型の変数 ・ args main メソッドを書くときには , これらと セットにします。 7 行目の「 system. out. println ( … ) 」という のは , Java で一般的にコンソールに文字列 を表示するやり方です。ちなみにこれは表 示し終えたら改行します。改行したくない ときは System. out. print ( … ) を使います。 System というのもクラスです。 Java のラ イプラリの中にあるクラスは , import とい う手続きをしたあと , 自由に使うことがで ・ stati c Fig. 3 オブジェクト指向プログラムの概貪 図 ( プログラム ) ・ VOid ここで簡単にソースコードの内容を解説 しましよう。もう一度 List 1 を見てくださ 1 ~ 3 行目はコメントです。これはプログ ラムの実行には関係ありません。コンパイ ラには無視されるのですが , 人間があとで 見てわかりやすいように説明を書いておく ものです。 Java のコメントには 2 種類あります。 ・ / * で始まって * / で終わるもの。複数 行のコメントが書ける ・ / / で始まるもの。行末までコメントに 特集プログラミング入門 Java 言語入門講座イ 9