オブジェクト指向へ

 暗号は解読法が発見されれば、新しい暗号法の開発という、いたちごっこです。
 アプリケーションもこの進化に対応するために、前章のCipherText構造体や各関数を、随時修正していかなければいけません。
 しかし、修正は必要最小限にとどめたいものです。
 特に使う側は、従来の暗号法を使う限りなんの変更も必要なく、新しい暗号法を導入する場合もほんの少しの変更で済むようにするのが理想です。
 そのためには、どのような手法があるのか、いろいろなアイデアが生み出されました。

構造体の調整
 まっさきに思いつくのは、CipherText構造体への、どの暗号法を使って暗号化したかを示す要素の追加です。ここでは仮にtypeというint型の要素とします。
typedef struct {
int type; ←使う暗号法によって値を変える。
int shift;
char* text;
 新しい暗号法のための追加情報
} CipherText;


テン*シー*シー-1

 cipherやdecipher関数で、type要素の値に従って暗号処理を変えるわけです。
int cipher(const char * plain_text, CipherText * cipher)
{
if (cipher->type == 1) {
従来の暗号化処理
・・・
}
if (cipher->type == 2) {
新しい暗号化処理
・・・
}

 暗号法によっては、ずらし量などを使わない方法もあるでしょう。cipher関数の引数からは、暗号法に依存するような引数(今回ならずらし量)などは取りはぶき、初期化関数のinitCipherTextで設定するようにします。
void initCipherText(CipherText* cipher, int shift); ← 引数shiftを追加。
 この関数でCipherText構造体のshift要素を設定する。

int cipher(const char * plain_text, CipherText * cipher);← 引数shiftは削除。
 CipherText構造体のshift要素をそのまま利用する。


 この手法なら、新しい暗号方法のCipherText初期化関数を用意してやるだけで、使う側は利用できる事になります。

 例えば、受け取った文字列の奇数番目の文字には別のずれ量を適用する暗号法を用意した場合、以下のようなCipherText構造体の変更と、初期化関数の追加となり
typedef struct {
int type;
int shift
char* text;
int altShift; ←奇数番目用のずらし量
} CipherText;

void initCipherText(CipherText* cipher, int shift);
void initCipherDoubleText(CipherText* cipher, int shift, int altShift); ←新しい暗号法用初期化関数
・・・

 これでmain.c側は初期化部だけの変更で新しい暗号法が利用できます。 
int main (int argc, const char * argv[])
{
CipherText a;

initCipherDoubleText(&a, 3, 5); ← 新しい暗号法で初期化
if (cipher("HELLO WORLD", &a) == 1) { ← ずらし量は引数から無くす
char* plain = decipher(&a);
if (plain != NULL) {
printf("%s = %s\n", plain, a.text);
free(plain);
}
}
deleteCipherText(&a);
return 0;
}

 使う側はこれで十分でしょう。
 しかしCipherText構造体やcipherやdecipher関数は新しい暗号法が組み込まれるたびに修正が必要です。
 修正には不具合混入の可能性がつきまといます。どうにかして、既存の関数の修正はせずに機能を追加する方法はないでしょうか?
 この解決策として関数ポインタの利用が考えられます。

関数ポインタ型
 関数ポインタ型は、関数自体を変数として持つための型です。
 CPUの視点から見れば、関数(サブルーチン)も番地でしかなく、そこに用意する引数や戻り値さえ把握できれば立派に変数として持てるはずです。
 このための記述法が
int (*func)(const char * plain_text, CipherText * cipher); ←引数名は省略してもよい。

 といったものです。
 この場合、funcという名前の関数ポインタ型変数を宣言した事になります。
 この変数は、戻り値にint型、引数にconst char*型、CipherText*型を持つ関数の番地を記憶する事になり、この条件(戻り値、引数)を備える関数ならどれでも代入が可能です。

 例えば先の暗号化用の関数
int cipher(const char * plain_text, CipherText * cipher) { …  }

 を代入するなら
func = cipher;

 と記述します。
 利用するさいはfuncを関数名のように記述すれば利用できます。
int main (int argc, const char * argv[])
{
CipherText a;
int (*func)(const char * plain_text, CipherText * cipher);
func = cipher;


initCipherText(&a, 3);
if (func("HELLO WORLD", &a) == 1) {
・・・
}
deleteCipherText(&a);
return 0;
}


 この関数ポインタ型をcipher用、decipher用に用意して、CipherText構造体に組み込みます。
typedef struct CipherText {
int type;
int shift;
char* text;
int altShift;
int (*cipher)(const char * plain_text, struct CipherText * cipher);
char* (*decipher)(const struct CipherText * cipher);

} CipherText;



テン*シー*シー-2
 と記述すると問題があります。
 構造体CipherTextの名前が決まる前に引数の型として指定する必要があるからです。
 これを解決するのがtypedef structの次に名前を付ける方法です。

typedef struct CipherText

 このようにすると
struct CipherText
CipherText

 が同じ扱いになります。
テン*シー*シー-3

 そして、initCipherText関数でこれらの関数ポインタ要素に、従来のcipher、decipher関数を設定、新しい暗号法用のinitCipherDoubleText関数では新しい暗号法用の暗号化/暗号解凍関数(仮にcipherDouble、decipherDoubleという名前にする)を設定してやればどうでしょうか?
void initCipherText(CipherText* inCipher, int shift)
{
inCipher->shift = shift;
inCipher->text = NULL;
inCipher->cipher = cipher;
inCipher->decipher = decipher;

}
void initCipherDoubleText(CipherText* inCipher, int shift, int altShift)
{
inCipher->shift = shift;
inCipher->altShift = shift;
inCipher->text = NULL;
inCipher->cipher = cipherDouble;
inCipher->decipher = decipherDouble;

}

 main関数で以下のように記述すれば、関数を呼び出すこと自体が暗号法の選択になり、cipher、decipher関数でtype要素の値をみて暗号法を変える必要はなくなります。
int main (int argc, const char * argv[])
{
CipherText a;

initCipherText(&a, 3); ←initCipherDoubleText(&a, 3, 5)とすれば、新しい暗号法にもなる。
if (a.cipher("HELLO WORLD", &a) == 1) {
char* plain = a.decipher(&a);
if (plain != NULL) {
printf("%s = %s\n", plain, a.text);
free(plain);
}
}
deleteCipherText(&a);
return 0;
}

サンプルプロジェクト:study-22-cipher-obj.zip

オブジェクト指向
 cipher、decipher関数が、CipherText構造体のtype要素を見て動作を決定するのではなく、CipherText構造体の変数a自体がどのように動作すべきかを選択しているわけです。
 動作を、関数ではなく変数(オブジェクト:物、物体)に制御させる、このようなプログラム手法をオブジェクト指向と呼びます。
 ここで例示したのは、オブジェクト指向のほんの一部、多態性(ポリモーフィズム)のC言語による実践です。

 このやり方なら、この後、新しい暗号化法が追加される時も、古い暗号化、暗号解凍関数関数の変更は必要ありません。新しい暗号化、暗号解凍関数の追加だけで終わります。

 このように、オブジェクト指向はC言語でも実践可能です。
 ただし、スマートに記述できるとは言えません。これらをより洗練し容易に記述できるように考案されたものがC++言語、Objective C言語だと言えます。

 そろそろ前に進みましょう。
 C言語について知らない事はまだまだありますが、始めるにはこのくらいの知識で十分です。
 あとは、いろいろなアプリケーションを作りながら、他の人のプログラムを読みながら、知識を増やしていってください。インターネット上には、有志の方達のC言語に関する情報がいくらでも転がっています。本を読むのでもいいでしょう。


 というわけで、ブートキャンプはおしまいです。
 「iPhoneアプリ開発のコツとツボ35」の付録PDFの方は、この後、追補資料として「Objective-Cの基礎知識」を続けているので、そのままObjective-Cの基礎知識を身につけて本編に進んでください。

 本買う金は無ぇ~な、な人は、以下のAppleのドキュメントがおすすめ。

Objective-C プログラミング言語 ←日本語ですよん。ただし蝶専門的。

 でもって、ちとXcodeのバージョンが古いけど、このブログの「iPhoneアプリ開発ドリル」なんかをやっていくといいでしょう。本の方は、もうちょい詳しくターゲットアクションやデリゲート、ファイルの読み書きなんかも扱います。
 逆に本を読んだ人も、「テーブル項目の並べ替えをサポートする」や「Aqua風ボタンを作る」はおまけネタとして楽しめるはず。

iPhoneアプリ開発ドリル ↓
目次(環境)
目次(UIViewController)
目次(UIView)
目次(定期処理)