1.2. 設計モデリング¶
設計モデリングのアウトプットとなる設計モデルは、MDA [1] にて定義される PSM [2] と等価である。 すなわち設計モデルの作成を進めるには、まず、設計モデルと実装を紐づけるルールを定義する必要がある。 ルール策定の際には、以下を考慮しなくてはならない。
- プログラミング言語
- RTOS
- マルチタスク制御
- 動的メモリ確保
なお、本ドキュメントで説明するマッピングルールは、以下の前提で定義する。
- プログラミング言語には C言語を使用
- 実装対象のプラットフォームは RTOS を搭載
- マルチタスク制御あり
- 動的メモリ確保あり
1.2.1. C言語によるオブジェクト指向プログラミング¶
C言語はオブジェクト指向をサポートする言語ではないため、クラスなど、UMLで扱われるモデルをそのまま実装することができない。 このため、まず [Gre00] に従い、以下のモデルを導入する。
- シングルインスタンスモジュール (Single-instance module)
- システム稼働中に必要となるインスタンスは、ひとつだけであるモジュール。 一般的に C言語で実装されるファイルモジュールと等価と考えて差支えない。
- マルチインスタンスモジュール (Multiple-instance module)
- システム稼働中、複数のインスタンスが必要となるモジュール。 Java、C#、C++ などオブジェクト指向をサポートする言語のクラスインスタンスとほぼ等価である。
- 型による動的インターフェイス (Per-type dynamic interface)
- 同じインターフェイスを持つモジュールの型が複数あり、それぞれ独自のインターフェイスを持てるようにするためのモデル。 オブジェクト指向の継承を C言語で実現するためのモデル。
以下、各モデルごとに UML での表記ルールと、C言語での実装例を示す。
注釈
[Gre00] では、動的インターフェイス (Dynamic Interface) というモデルも定義されているが、 型による動的インターフェイスのみで十分と判断し、使用しないこととする。
1.2.1.1. シングルインスタンスモジュール¶
以下にシングルインスタンスモジュールの UML 表記を示す。 当該モジュールのクラスには、ステレオタイプ <<SingleInstance>> を付与する。
以下にヘッダファイル (LightScheduler.h) の記述内容を示す。 プロトタイプ宣言は、基本的にクラスの public 操作をそのまま落としこめばよい。
#ifndef D_LightScheduler_H
#define D_LightScheduler_H
#include "TimeService.h"
enum { LS_OK=0, LS_TOO_MANY_EVENTS, LS_ID_OUT_OF_BOUNDS };
void LightScheduler_Create(void);
void LightScheduler_Destroy(void);
int LightScheduler_ScheduleTurnOn(int id, Day day, int minuteOfDay);
int LightScheduler_ScheduleTurnOff(int id, Day day, int minuteOfDay);
void LightScheduler_Randomize(int id, Day day, int minuteOfDay);
void LightScheduler_ScheduleRemove(int id, Day day, int minuteOfDay);
void LightScheduler_WakeUp(void);
#endif /* D_LightScheduler_H */
次にソースコード (LightScheduler.c) の記述内容を示す。まずヘッダファイルインクルード、定数、属性の定義、private メソッドのプロトタイプ宣言を行う。 シングルインスタンスモジュールの場合、属性は static 変数として定義する。
#include "LightScheduler.h"
#include "LightController.h"
#include "TimeService.h"
#include "RandomMinute.h"
#include <stdlib.h>
#include <string.h>
enum
{
TURN_ON, TURN_OFF, DIM, RANDOM_ON, RANDOM_OFF
};
enum
{
MAX_EVENTS = 128, UNUSED = -1
};
typedef struct
{
int id;
Day day;
int minuteOfDay;
int event;
int randomize;
int randomMinutes;
} ScheduledLightEvent;
static ScheduledLightEvent eventList[MAX_EVENTS];
続いて Create/Destroy メソッドを定義する。Create メソッドでは属性の初期化や、資源の獲得等を行う。 Destroy メソッドでは必要に応じて資源の解放等を行う。(コード例は属性の初期化のみ)
void LightScheduler_Create(void)
{
int i;
for (i = 0; i < MAX_EVENTS; i++)
{
eventList[i].id = UNUSED;
}
}
void LightScheduler_Destroy(void)
{
}
以降は public/private メソッドを定義する。実装の詳細は省略する。
int LightScheduler_ScheduleTurnOn(int id, Day day, int minuteOfDay)
{
...
}
int LightScheduler_ScheduleTurnOff(int id, Day day, int minuteOfDay)
{
...
}
void LightScheduler_Randomize(int id, Day day, int minuteOfDay)
{
...
}
void LightScheduler_ScheduleRemove(int id, Day day, int minuteOfDay)
{
...
}
void LightScheduler_WakeUp(void)
{
...
}
1.2.1.2. マルチインスタンスモジュール¶
以下にマルチインスタンスモジュールの UML 表記を示す。 当該モジュールは一般的なクラスとして扱うため、特別な表記は付与しない。
以下にヘッダファイル (CircularBuffer.h) の記述内容を示す。 まず、クラスデータ構造 (CircularBufferStruct) へのポインタ型 (CircularBuffer) を 定義する。次に、クラスの public 操作をプロトタイプ宣言に落としこむが、モデルで 定義したパラメタに加え、第1パラメタにクラスデータ構造へのポインタを定義しなければ ならないことに注意する。
#ifndef D_CircularBuffer_H
#define D_CircularBuffer_H
typedef struct CircularBufferStruct * CircularBuffer;
CircularBuffer CircularBuffer_Create(int capacity);
void CircularBuffer_Destroy(CircularBuffer);
int CircularBuffer_IsEmpty(CircularBuffer);
int CircularBuffer_IsFull(CircularBuffer);
int CircularBuffer_Put(CircularBuffer, int);
int CircularBuffer_Get(CircularBuffer);
int CircularBuffer_Capacity(CircularBuffer);
void CircularBuffer_Print(CircularBuffer);
int CircularBuffer_VerifyIntegrity(CircularBuffer);
#endif /* D_CircularBuffer_H */
このように、クラスデータ構造へのポインタ型を前方宣言することにより、当該モジュールの 詳細を隠ぺいすることができる。
注釈
動的メモリ確保が使用できないプラットフォームでは、クラスデータ構造を公開する必要がある。 この場合、クラスデータ構造は Private ヘッダ (CircularBufferPrivate.h 等) に定義し、 当該モジュールの利用者が意識する必要がないものであることを明示する。
次にソースコード (CircularBuffer.c) の記述内容を示す。 まずヘッダファイルインクルード、定数、属性の定義、private メソッドのプロトタイプ宣言を行う。 マルチインスタンスモジュールの場合、属性はクラスデータ構造のフィールドとして定義する。
#include "CircularBuffer.h"
#include "Utils.h"
#include <stdlib.h>
#include <string.h>
typedef struct CircularBufferStruct
{
int count;
int index;
int outdex;
int capacity;
int * values;
} CircularBufferStruct ;
enum {BUFFER_GUARD = -999};
続いて Create/Destroy メソッドを定義する。マルチインスタンスモジュールの場合、属性を保持する 領域は、malloc()等、標準ライブラリや RTOS が提供する動的メモリ確保 API を用いて獲得する。 Destroy メソッドでは、属性保持用の領域を解放する。
CircularBuffer CircularBuffer_Create(int capacity)
{
CircularBuffer self = calloc(capacity, sizeof(CircularBufferStruct));
self->capacity = capacity;
self->values = calloc(capacity + 1, sizeof(int));
self->values[capacity] = BUFFER_GUARD;
return self;
}
void CircularBuffer_Destroy(CircularBuffer self)
{
free(self->values);
free(self);
}
以降は public/private メソッドを定義する。属性は、第1パラメタ経由で変更、参照する。
int CircularBuffer_VerifyIntegrity(CircularBuffer self)
{
return self->values[self->capacity] == BUFFER_GUARD;
}
int CircularBuffer_IsEmpty(CircularBuffer self)
{
return self->count == 0;
}
int CircularBuffer_IsFull(CircularBuffer self)
{
return self->count == self->capacity;
}
int CircularBuffer_Put(CircularBuffer self, int value)
{
if (self->count >= self->capacity)
return 0;
self->count++;
self->values[self->index++] = value;
if (self->index >= self->capacity)
self->index = 0;
return 1;
}
int CircularBuffer_Get(CircularBuffer self)
{
int value;
if (self->count <= 0)
return 0;
value = self->values[self->outdex++];
self->count--;
if (self->outdex >= self->capacity)
self->outdex = 0;
return value;
}
int CircularBuffer_Capacity(CircularBuffer self)
{
return self->capacity;
}
void CircularBuffer_Print(CircularBuffer self)
{
int i;
int currentValue;
currentValue = self->outdex;
FormatOutput("Circular buffer content:\n<");
for (i = 0; i < self->count; i++) {
if (i != 0)
FormatOutput(", ");
FormatOutput("%d", self->values[currentValue++]);
if (currentValue >= self->capacity)
currentValue = 0;
}
FormatOutput(">\n");
}
注釈
各メソッドの第1パラメタは、C++ の this ポインタと同じ役割を果たす。
1.2.1.3. 型による動的インターフェイス¶
以下に型による動的インターフェイスの UML 表記を示す。 他モジュールへのインターフェイスとなるモジュール (スーパークラス) は抽象クラスで定義し、 固有の処理・データは抽象クラスのサブクラスにまとめる。
スーパークラスの実装¶
以下にヘッダファイル (LightDriver.h) の記述内容を示す。 マルチインスタンスモジュール同様、クラスデータ構造 (LightDriverStruct) へのポインタ型 (LightDriver) を定義し、 public 操作をプロトタイプ宣言に落としこむ。
#ifndef D_LightDriver_H
#define D_LightDriver_H
typedef struct LightDriverStruct * LightDriver;
void LightDriver_Destroy(LightDriver);
void LightDriver_TurnOn(LightDriver);
void LightDriver_TurnOff(LightDriver);
const char * LightDriver_GetType(LightDriver driver);
int LightDriver_GetId(LightDriver driver);
#include "LightDriverPrivate.h"
#endif /* D_LightDriver_H */
型による動的インターフェイスを使用する場合は、複数のファイルが LightDriverStruct のレイアウトを知る必要があるため、 これを Private ヘッダに定義する。
#ifndef D_LightDriverPrivate_H
#define D_LightDriverPrivate_H
typedef struct LightDriverInterfaceStruct * LightDriverInterface;
typedef struct LightDriverStruct
{
LightDriverInterface vtable;
const char * type;
int id;
} LightDriverStruct;
typedef struct LightDriverInterfaceStruct
{
void (*TurnOn)(LightDriver);
void (*TurnOff)(LightDriver);
void (*Destroy)(LightDriver);
} LightDriverInterfaceStruct;
#endif /* D_LightDriverPrivate_H */
最後に、ソースコード (LightDriver.c) の記述内容を示す。 サブクラスメソッドは、vtable フィールドを通して呼び出す。
#include "LightDriver.h"
#include "common.h"
void LightDriver_TurnOn(LightDriver self)
{
if (self)
self->vtable->TurnOn(self);
}
void LightDriver_TurnOff(LightDriver self)
{
if (self)
self->vtable->TurnOff(self);
}
void LightDriver_Destroy(LightDriver self)
{
if (self)
self->vtable->Destroy(self);
}
const char * LightDriver_GetType(LightDriver driver)
{
return driver->type;
}
int LightDriver_GetId(LightDriver driver)
{
return driver->id;
}
サブクラスの実装¶
以下に X10LightDriver サブクラスのヘッダファイル (X10LightDriver.h) の記述内容を示す。 クラスデータ構造 (X10LightDriverStruct) へのポインタ型 (X10LightDriver) 、および公開する列挙型 (X10_HouseCode)を 定義する。プロトタイプ宣言は、Create メソッドのみ行う。
#ifndef D_X10LightDriver_H
#define D_X10LightDriver_H
#include "LightDriver.h"
typedef struct X10LightDriverStruct * X10LightDriver;
typedef enum X10_HouseCode {
X10_A,X10_B,X10_C,X10_D,X10_E,X10_F,
X10_G,X10_H,X10_I,X10_J,X10_K,X10_L,
X10_M,X10_N,X10_O,X10_P } X10_HouseCode;
LightDriver X10LightDriver_Create(int id, X10_HouseCode code, int unit);
#endif /* D_X10LightDriver_H */
次にソースコード (X10LightDriver.c) の記述内容を示す。 まず、ヘッダファイルをインクルードした後、クラスデータ (X10LightDriverStruct) 構造を定義する。 X10LightDriverStruct の先頭に、スーパークラスのフィールド (base) を定義している点に注意する。
#include "X10LightDriver.h"
#include "LightDriverPrivate.h"
#include <stdlib.h>
#include <memory.h>
#include "common.h"
typedef struct X10LightDriverStruct
{
LightDriverStruct base;
X10_HouseCode house;
int unit;
} X10LightDriverStruct;
続いて vtable に登録する関数を、static 関数として定義する。
static void destroy(LightDriver super)
{
X10LightDriver self = (X10LightDriver)super;
free(self);
}
static void turnOn(LightDriver super)
{
X10LightDriver self = (X10LightDriver)super;
formatTurnOnMessage(self);
sendMessage(self);
}
static void turnOff(LightDriver super)
{
X10LightDriver self = (X10LightDriver)super;
explodesInTestEnvironment(self);
formatTurnOffMessage(self);
sendMessage(self);
}
vtable に登録する関数を interface としてまとめ、 最後に Create メソッドを定義する。
static LightDriverInterfaceStruct interface =
{
turnOn,
turnOff,
destroy
};
LightDriver X10LightDriver_Create(int id, X10_HouseCode house, int unit)
{
X10LightDriver self = calloc(1, sizeof(X10LightDriverStruct));
self->base.vtable = &interface;
self->base.type = "X10";
self->base.id = id;
self->house = house;
self->unit = unit;
return (LightDriver)self;
}
1.2.2. UML-C マッピングルール¶
本節では、UMLとC言語概念のマッピングルールを定義する。
1.2.2.1. UML to C¶
クラス図¶
- クラス
- C言語によるオブジェクト指向プログラミング で導入したモデルにもとづいて定義する。 構造体など、C言語が提供する型の表現は、 C to UML を参照のこと。
- 属性 (attribute)
- クラスが、基本型 (char、short、int など) のインスタンス、もしくはポインタ [3] を保持する場合は、属性で表現する。
注釈
基本型には、int32_t など、基本型を typedef した型も含む。
- 関連 (association)
- クラスが、ポインタを通してクラスインスタンスを参照する場合は、関連で表現する。
- 集約 (aggregation)
- 集約は、厳密には関連以上の意味を持たない [Mar00] ため、使用しないこと。 使用する場合は、ノート等で意図を明記すること。
- コンポジション (composition)
クラス間の関係が以下のいずれかに当てはまる場合は、コンポジションで表現する。
- あるクラスが、もう一方のクラスのインスタンスを保持する場合 (シングルインスタンスモジュール の例を参照)
- あるクラスが、ポインタを通してもう一方のクラスインスタンスを参照するが、 もう一方のクラスインスタンスは共有不可 (所有者数は 1 以下) である場合
1.2.2.2. C to UML¶
型¶
- 構造体
- ステレオタイプ <<struct>> を付加したクラスとする
- メンバ変数は public 属性で定義する
- 以下は非表示とする
- 可視性
- 操作区画
- 列挙型
- ステレオタイプ <<enumeration>> を付加したクラスとする
- 列挙定数は int 型の public 属性として定義する
- 以下は非表示とする
- 可視性
- 属性の型
- 操作区画
- 共用体
- ステレオタイプ <<union>> を付加したクラスとする
- メンバ変数は public 属性で定義する
- 以下は非表示とする
- 可視性
- 操作区画
プリプロセッサ¶
- マクロ定数
- 定義されるクラスの属性として定義する
- 属性にはステレオタイプ <<macro>> を付加する
- マクロ関数
- 特殊な場合を除いて、マクロ関数はインライン関数で代替可能であるため、本ドキュメントはインライン関数の使用を推奨する。 従って、マクロ関数の表記法は定義しない。
1.2.3. 組込みソフトウェア特有概念の表現ルール¶
本節では、 [Ogs00] を参考に、割り込みなど組込みソフトウェア特有の概念を UML で表現するルールを定義する。
1.2.3.1. 構造面¶
- 割り込み
- 割り込みコントローラをアクターで表現する
- 割り込みハンドラとなる操作に、ステレオタイプ <<interrupt>> を付加する
- タスク
- アクティブクラスとする
- タスクのエントリポイントとなる操作には、ステレオタイプ <<OsTask>> を付加する
- タスクを管理する RTOS は、アクターで表現する
1.2.3.2. 振る舞い面¶
- 排他
- シーケンス図上では複合フラグメント critical を使用し、排他に使用する手段 (割り込み禁止、セマフォ等) を名前で表現する
- マルチタスクによる並行処理
- シーケンス図上では複合フラグメント par を使用する
- RTOS からのタスク起動メッセージは非同期とする [4]
脚注
[1] | Model-Driven Architecture (モデル駆動型アーキテクチャ)、Object Management Group (OMG) が2001年に 公式に発表したソフトウェア設計手法のこと。 |
[2] | platform-specific model (プラットフォーム特化モデル) |
[3] | astah* では、ポインタをタイプ修飾子で表現することができる。その他のツールでは、ポインタ型のクラスを別途定義すること。 |
[4] | タスクの実行完了を RTOS が待つわけではないため。 |