C言語におけるモジュール化設計の様式について述べる

5315 ワード

今日はモジュール化の問題について引き続き話します.これはゆっくりシリーズにしたいのですが、必ずしも連続して書くとは限りません.基本的に思い出したので、考えを整理します.主に後で集中して整理するために敷物を作ります.
階層が明確なコードが最もメンテナンスしやすいことはよく知られています.システム全体に大きな副作用をもたらす心配がなく、階層上のモジュールを簡単に交換することができます.
階層のはっきりしない設計の中で、最悪の1つはモジュールの循環依存です.すなわち,2つのモジュールが誰が上にいるのか,誰が下にいるのか分からない.この時、最も絡みやすく、その結果、両者を一体として維持することが多い.初期化手順など複雑な詳細も含まれています.
次に,階層を越えたモジュール連絡である.モジュールAがモジュールBの上層であり、モジュールBがモジュールCの上層である場合、モジュールCがモジュールAに見えるように、モジュールAにはC導出インタフェースの直接呼び出しがあり、明確な設計にはタブーである.しかし、この問題を完全に回避することは難しいが、A対Cの呼び出しを完全にBを通過させる.しかし、通常は最善を尽くすべきだ.(注:後で本を書く場合は、実際の例を補足して説明しようと思います)ただし、言語が本来サポートされていないデータ型やインフラストラクチャについては、システム用に作成する必要があります.例外もあります.メモリ管理、ロゴ管理、文字列列列(C言語は元のライブラリ関数で管理するのが面倒です)など、基礎モジュールとして提供することができますが、異なる階層のモジュールで直接使用される可能性があります.しかし、一定の階層に上がった後、それらを隠す必要があります.
次はもっと現実的な分析をします.
C言語を例にとると,C言語はnamespaceのオリジナルサポートに欠けているため,apiに統一プレフィックスを付けて区別することが多い.これも面倒ではありません.
モジュールAは"A_の山のように見えますxxxxx'を名前にする方法.私個人は単一モジュールが大きすぎるべきではないと主張し、実現時に同じモジュールに置くのに適している.cファイルでいいです.通常、モジュールはオブジェクトのクラスを中心に処理されます.これらのオブジェクトは整数handleで表すことも、特定のタイプのオブジェクトポインタで表すこともできます.二つの案にはそれぞれ千秋がある.まず対象ポインタのシナリオについてお話しします.
モジュールAのインタフェース記述ファイルは、(後でより現実的なコードを補完したい):
 1 #ifndef _A_h

 2 #define _A_h

 3 

 4 struct A;

 5 struct B;

 6 

 7 struct A* A_create(void);

 8 void A_release(struct A *self);

 9 void A_bind(struct A *self , struct B *b);

10 void A_commit(struct A *self);

11 void A_update(void);

12 

13 int A_init(void);

14 

15 #endif

ここでは,Aというデータ型を定義した.個人的にはtypedefやマクロでコード入力を減らすことに反対します.特別な理由がない限り、新しいタイプを定義するのではなくstruct接頭辞を書きます.特に、最下位のモジュール設計ではなおさらです.インタフェース記述の場合、struct Aの詳細は絶対に露出すべきではなく、そのデータ構造は実装されたファイルa.cにのみ存在すべきである.
Aに関するインタフェースは通常2つのクラスに分けられ、1つはstruct A*に対していくつかの処理を行うので、最初のパラメータをselfポインタに入力します.これはC++のthisポインタに相当します.例えば、前例のA_commit;もう1つのクラスはC++クラスに近い静的メンバー関数であり、通常、A_updateのようなオブジェクトのすべてを処理するために使用される.
注:私はCでC++をシミュレートするつもりはありませんが、データ型に基づいていくつかの処理方法を行います.Cにとって、このような書き方も一般的なパターンです.オブジェクト向けなど複雑なシステムを構築する際によく使われる方法については、後で自分がよく使う別のパターンについてお話しします.C++のように、似ていなくてもいいかもしれません.どう書けばいいかは、見識の問題だ.あまりこだわる必要はありません.
ここでの例では、別のデータ型Bについても言及する.明らかに、それはBモジュールに置かれています.
私たちは通常a.hでinclude b.hに行くのではなく、struct Bを宣言するだけです.(C言語にとって、これは必要ありませんが、書くのは良い習慣です).これは、BがAの下にあるモジュールであれば、Aモジュールの実現において、Bの方法が用いられるので、Aモジュールを用いた人には、Bのインタフェースが見えないようにします.a.hを含む同時暗黙的にb.hを含めることは必要ありません.
例示的なコードから,struct Aはstruct Bに対する何らかのパッケージであり,Aに対する操作によってその中のBタイプに間接的に操作できると推測できる.Aのモジュール初期化A_initでは必ずBが初期化されます.そうであれば、Bの階層はAの下にある.
struct Bには、struct Aタイプの参照も保持されることが多い.まず、私たちはこのような状況を避けるように努力しなければならない.すなわち、下層に位置するBは上層のAについて何も知らないほうがよい.Bモジュールにstruct Aが表示される必要がある場合は、少なくともstruct A*のみが参照であり、Aモジュール内のインタフェースへの呼び出しが絶対に発生しないことを保証する必要があります.巧みな方法を用いるとは思わないで,サイクル依存初期化問題を迂回すれば十分である.これは設計の原則であるべきで、違反しないでください.
btw,軽率なインタフェース設計は往々にして後日システムの脆弱な根源である.図はすぐに、勝手にインタフェースを露出したり、頭がいいと思って「巧み」な方法を使ったり、文法糖で設計原則を迂回したりするのは危険です.
一般的な難解な問題は、struct Aとstruct Bが互いに双方向に参照されている場合である.どのようにしてこの引用関係を構築しますか?この確立の過程は、果たしてAの方法なのか、それともBの方法なのか.私の答えは、誰が上層部にいるか、誰の方法ですか.
しかし、AとBは互いに内部データレイアウトの詳細が見えず、Bの内部にAタイプを参照させ、例えばBモジュールからインタフェースを露出させる必要がある.このインタフェースは、Aのみで使用できるかもしれません.この例では、A_bindという方法しか使用できません.
C++ならfriendを採用するかもしれません.他のテクニックも使用できます.どうせC++で掘り起こす文法が多すぎる.でもCはどうするの?次は自分の案をください.
もともと、Bで導出したapiは、
void B_set_A(struct B *self,struct A * a); 


次のように書きます.
struct i_A;



void B_set_A(struct B *self,struct i_A *a);


b.cの実装ではstruct i_に関数を追加するA*からstruct A*への変換.
static inline struct A * A(struct i_A *a) { return (struct A *)a; }


次に、a.cの実装では、struct A*をstruct i_A *に変換するための類似関数が追加される.
このように、a.c以外のモジュールでは、struct i_Aというインタフェースを誤って使用することなく、B_set_Aのタイプを得ることができない.
テキストリンク:http://blog.codingnow.com/2010/01/modularization_in_c_1.html