設 計
どんな「ちょっとしたプログラム」でも、何をしたいかが明確で無ければ始められない。プログラミングとは自分の「思想」や「意志」を記号化する作業だからである。
コーディングの前に
以前、C言語を使った時にも、必要なメモリーは配列に定義して最初から確保するような単純なソフトしか書いたことがないので、プログラムが起動された後で「インスタンス化」によってオブジェクトがメモリーに展開されると言う概念が、どうもピンと来ない。「Interface Builder」でコロッとインスタンス化して使う部分は、最初からインスタンス化されているので気にしなくて良い訳だが、Model - View - Controllerの設計でのModel部分をどうするかが関心事となる。「Interface Builder」でコロッとインスタンス化して、線で繋いで使うこともできるが、どうもそれでは納得が行かない。Model部のファイルは、「Interface Builder」とは全く無縁に、Controller部の .mファイルに何か書き込むだけでインスタンス化されて呼び出されるべきと考えてしまう。なんで?と問われても戸惑うが、ソフトウエアの部品化、再利用性の見地からすると、おおもとのObjectiveCの作者がそう作った筈だと確信している。インスタンス化しないのもアリ
極端な例だが、この例ではあえてインスタンス化せずに、Model部はクラスのままで実行している。こんなことをしても(後で示すように)ちゃんとインスタンス化した場合と基本形式は変わらないので、後からやっぱりインスタンス化しようと考えを変えたとしても、十分に再利用性がある。非常に短いソフトだが、いろいろと試行錯誤の末たどり着いたもので、参考になると思う。もちろん、このままでは全く拡張性はないし、決してこう言うプログラミング法が良いのだと主張するつもりはないが、「ちょっとしたプログラム」の場合には、ObjectiveCの見やすさを保ったままで短く書けるメリットがあるとも言える。「Model部」のコーディング
「Interface Builder」は、インスタンス化や配線をしない場合でも、コードのひな形を作成するためのツールとして便利に利用させてもらう。NSObjectのサブクラスにMyModelObjectを作成し、形式的にアクションを3つ加えてコード化する。アクションの名前は、使用予定のメソッド名にしておくと更に後での書き直し量が減る。
/* MyModelObject */
#import <Cocoa/Cocoa.h>
@interface MyModelObject : NSObject
{
}
+ (void)initWith:(double)sender;
+ (void)addData:(double)sender;
+ (double)lookData;
@end
|
#import "MyModelObject.h"
@implementation MyModelObject
double sumData;
+ (void)initWith:(double)sender
{
sumData = sender; // 初期値のセット
}
+ (void)addData:(double)sender
{
sumData += sender;
}
+ (double)lookData
{
return sumData;
}
@end
|
「View部」の作成
「Interface Builder」でさささっと作るだけある。実際に「ちょっとしたプログラム」の場合には、ウインドウ上にコメントを書く必要など無いが、忘れた頃に将来書き直す可能性があるならば、配線の状況に付いてコメントしておくのも悪くないかも知れない。「Controller部」のコーディング
「Interface Builder」で、NSObjectのサブクラスにMyControlObjectを作成し、アウトレットを3つ、アクションを1つ加え、コロッとインスタンス化して、コード化もする。ボタンからはアクションへ、3つのアウトレットからは四角やボタンへ配線を張る。このようにボタンとかに配線を張る必要があるけれどウインドウに張り付いていない自作プログラムは、ここでコロッとインスタンス化することが必須となる。
/* MyControlObject */
#import <Cocoa/Cocoa.h>
@interface MyControlObject : NSObject
{
IBOutlet id myOutlet1;
IBOutlet id myOutlet2;
IBOutlet id myOutlet3;
}
- (IBAction)myAction:(id)sender;
@end
|
#import "MyControlObject.h"
#import "MyModelObject.h"
@implementation MyControlObject
BOOL model=NO;
- (IBAction)myAction:(id)sender
{
if( model==NO ){
[ MyModelObject initWith: [ myOutlet1 doubleValue ] ]; // modelの初期化
[ myOutlet2 setTitle: @"ADD" ]; // ボタンのタイトルをADDに変更
model = YES;
}else
[ MyModelObject addData: [myOutlet1 doubleValue] ]; // modelへのデータの追加
[ myOutlet3 setDoubleValue: [ MyModelObject lookData ] ]; // modelからのデータ読み出し
}
@end
|
サンプルソフトのダウンロード
あえてダウンロードするまでも無いソフトですが、UPしておきます。 たったの12KB! ソースのみですので「Project Builder」でコンパイルして下さい。ちゃんとインスタンス化してみる(その1)
いくらなんでも、クラスメソッドでの実装は邪道ですか。失礼しました。しかし、このページを書いた当初の何も良く解っていない私にとっては、インスタンスじゃなくてもちゃんと動くのが解って、とてもほっとした記憶がある。「Model部」のコーディング
下に示すのが、ちゃんとインスタンス化した場合の MyModelObject.h ファイル。
#import <Cocoa/Cocoa.h>
@interface MyModelObject : NSObject
{
double sumData;
}
+ (id)modelWith:(double)sender;
- (void)addData:(double)sender;
- (double)lookData;
@end
|
#import "MyModelObject.h"
@implementation MyModelObject
- (id)initWith:(double)sender
{
sumData = sender; // 初期値のセット
return self;
}
+ (id)modelWith:(double)sender
{
id x = [ [ MyModelObject alloc ] initWith: sender ];
return x;
}
- (void)addData:(double)sender
{
sumData += sender;
}
- (double)lookData
{
return sumData;
}
@end
|
「View部」の作成
「Moedl部」をインスタンス化したかどうかには影響されないので、変更無し。「Controller部」のコーディング
MyControlObject.hファイル。model オブジェクト(前の例ではフラグだったが)の宣言が引っ越してきたのにつれて、MyModelObject.h のインポート宣言もこちらに引っ越した。
/* MyControlObject */
#import <Cocoa/Cocoa.h>
#import "MyModelObject.h"
@interface MyControlObject : NSObject
{
IBOutlet id myOutlet1;
IBOutlet id myOutlet2;
IBOutlet id myOutlet3;
MyModelObject *model;
}
- (IBAction)myAction:(id)sender;
@end
|
#import "MyControlObject.h"
@implementation MyControlObject
- (IBAction)myAction:(id)sender
{
if( model==nil ){
model = [ MyModelObject modelWith: [ myOutlet1 doubleValue ] ]; // modelのインスタンス化
[ myOutlet2 setTitle: @"ADD" ]; // ボタンのタイトルをADDに変更
}else
[ model addData: [myOutlet1 doubleValue] ]; // modelへのデータの追加
[ myOutlet3 setDoubleValue: [ model lookData ] ]; // modelからのデータ読み出し
}
@end
|
もう一つ細かいことだが、なぜ人に見せる必要のないインスタンス変数の宣言までも、.h 側に書かせるのか? Objective C の考案者の意図がつかめず、この件がずっと頭に引っ掛かっている。ずっと後に気付いたことだが、サブクラスが作られたときにインスタンス変数が継承されることを念頭に入れると、.h 側に書く意義があるのだと言うのも、一つの理由のようだ。しかしそれなら、サブクラスにさえも見せる気のないインスタンス変数であれば、.m 側だけに定義しても良いことになる。この話の続きは難しくなるのでまた別の機会にしましょう。
サンプルソフトのダウンロード
Model部をちゃんとインスタンス化するバージョンも、UPしておきます。 これもたったの12KB! 参考にして下さい。ちゃんとインスタンス化してみる(その2)
上の例ではカッコをつけて、わざわざインスタンス化を実行するクラスメソッドを実装してみたが、だんだん慣れてくると、敢えてそんなことをしなくても良いことが解ってくる。インスタンス化されるクラスの側にインスタンス化を実行するメソッドを置くのと、使用する側がインスタンス化するのとどう違うのか? デフォルトのクラス達にも両方あることは知っての通りである。これは、単なる使い勝手の違いではなくて、インスタンスが破棄されるタイミングについて理解してからでないと、説明できない。詳しいことは「お行儀良く味見」に書くとして、実際問題この例題ではどちらでもちゃんと動作する。「Model部」のコーディング
MyModelObject.h ファイル。
/* MyModelObject */
#import <Cocoa/Cocoa.h>
@interface MyModelObject : NSObject
{
double sumData;
}
- (id)initWith:(double)sender;
- (void)addData:(double)sender;
- (double)lookData;
@end
|
#import "MyModelObject.h"
@implementation MyModelObject
- (id)initWith:(double)sender
{
sumData = sender; // 初期値のセット
return self;
}
- (void)addData:(double)sender
{
sumData += sender;
}
- (double)lookData
{
return sumData;
}
@end
|
「Controller部」のコーディング
MyControlObject.hファイル。(その1)と同じ。
/* MyControlObject */
#import <Cocoa/Cocoa.h>
#import "MyModelObject.h"
@interface MyControlObject : NSObject
{
IBOutlet id myOutlet1;
IBOutlet id myOutlet2;
IBOutlet id myOutlet3;
MyModelObject *model;
}
- (IBAction)myAction:(id)sender;
@end
|
#import "MyControlObject.h"
@implementation MyControlObject
- (IBAction)myAction:(id)sender
{
if( model==nil ){
model = [ [ MyModelObject alloc ] initWith: [ myOutlet1 doubleValue ] ]; // modelの初期化
[ myOutlet2 setTitle: @"ADD" ]; // ボタンのタイトルをADDに変更
}else
[ model addData: [myOutlet1 doubleValue] ]; // modelへのデータの追加
[ myOutlet3 setDoubleValue: [ model lookData ] ]; // modelからのデータ読み出し
}
@end
|
サンプルソフトのダウンロード
これも、UPしておきます。 これもたったの12KB! 参考にして下さい。インスタンスの動作確認
さて、上でModel部もちゃんとインスタンス化したからには、この機能が複数のインスタンスとなっても、それぞれがちゃんと独立して別々に動作するものかどうか、ぜひとも確かめて見たくなる。
サンプルソフトのダウンロード
自分で書き直して実感しないとつまらないとは思いますが、完成品をUPしておきます。 やっぱり12KB! ソースのみですので「Project Builder」でコンパイルして下さい。ファイル分割の必要性
さて話は変わって「Interface Builder」にコード化をお願いすると、各クラス毎に.hファイルと.mファイルを作ってくれる。このまま利用して何の不都合もないのだが、はたして.hファイルは必須なのか?と言う素朴な疑問が、古風なC言語ユーザーとしては気になってしかたがない。このあとの話はこう言うプログラミング法が良いのだと言うつもりで紹介するのでは無く、こう言う実験的ソフトを通して、Cocoaの理解を深めているだけであることを理解して読んで下さい。MyModelObject.m ファイル。
#import <Cocoa/Cocoa.h>
@interface MyModelObject : NSObject
{
double sumData;
}
- (id)initWith:(double)sender;
- (void)addData:(double)sender;
- (double)lookData;
@end
@implementation MyModelObject
- (id)initWith:(double)sender
{
sumData = sender; // 初期値のセット
return self;
}
- (void)addData:(double)sender
{
sumData += sender;
}
- (double)lookData
{
return sumData;
}
@end
|
#import <Cocoa/Cocoa.h>
@interface MyModelObject : NSObject
{
double sumData;
}
- (id)initWith:(double)sender;
- (void)addData:(double)sender;
- (double)lookData;
@end
@interface MyControlObject : NSObject
{
IBOutlet id myOutlet1;
IBOutlet id myOutlet2;
IBOutlet id myOutlet3;
MyModelObject *model;
}
- (IBAction)myAction:(id)sender;
@end
@implementation MyControlObject
- (IBAction)myAction:(id)sender
{
if( model==nil ){
model = [ [ MyModelObject alloc ] initWith: [ myOutlet1 doubleValue ] ]; // modelの初期化
[ myOutlet2 setTitle: @"ADD" ]; // ボタンのタイトルをADDに変更
}else
[ model addData: [myOutlet1 doubleValue] ]; // modelへのデータの追加
[ myOutlet3 setDoubleValue: [ model lookData ] ]; // modelからのデータ読み出し
}
@end
|
それでは、ついでなので、徹底的にやってみよう。現在.mファイルは3個あるわけだが、これが1個ならどうだろうか。全部を main.m ファイルの中に書き込んで、ファイルを一つにして見た。
main.m ファイル。
#import <Cocoa/Cocoa.h>
@interface MyModelObject : NSObject
{
double sumData;
}
- (id)initWith:(double)sender;
- (void)addData:(double)sender;
- (double)lookData;
@end
@implementation MyModelObject
- (id)initWith:(double)sender
{
sumData = sender; // 初期値のセット
return self;
}
- (void)addData:(double)sender
{
sumData += sender;
}
- (double)lookData
{
return sumData;
}
@end
@interface MyControlObject : NSObject
{
IBOutlet id myOutlet1;
IBOutlet id myOutlet2;
IBOutlet id myOutlet3;
MyModelObject *model;
}
- (IBAction)myAction:(id)sender;
@end
@implementation MyControlObject
- (IBAction)myAction:(id)sender
{
if( model==nil ){
model = [ [ MyModelObject alloc ] initWith: [ myOutlet1 doubleValue ] ]; // modelの初期化
[ myOutlet2 setTitle: @"ADD" ]; // ボタンのタイトルをADDに変更
}else
[ model addData: [myOutlet1 doubleValue] ]; // modelへのデータの追加
[ myOutlet3 setDoubleValue: [ model lookData ] ]; // modelからのデータ読み出し
}
@end
int main(int argc, const char *argv[])
{
return NSApplicationMain(argc, argv);
}
|
2002.5.11
最初の記事アップは、2002.1.20 でしたが、 後で読み返してみると気になる文章が多かったので、2002.4.19 に推敲を加え、今回 2002.5.11 に大改修を加えました。参考までに最初のつたないページもここに残しておきます。
次の話題、3. ちょっとしたカレンダーが欲しいへ