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>> を付与する。

../_images/Single-instance_module.png

シングルインスタンスモジュール - クラス図

以下にヘッダファイル (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 表記を示す。 当該モジュールは一般的なクラスとして扱うため、特別な表記は付与しない。

../_images/Multi-instance_module.png

マルチインスタンスモジュール - クラス図

以下にヘッダファイル (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 表記を示す。 他モジュールへのインターフェイスとなるモジュール (スーパークラス) は抽象クラスで定義し、 固有の処理・データは抽象クラスのサブクラスにまとめる。

../_images/Per-type_dynamic_interface.png

型による動的インターフェイス - クラス図

スーパークラスの実装

以下にヘッダファイル (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] を保持する場合は、属性で表現する。
../_images/uml-attribute.png

注釈

基本型には、int32_t など、基本型を typedef した型も含む。

関連 (association)
クラスが、ポインタを通してクラスインスタンスを参照する場合は、関連で表現する。
集約 (aggregation)
集約は、厳密には関連以上の意味を持たない [Mar00] ため、使用しないこと。 使用する場合は、ノート等で意図を明記すること。
コンポジション (composition)

クラス間の関係が以下のいずれかに当てはまる場合は、コンポジションで表現する。

  1. あるクラスが、もう一方のクラスのインスタンスを保持する場合 (シングルインスタンスモジュール の例を参照)
  2. あるクラスが、ポインタを通してもう一方のクラスインスタンスを参照するが、 もう一方のクラスインスタンスは共有不可 (所有者数は 1 以下) である場合

1.2.2.2. C to UML

構造体
  • ステレオタイプ <<struct>> を付加したクラスとする
  • メンバ変数は public 属性で定義する
  • 以下は非表示とする
    • 可視性
    • 操作区画
../_images/c-struct.png
列挙型
  • ステレオタイプ <<enumeration>> を付加したクラスとする
  • 列挙定数は int 型の public 属性として定義する
  • 以下は非表示とする
    • 可視性
    • 属性の型
    • 操作区画
../_images/c-enum.png
共用体
  • ステレオタイプ <<union>> を付加したクラスとする
  • メンバ変数は public 属性で定義する
  • 以下は非表示とする
    • 可視性
    • 操作区画
../_images/c-union.png

プリプロセッサ

マクロ定数
  • 定義されるクラスの属性として定義する
  • 属性にはステレオタイプ <<macro>> を付加する
../_images/c-macro.png
マクロ関数
特殊な場合を除いて、マクロ関数はインライン関数で代替可能であるため、本ドキュメントはインライン関数の使用を推奨する。 従って、マクロ関数の表記法は定義しない。

1.2.3. 組込みソフトウェア特有概念の表現ルール

本節では、 [Ogs00] を参考に、割り込みなど組込みソフトウェア特有の概念を UML で表現するルールを定義する。

1.2.3.1. 構造面

割り込み
  • 割り込みコントローラをアクターで表現する
  • 割り込みハンドラとなる操作に、ステレオタイプ <<interrupt>> を付加する
../_images/embedded-interrupt.png
タスク
  • アクティブクラスとする
  • タスクのエントリポイントとなる操作には、ステレオタイプ <<OsTask>> を付加する
  • タスクを管理する RTOS は、アクターで表現する
../_images/embedded-os-task.png

1.2.3.2. 振る舞い面

排他
  • シーケンス図上では複合フラグメント critical を使用し、排他に使用する手段 (割り込み禁止、セマフォ等) を名前で表現する
../_images/interrupt-handering.png
マルチタスクによる並行処理
  • シーケンス図上では複合フラグメント par を使用する
  • RTOS からのタスク起動メッセージは非同期とする [4]
../_images/multi-task.png

脚注

[1]Model-Driven Architecture (モデル駆動型アーキテクチャ)、Object Management Group (OMG) が2001年に 公式に発表したソフトウェア設計手法のこと。
[2]platform-specific model (プラットフォーム特化モデル)
[3]astah* では、ポインタをタイプ修飾子で表現することができる。その他のツールでは、ポインタ型のクラスを別途定義すること。
[4]タスクの実行完了を RTOS が待つわけではないため。