はじめに
「C言語でトライ! デザインパターン」
今回はAbstract Factoryパターン。抽象クラスをまとめたクラスも実は抽象クラスという、インターフェースを包含したインターフェースといった感じでしょうか。その抽象化されたインターフェースを使ってオブジェクトの生成を行うという。
元々オブジェクト指向な言語であれば有用だと思います。UI開発でも非常に便利そう。
インターフェースクラスのないCでパターンだと、そのまま表現しようとすると面白い使い方が中々出てこないですね。規模の大きなもの(独自HTTPプロトコルを利用したWebアプリケーション-HTTPサーバー間のインターフェース定義とか)でより活きそうなデザインかなと思いつつ、一応自分なりに面白そうかなと思った使い方をサンプルに載せてみました。
デザインパターン一覧
作成したライブラリパッケージの説明
公開コードはこちら
Abstract Factoryパターン
Wikipediaの説明はこちらです。
関連するインスタンス群を生成するための API を集約することによって、複数のモジュール群の再利用を効率化することを目的とする。日本語では「抽象的な工場」と翻訳される事が多い。
Techscoreで紹介されている一般的なクラス図はこちら。インターフェースクラスの組み合わせで実現されるインターフェースクラスAbstructFactoryを定義し、利用者もAbstructFactoryを利用する形にして、インターフェース一部もAbstructFactory自体もまるっと差し替えられるようにしよう!という感じですかね。
**ポイントはgetProductを利用して生成を行う点。**普通に私はこのポイントについて最初見落としていました。
Abstract Factory応用とされるDOMについて
WikipediaではDocument Object Model (DOM)がAbstract Factoryパターンの応用例として挙げられていました。
DOMはJavaScriptで利用されている、HTMLに対する操作を行う為のインターフェースですね。実際にはこちらはJavaScriptの為に用意されたインターフェースというわけではなく、DOM StandardでHTML操作のインターフェース群であるDOMが定義されていて、そのインターフェース群をJavaScriptで実現しているという図式になるようです。
クラス図で表現するとこんな感じですかね。生成部分は抜きにして、「Objectツリーと、各Objectが持つインターフェース」を表現したという印象です。
~~生成部分は抽象化されていない(というか読み込み時点でものが作られている状態なので)気がしますが、~~クラスの関係性についてはAbstructFactoryと似た構成になっていますかね。
2018/10/06 追記
コメントを踏まえて仕様を見直しました。どちらかというと「既存のHTMLの情報を取得する」APIに対してではなく、「HTMLに新たな要素を追加する追加する」系のAPIが該当するような気がしてきました。
例えばclassで記載したNodeオブジェクトのインターフェース定義内にはこんなメソッドがあります。
interface Document : Node {
[CEReactions, NewObject] Element createElement(DOMString localName, optional (DOMString or ElementCreationOptions) options);
};
既存のHTML情報を取得するほかに、createXXXで好きなHTML要素を追加することが出来るようになっています。
このcreateElementのようなcreateXXXで好きなHTML要素を追加することが出来るようになっています。こちらが生成のメソッドに対応。
そしてElement等の実体をJavaScript側でcreateElementを利用してDocumentに追加、HTMLを加工していく。
そんな風に既存のHTMLをAbstructFactoryの思想をうまく利用して再構築も出来ますよ!というのが「Abstract Factory応用」と説明された理由な気がします。
う~ん、Webクライアントサイドの知識が薄くていまいち確信が持てないけど、こんなニュアンスかな~。
サンプル
まずはConcreateFactory部分を作成
Cでの活用方法だと、ある程度大きな規模のライブラリに対してAbstructFactoryの考え方を適用し、一部機能はインターフェースクラスを模したproductを取得する形だと面白いんじゃないかなと思います。
例えばライブラリAPI定義はこのような感じ。abstruct_factory_newと書いていますが、生成出来るものはいつも同じなのでTechscoreのクラス図でいうConcreateFactoryの位置づけですね。
/*! product1の定義 */
typedef struct abstruct_product1_t {
char *(*get_name1)();
void (*free)(AbstructProduct1 this);
} abstruct_product1_t, *AbstructProduct1;
/*! product2の定義 */
typedef struct abstruct_product2_t {
char *(*get_name2)();
void (*free)(AbstructProduct2 this);
} abstruct_product2_t, *AbstructProduct2;
/*! AbstructFactory定義。privateメンバーは実装者に任せる */
struct abstruct_factory_t;
typedef struct abstruct_factory_t *AbstructFactory;
/*必要なインターフェースは定義しておく*/
struct abstruct_factory_t {
AbstructProduct1 (*get_product1)(AbstructFactory this);
AbstructProduct2 (*get_product2)(AbstructFactory this);
};
/*インターフェース継承用のdefine*/
#define ABSTRUCT_FACTORY_IF \
AbstructProduct1 (*get_product1)(AbstructFactory this);\
AbstructProduct2 (*get_product2)(AbstructFactory this);
AbstructFactory abstruct_factory_new(void);
void abstruct_factory_free(AbstructFactory this);
ライブラリの規模が大きくなるなら、このような形にしてproductを抽象化してあげるのはありだと思います。
利用者はProductの実体を意識せずにProductが利用可能ですし、Cでのインターフェースクラス利用としても悪くない形ではないかなと思います。
thisを渡さないといけないのが回りくどいですが、そこはオブジェクト指向言語でないCの限界ですね。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "abstruct_factory.h"
int main() {
AbstructFactory factory = abstruct_factory_new();
AbstructProduct1 product1 = factory->get_product1(factory);
AbstructProduct2 product2 = factory->get_product2(factory);
printf("call product1 API:%s\n", product1->get_name1());
printf("call product2 API:%s\n", product2->get_name2());
product1->free(product1);
product2->free(product2);
abstruct_factory_free(factory);
return 0;
}
実行するとこんな感じにget_name1、get_name2が実行されます。
$ ./sample
call product1 API:product1
call product2 API:product2
AbstructFactoryライブラリ内の実装
ライブラリ側の実装はこんな感じ。abstruct_factory.cとproduct1.c, product2.cの3ファイルに分けて実装しました。
main処理であるabstruct_factory.cは例えばこんな感じ
#include <stdlib.h>
#include "product1.h"
#include "product2.h"
typedef struct abstruct_factory_imple_t {
ABSTRUCT_FACTORY_IF
/*プライベートメンバーはIF defineの後に定義*/
AbstructProduct1 (*create_product1)();
AbstructProduct2 (*create_product2)();
} *AbstructFactoryImple;
static AbstructProduct1 abi_get_product1(AbstructFactory this) {
return ((AbstructFactoryImple)this)->create_product1();
}
static AbstructProduct2 abi_get_product2(AbstructFactory this) {
return ((AbstructFactoryImple)this)->create_product2();
}
AbstructFactory abstruct_factory_new(void) {
AbstructFactoryImple instance = calloc(1, sizeof(*instance));
if(!instance) return NULL;
/*メソッド設定*/
instance->get_product1 = abi_get_product1;
instance->get_product2 = abi_get_product2;
/*プライベートメンバー設定*/
instance->create_product1 = product1_new;
instance->create_product2 = product2_new;
return (AbstructFactory)instance;
err:
abstruct_factory_free((AbstructFactory)instance);
return NULL;
}
void abstruct_factory_free(AbstructFactory this) {
AbstructFactoryImple instance = (AbstructFactoryImple)this;
free(this);
}
productの処理はこんな感じにしてみました(2も同様なので省略)
#include <stdlib.h>
#include "product1.h"
//get_name1の実体定義
static char *product1_get_name1() {
return "product1";
}
static void product1_free(AbstructProduct1 this) {
free(this);
}
AbstructProduct1 product1_new() {
AbstructProduct1 instance = calloc(1, sizeof(*instance));
if(!instance) return NULL;
instance->get_name1 = product1_get_name1;
instance->free = product1_free;
return instance;
}
さらにConcreateFactory部分を抽象化
このライブラリを直接使う形になると、1つのAbstrcutFactoryしか持てない形になり、AbstrcutFactoryの抽象化が出来ていない状態です。
なのでmain側をもう少し工夫してAbstrcutFactoryの抽象化しました。
クラス設計はこのようにしました。AbstrcutFactoryの実体であるConcreateFactoryにあたる部分はライブラリとして実装。ライブラリを動的にロードすることでAbstrcutFactoryを差し替えることが出来るよう、Factoryの生成者であるFactoryManagerクラスを追加。このクラスのコンストラクタに指定するパラメーターによって生成されるAbstrcutFactoryの実体が変わるような仕様です。
FactoryManagerクラスのメソッド定義はこんな感じ
#include "products_factory.h"
struct factory_manager_t {
AbstructFactory factory;
};
typedef struct factory_manager_t *FactoryManager;
FactoryManager factory_manager_new(const char * factory_libname);
void factory_manager_free(FactoryManager this);
mainはこう変更されます。ConcreateFactoryへの依存もなくなりました。
int main() {
//ここが変更点。引数を変えることでAbstructFactoryを複数扱えるようになり、AbstructFactoryが抽象化された。
FactoryManager manager = factory_manager_new("../lib/.libs/libproducts_factory.so");
if(!manager) return 0;
AbstructProduct1 product1 = manager->factory->get_product1(manager->factory);
AbstructProduct2 product2 = manager->factory->get_product2(manager->factory);
printf("call product1 API:%s\n", product1->get_name1());
printf("call product2 API:%s\n", product2->get_name2());
product1->free(product1);
product2->free(product2);
//解放の仕方もAbstructFactoryは意識しない形に。
factory_manager_free(manager);
return 0;
}
FactoryManagerの実装はこんな感じ。エラー処理は省略
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>
#include "abstruct_factory.h"
typedef struct factory_manager_imple_t {
AbstructFactory factory;
void *handle;
void (*factory_free)(AbstructFactory this);
} *FactoryManagerImple;
FactoryManager factory_manager_new(const char * factory_libname) {
FactoryManagerImple instance = calloc(1, sizeof(*instance));
//AbstructFactory実体のロード
instance->handle = dlopen(factory_libname, RTLD_NOW);
//メソッド読み込み
AbstructFactory (*abstruct_new)(void);
abstruct_new = dlsym(instance->handle, "abstruct_factory_new");
instance->factory_free = dlsym(instance->handle, "abstruct_factory_free" );
//AbstructFactory実体の生成
instance->factory = abstruct_new();
return (FactoryManager)instance;
}
void factory_manager_free(FactoryManager this) {
FactoryManagerImple instance = (FactoryManagerImple)this;
if(instance->factory) instance->factory_free(instance->factory);
if(instance->handle) dlclose(instance->handle);
free(instance);
}
サンプルコードは以下に格納しています。
https://github.com/developer-kikikaikai/design_patter_for_c_appendix/tree/master/abstruct_factory
感想
インターフェースクラスのないC言語でのAbstract Factoryクラス、使うの難しいだろうなと思いつつ考えてみました。実装だけ見ると大分周りくどいですね。その分構成としては色々な部分の依存度が低くなっているので切り分けのしやすい構成だと思います。スケジュールやチーム構成等その時々の状況に合わせてうまく活用していきたいです。
抽象化・切り分けのしやすい構成に出来るパターンだけに、やりすぎて目的と手段が逆転しないように気を付けないといけないですね。