もくじ へ  このページの記事は、前のページの「お行儀良く味見」が理解されていることを前提として書かれています。

2.2 続Cocoa の味見 - インスタンス変数の隠蔽工作 -

 インスタンス変数は .h ファイルの @interface 〜 @end の中の { } の中に宣言すると言うのがルールである。と言うことは、他人に見せる必要の無い全くローカルなインスタンス変数も、.h ファイルとともに公開することが義務付けられているかに見える。
 ちょっとしたプログラムの場合には、べつにインスタンス変数の隠蔽 にこだわる必要性はないように思われるかも知れない。しかし、ちょっと考え過ぎと言われるだろうが、私のように超怠慢な性格の持ち主だと、ソフトの開発時に .h ファイルと .m ファイルを同時にいじるのがうっとうしいと言うのも一つの正当な理由だと思う。
 他のオブジェクトとやり取りする "メッセージ" に関する仕様は、最初の設計時点でビシッと決めて、後でふらふらと変えない方が良いのは、どんなプログラムでも当然のことである。しかし、仕様が決まったモデルに関して、これをどのようなアルゴリズムで実装するかは、バージョンごとに大きな変更があっても不思議では無い。アルゴリズムが変われば、使用される変数も当然変わってくる。Objective-C の言語体系として、アルゴリズムは他人に見せる必要が無いのに、そのアルゴリズムで使用される変数だけは他人に見せなさいと言うのは、どうも腑に落ちないことだし、実利的にも .h ファイルと .m ファイルを同時にいじる面倒臭さと言う欠点がある。
 さて、どうすれば良いか。オブジェクトに機能を追加する方法としては、カテゴリーとかプロトコルとか言うものがあるようだが、どちらも「インスタンス変数を追加できない」と明確に規定されているので、この場合は役に立たない。「インスタンス変数を追加したければサブクラス」しか無いわけで、基本の基本であるサブクラスで、これを実装することになる。
 解ってしまえば何てことはなくて、.m ファイルの中に自分のサブクラスの @interface 〜 @end 及び @implementation 〜 @end を全部書いてしまえば、このサブクラスは他人からは知られない秘密のサブクラスとなる。このサブクラスを使って、自分のメソッドをオーバーライド(上書き)すれば、インスタンス変数も含めて他人に知られずに、好きなようなアルゴリズムが書けるわけである。そして仕上げは、自分が生成(インスタンス化)された時に、さりげなくサブクラスをインスタンス化して呼び出し側に戻してやるようにすることで、呼び出し側は生成されたものがサブクラスとはツユ知らず、これを使ってしまうと言う寸法である。

「Model部」のコーディング

 MyModelObject.h ファイルでは、もはやメソッドしか定義されず、インスタンス変数は隠蔽される。


/* MyModelObject */

#import 

@interface MyModelObject : NSObject
- (id)initWith:(double)sender;
+ (id)modelWith:(double)sender;
- (void)addData:(double)sender;
- (double)lookData;
@end
     

 影武者? MyModelObjectLocal を秘密裏に従えた MyModelObject.m ファイルである。


#import "MyModelObject.h"

@interface MyModelObjectLocal : MyModelObject
{
    double sumData;
}
@end

@implementation MyModelObjectLocal

- (id)initWith:(double)sender
{
    sumData = sender;		// 初期値のセット
    return self;
}

- (void)addData:(double)sender
{
    sumData += sender;
}

- (double)lookData
{
    return sumData;
}

@end

@implementation MyModelObject

- (id)initWith:(double)sender
{
    [ self autorelease ];
    return [ [ MyModelObjectLocal alloc ] initWith: sender ];
}

+ (id)modelWith:(double)sender
{
    return [ [ [ MyModelObjectLocal alloc ] initWith: sender ] autorelease ];
}

- (void)addData:(double)sender { }
- (double)lookData { return 0.0; }

@end
    

 こんな使われ方をされることを想定するならば、一般にCocoa の例題に書かれているように、


    anObj = [ [ ClassName alloc ] init ];
      

 と書くならば、何の問題も無いが、


    anObj = [ ClassName alloc ];
    [ anObj init ];
      

 と書くと正しく動作しない場合があることになる。偽装工作のために init が、受け取ったのと違うオブジェクトを返してくるかもしれないからである。

「Model部」のコーディング(サブクラス対応)

 "自分" と騙って、呼び出し側に実はローカルなサブクラスを使わせる偽装工作は、もちろん個人で作るちょっとしたプログラムでは何の問題も出ないが、他人が知らずにサブクラスを作るような場合には問題が出る。もしも自分に新しいメソッドが追加された場合には、それはローカルなサブクラスに追加されたメソッドでは無いので、それがオーバーライド(上書き)を期待したものでも無視されてしまうし、新しい名前のメソッドなら存在しないメソッドとして実行時エラーになってしまう。
 つまり、他人にサブクラスを作られることに対応するためには、自分のインスタンスがちゃんと機能していないといけないことになる。

 あくまでも外部に対しては、影武者?なローカルサブクラスを持っていることをオクビにも見せずに、シラを切り通したいならば、自分で生成したローカルサブクラスのインスタンスへのポインタを記憶しておくために、少々工夫が必要となる。また、破棄の責任も果たさなければならない。
 以下がこのようなことを考慮した MyModelObject.m ファイルである。


#import "MyModelObject.h"

@interface MyModelObjectLocal : MyModelObject
{
    double sumData;
}
@end

@implementation MyModelObjectLocal

- (id)initWith:(double)sender
{
    sumData = sender;		// 初期値のセット
    return self;
}

- (void)addData:(double)sender
{
    sumData += sender;
}

- (double)lookData
{
    return sumData;
}

@end

@implementation MyModelObject

- (MyModelObjectLocal *)memo: (MyModelObjectLocal *)sender
{
    static MyModelObjectLocal *x = nil;
    if( x == nil )
        x = sender;
    return x;
}

- (id)initWith:(double)sender
{
    [ self memo: [ [ MyModelObjectLocal alloc ] initWith: sender ] ];
    return self;
}

+ (id)modelWith:(double)sender
{
    return [ [ [ self alloc ] initWith: sender ] autorelease ];
}

- (void)addData:(double)sender
{
    [ [ self memo: nil ] addData: sender ];
}

- (double)lookData
{
    return [ [ self memo: nil ] lookData ];
}

- (void)dealloc
{
    [ [ self memo: nil ] release ];
    [ super dealloc ];
}

@end
    

 自分で徹底的にやって見ながら、こんなにまでしてヒタ隠しに隠してどうする、と言う気がしてきた。もちろん、1回こうやって形を決めてしまえば、モデルのアルゴリズムに関係するのは MyModelObjectLocal の中だけだから、書き直すのに広い範囲を気にする必要は無いので、実用性が無いわけでは無い。
 でも何で Objective-C では、普通に書く場合に、インスタンス変数を .h ファイルに書いて、公開しなければいけないルールになっているのだろう? こうやって抜け道がある以上、奇妙なルールに感じられる。このことが気になっているのは、私だけではないと思うのだが。

2002.6.6