いつもの Integerクラスを継承して値を変更する機能を持つ IntegerVariableクラスをつくってみる。
ヘッダファイル (header file)
IntegerVariableクラスのヘッダを次に示す。/* 11.Aug.2012 kei moses kissinger */
#ifndef _Included_IntegerVariable
#define _Included_IntegerVariable
typedef struct IntegerVariable* IntegerVariable;
typedef struct interface_IntegerVariable {
/* Integer class */
void (*finalize)(IntegerVariable this);
int (*getValue)(IntegerVariable this);
/* IntegerVariable class */
void (*setValue)(IntegerVariable this, int value);
}* interface_IntegerVariable;
extern void static_IntegerVariable(interface_IntegerVariable interface_methods);
extern IntegerVariable new_IntegerVariable(int value);
extern void IntegerVariable_init(IntegerVariable this, int value);
extern void IntegerVariable_finalize(IntegerVariable this);
extern int IntegerVariable_getValue(IntegerVariable this);
extern void Integer_Variable_setValue(IntegerVariable this, int value);
#endif /* _Included_IntegerVariable */
基本的な構造は Integerクラスのものと同じである。
interface_IntegerVariableのメンバに setValue関数のポインタが追加されている。
注意が必要なのは、IntegerVariableで追加する関数よりも前の部分はスーパクラスのものと内容(関数ポインタの数とその順序)が同じでなければならない。そうでなければ呼び出したときに意図したものと異なる振る舞いをする。
ただし、スーパクラスで定義された関数であっても、thisポインタのクラス名だけは自クラスに置き換えている。これは呼び出し時にキャストを不要にするためである。
Cのキャストは何でも出来るため危険であり、キャストした内容を保証するのはプログラマの責任である。 softies projectでは呼び出しごとにキャストするのではなく、型変換を関数テーブルの初期化時に限定することで間違いを起こしにくくする戦略を採っている。 先ほどの関数テーブルのスーパクラス部分を同じにする注意点はキャストを安全に行うために必要である。
今までに無かった init関数はインスタンスを初期化する部分である。詳細はソースファイルのところで述べる。
IntegerVariableクラスで追加された setValue関数は勿論、継承する finalize, getValue関数も宣言し、各々 thisポインタは IntegerVariableクラスとしている。これは無くとも良いが、そうすると呼び出し側で毎回
this->interface_methods->getValue((Integer)this);のように記述しなければならなくなる。
ソースファイル (source file)
IntegerVariableクラスのソースを次に示す。/* 11.Aug.2012 kei */
#include <IntegerVariable.h>
#include <Integer.h>
/****************************************************************
private
****************************************************************/
#include <malloc.h>
struct IntegerVariable {
interface_IntegerVariable interface_methods;
/* Integer class */
int value; /* A bogus example. private value replaced with protected. */
/* IntegerVariable class */
};
static struct interface_IntegerVariable interface_methods;
static void setValue(IntegerVariable this, int value) {
this->value = value;
}
/****************************************************************
public
****************************************************************/
void static_IntegerVariable(interface_IntegerVariable interface_methods) {
/* ToDo: lock interface_methods */
if (interface_methods->finalize == NULL) {
static_Integer((interface_Integer)interface_methods);
interface_methods->setValue = setValue;
}
}
IntegerVariable new_IntegerVariable(int value) {
static_IntegerVariable(&interface_methods);
IntegerVariable this =
(IntegerVariable)malloc(sizeof(struct IntegerVariable));
if (this != NULL)
IntegerVariable_init(this, value);
return this;
}
void IntegerVariable_init(IntegerVariable this, int value) {
Integer_init((Integer)this, value);
this->interface_methods = &interface_methods;
}
void IntegerVariable_finalize(IntegerVariable this) {
this->interface_methods->finalize(this);
}
int IntegerVariable_getValue(IntegerVariable this) {
return this->interface_methods->getValue(this);
}
void IntegerVariable_setValue(IntegerVariable this, int value) {
this->interface_methods->setValue(this, value);
}
冒頭では自身のヘッダ IntegerVariable.hとスーパクラスの Integer.hをインクルードしている。
IngerVariable.hの中ではスーパクラスが Integerクラスであることが明示されていないためである。(コメントに記述されているのみ。)
struct IntegerVariableでは、関数テーブルと同様に、プログラマの責任でスーパクラスの構造を引用しなければならない。 int valueをサブクラスでも記述しているてんがそれである。スーパクラスに属性を追加したとき、サブクラスも修正が必要であることを意味する。 ただしこの点の改善方法は別の機会に紹介する。
setValue関数が実装されているが、finalizeと getValueは Integerクラスを継承しているためこのクラスでは実装していない。
static_IntegerVariable関数は、以前示した static_Integerと同じように関数のポインタを関数テーブルに設定しているが、スーパクラスの関数を利用するために static_Integerを冒頭で呼び出している。
コンストラクタは new_IntegerVariableと IntegerVariable_initの2つの部分から成っている。new_IntegerVariableではインスタンスのためのメモリ割り当てを行うだけで初期化は IntegerVariable_initのほうで行っている。その訳はIntegerクラスの init関数のところで説明する。
あとから気づいたのだが、setValue関数にはインチキが含まれている。クラス図では Integerの valueは privateとしてあるが、これをサブクラスからアクセスしてるのだから protectedでなければならない。
Integerクラスの init関数 (init function of Integer class)
init関数を追加するために Integer.h, Integer.cを修正する。 (part of Integer.h, Integer.c)extern Integer new_Integer(int value);
extern void Integer_init(Integer this, int value);
extern void Integer_finalize(Integer this);
Integer new_Integer(int value) {
static_Integer(&interface_methods);
Integer this = (Integer)malloc(sizeof(struct Integer));
if (this != NULL)
Integer_init(this, value);
return this;
}
void Integer_init(Integer this, int value) {
this->interface_methods = &interface_methods;
this->value = value;
}
コンストラクタを newと initに分けた理由は、initをサブクラスの初期化からも呼び出したいからである。
newでメモリを確保して initを呼び出している。サブクラスではサブクラス自身の newでメモリ割り当てを行うため、スーパクラスの初期化だけを行う機能を呼び出したい。newと initの機能が一体になっていたのでは不要なメモリ割り当てが発生してしまうのである。
使用例 (example of use)
IntegerVariableクラスのテストケース IntegerVariableTest.cをつぎに示す。/* 11.Aug.2012 kei */
#define import_lwd_unit_Assert
#define import_lwd_unit_CUnit
#include <lwd/unit/Assert.h>
#include <lwd/unit/CUnit.h>
#include <IntegerVariable.h>
#include <Integer.h>
/****************************************************************
test cases
****************************************************************/
static IntegerVariable instance;
void setUp() {
instance = new_IntegerVariable(811);
}
void tearDown() {
IntegerVariable_finalize(instance);
}
void testNewAndFinalize() {
/* setUp, tearDown */
}
void testGetValue() {
int value = IntegerVariable_getValue(instance);
Assert_equalsInt("A value must be 811", 811, value);
}
void testSetValue() {
IntegerVariable_setValue(instance, 812);
int value = IntegerVariable_getValue(instance);
Assert_equalsInt("A value must be 812", 812, value);
}
void testInheritedGetValue() {
Integer reference = (Integer)instance;
int value = Integer_getValue(reference);
Assert_equalsInt("A value must be 811", 811, value);
}
/****************************************************************
main
****************************************************************/
int main(int argc, char** argv) {
return CUnit_test(argc, argv);
}
testGetValue()と testInheritedGetValue()では各々自クラスとスーパクラスとして保持したインスタンスで継承をテストしている。
動作確認の様子をつぎに示す。 なお、コンストラクタを newと initの部分に分けた Integerクラスも回帰テスト (regression test) している。
$ make clean test
cc -g -finstrument-functions -I. -I /export/home/kei/c/core/include -I /export/home
/kei/c/experimental/include -I /export/home/kei/c/io/include -I /export/home/kei/c
/lang/include -I /export/home/kei/c/message/include -I /export/home/kei/c/unit/include
-I /export/home/kei/c/util/include -I /export/home/kei/c/x11/include -I ../../char/
include -I ../../class1/include -c -o IntegerTest.o IntegerTest.c
cc -g -finstrument-functions -I. -I /export/home/kei/c/core/include -I /export/home
/kei/c/experimental/include -I /export/home/kei/c/io/include -I /export/home/kei/c
/lang/include -I /export/home/kei/c/message/include -I /export/home/kei/c/unit/include
-I /export/home/kei/c/util/include -I /export/home/kei/c/x11/include -I ../../char/
include -I ../../class1/include -c -o IntegerVariableTest.o IntegerVariableTest.c
# IntegerTest #
[CUnit] ./IntegerTest: testGetValue
[CUnit] ./IntegerTest: testNewAndFinalize
[SUCCEED] testGetValue in 2(ms)
[SUCCEED] testNewAndFinalize in 2(ms)
successes: 2 (100.0%)
failures: 0 (0.0%)
errors: 0 (0.0%)
total: 2 in 4(ms)
# IntegerVariableTest #
[CUnit] ./IntegerVariableTest: testGetValue
[CUnit] ./IntegerVariableTest: testInheritedGetValue
[CUnit] ./IntegerVariableTest: testNewAndFinalize
[CUnit] ./IntegerVariableTest: testSetValue
[SUCCEED] testGetValue in 3(ms)
[SUCCEED] testInheritedGetValue in 1(ms)
[SUCCEED] testNewAndFinalize in 2(ms)
[SUCCEED] testSetValue in 2(ms)
successes: 4 (100.0%)
failures: 0 (0.0%)
errors: 0 (0.0%)
total: 4 in 8(ms)
$
おまえ、AKB48のファンだって?全員の名前言えるか? 俺なんか Perfumeの全員の名前スラスラ言えるぞ!
0 件のコメント:
コメントを投稿