データ駆動プログラミングとテーブル駆動法(マルチif-else構造簡素化)

157197 ワード

リンク先の転載
http://tec.5lulu.com/detail/108asn4wm11y68sdc.html
1データ駆動プログラミングのコア
データ駆動プログラミングの核心的な出発点は
プログラムロジックよりも、人間はデータの処理が得意です.
.データはプログラムロジックよりも制御しやすいので、できるだけ設計の複雑さを制御しなければならない.
プログラムコードからデータに移行します.
本当にそうなの?例を見てみましょう.
他のプログラムが送信したメッセージを処理するプログラムがあり、メッセージタイプは文字列であり、各メッセージには関数が必要であると仮定します.第一印象では、私たちはこのように処理するかもしれません. 
   
   
   
   
  1. void msg_proc(const char *msg_type, const char *msg_buf)
  2. {
  3. if (0 == strcmp(msg_type, "inivite"))
  4. {
  5. inivite_fun(msg_buf);
  6. }
  7. else if (0 == strcmp(msg_type, "tring_100"))
  8. {
  9. tring_fun(msg_buf);
  10. }
  11. else if (0 == strcmp(msg_type, "ring_180"))
  12. {
  13. ring_180_fun(msg_buf);
  14. }
  15. else if (0 == strcmp(msg_type, "ring_181"))
  16. {
  17. ring_181_fun(msg_buf);
  18. }
  19. else if (0 == strcmp(msg_type, "ring_182"))
  20. {
  21. ring_182_fun(msg_buf);
  22. }
  23. else if (0 == strcmp(msg_type, "ring_183"))
  24. {
  25. ring_183_fun(msg_buf);
  26. }
  27. else if (0 == strcmp(msg_type, "ok_200"))
  28. {
  29. ok_200_fun(msg_buf);
  30. }
  31. 。。。。。。
  32. else if (0 == strcmp(msg_type, "fail_486"))
  33. {
  34. fail_486_fun(msg_buf);
  35. }
  36. else
  37. {
  38. log(" %sn", msg_type);
  39. }
  40. }

上記のメッセージタイプはsipプロトコル(まったく同じではなく、sipプロトコルはhttpプロトコルを参考にしている)から取られ、メッセージタイプはさらに増加する可能性があります.通常のプロセスを見ていると少し疲れているかもしれませんが、中間のメッセージが処理されているかどうかを検出するのも苦労します.また、メッセージを1つ増やさずに、プロセスブランチを1つ増やさなければなりません.
データ駆動プログラミングの考え方に従って、次のように設計される可能性があります. 
   
   
   
   
  1. typedef void (*SIP_MSG_FUN)(const char *);
  2. typedef struct __msg_fun_st
  3. {
  4. const char *msg_type;//
  5. SIP_MSG_FUN fun_ptr;//
  6. }msg_fun_st;
  7. msg_fun_st msg_flow[] =
  8. {
  9. {"inivite", inivite_fun},
  10. {"tring_100", tring_fun},
  11. {"ring_180", ring_180_fun},
  12. {"ring_181", ring_181_fun},
  13. {"ring_182", ring_182_fun},
  14. {"ring_183", ring_183_fun},
  15. {"ok_200", ok_200_fun},
  16. 。。。。。。
  17. {"fail_486", fail_486_fun}
  18. };
  19. void msg_proc(const char *msg_type, const char *msg_buf)
  20. {
  21. int type_num = sizeof(msg_flow) / sizeof(msg_fun_st);
  22. int i = 0;
  23. for (i = 0; i < type_num; i++)
  24. {
  25. if (0 == strcmp(msg_flow[i].msg_type, msg_type))
  26. {
  27. msg_flow[i].fun_ptr(msg_buf);
  28. return ;
  29. }
  30. }
  31. log(" %sn", msg_type);
  32. }

次のような考え方の利点があります.
1、可読性がより強く、メッセージ処理の流れが一目瞭然である.
2、修正が容易で、新しいメッセージを追加するには、データを修正すればいいので、プロセスを修正する必要はありません.
3、再利用、第1のスキームの多くのelse ifは実はメッセージタイプと処理関数が異なるだけで、論理は同じです.次のスキームは,このような同じ論理を抽出し,変化しやすい部分を外に持ち出すことである.
2裏に潜む思想 
多くの設計構想の背後にある原理は実際には共通しており、データ駆動プログラミングの背後に隠されている実現思想には以下のものが含まれている.
1、複雑さをコントロールする.プログラムロジックの複雑さを人間がより扱いやすいデータに移すことで,複雑さを制御する目標を達成する.
2、隔離変化.上記の例のように、各メッセージ処理の論理は変わらないが、メッセージは変化する可能性があり、変化しやすいメッセージと変化しにくい論理を分離する.
3、メカニズムと策略の分離.第2点とよく似ていて、本書の多くの場所でメカニズムと策略に言及しています.前例では、私の理解では、メカニズムはメッセージの処理ロジックであり、ポリシーは異なるメッセージ処理である(後で、メカニズムとポリシーを紹介する文章を専門に書きたい).
3データ駆動プログラミングで何ができるか 
上記の例に示すように、関数レベルの設計に適用できます.
同時に、プログラムレベルの設計にも適用できます.典型的には、テーブル駆動法でステータスマシンを実現することもできます(後述する記事で説明します).
DSLのようなシステムレベルの設計にも使用できます(この面では経験が不足していますが、現在は非常に確定していません).
それは何ではありません.
1、それは新しいプログラミングモデルではありません:それはただ1種の設計構想で、その上歴史は悠久で、unix/linuxコミュニティで多くの応用があります;
2、それはオブジェクト向けの设计の中のデータと同じではありません:“データドライバのプログラミングの中で、データはあるオブジェクトの状态を表すだけではなくて、実际にまたプログラムの流れを定义しました;OOはカプセル化を重视して、データドライバのプログラミングはできるだけ少ないコードを书くことを重视します.”
本の中の考えるべき言葉:
データがすべてを圧倒する.正しいデータ構造を選択し、すべての組織をきちんと整理すれば、正しいアルゴリズムは言うまでもありません.プログラミングの核心はアルゴリズムではなくデータ構造です.Rob Pike
プログラマーは手の施しようがない...コードを外し、腰をまっすぐにして、データをよく考えるのが最善の行動です.式のプログラミングの真髄.Fred Brooks
データはプログラムロジックよりも制御しやすい.設計の複雑さをコードからデータにできるだけ移すのは良い実践です.--『unixプログラミング芸術』の著者.
4複雑なテーブル駆動 
メッセージ(イベント)駆動システムを考慮すると、システムのあるモジュールは他のいくつかのモジュールと通信する必要がある.メッセージを受信した後、メッセージの送信者、メッセージのタイプ、自身の状態に応じて、異なる処理を行う必要があります.一般的な方法の1つは、ハードコーディングによって実現される3つのカスケードswitchブランチを使用することです. 
   
   
   
   
  1. switch(sendMode)
  2. {
  3. case:
  4. }
  5. switch(msgEvent)
  6. {
  7. case:
  8. }
  9. switch(myStatus)
  10. {
  11. case:
  12. }

この方法の欠点:1、可読性が高くない:メッセージの処理部分のコードを探すには多層コードをジャンプする必要がある.2、switchブランチが多すぎて、これも実は重複コードです.彼らには共通の特性があり、さらに精錬することもできる.3、拡張性が悪い:プログラムに新しいモジュールの状態を追加すると、すべてのメッセージ処理の関数を変更する可能性があり、非常に不便で、プロセスがエラーしやすい.4、プログラムには主心骨が欠けている:要綱をつかむことができる主幹が欠けており、プログラムの主幹は大量のコード論理に埋もれている.テーブル・ドライバで実装:定義された3つの列挙:モジュール・タイプ、メッセージ・タイプ、自己モジュール・ステータスに基づいて、関数ジャンプ・テーブルを定義します. 
   
   
   
   
  1. typedef struct __EVENT_DRIVE
  2. {
  3. MODE_TYPE mod;//
  4. EVENT_TYPE event;//
  5. STATUS_TYPE status;//
  6. EVENT_FUN eventfun;//
  7. }EVENT_DRIVE;
  8. EVENT_DRIVE eventdriver[] = // ,
  9. {
  10. {MODE_A, EVENT_a, STATUS_1, fun1}
  11. {MODE_A, EVENT_a, STATUS_2, fun2}
  12. {MODE_A, EVENT_a, STATUS_3, fun3}
  13. {MODE_A, EVENT_b, STATUS_1, fun4}
  14. {MODE_A, EVENT_b, STATUS_2, fun5}
  15. {MODE_B, EVENT_a, STATUS_1, fun6}
  16. {MODE_B, EVENT_a, STATUS_2, fun7}
  17. {MODE_B, EVENT_a, STATUS_3, fun8}
  18. {MODE_B, EVENT_b, STATUS_1, fun9}
  19. {MODE_B, EVENT_b, STATUS_2, fun10}
  20. };
  21. int driversize = sizeof(eventdriver) / sizeof(EVENT_DRIVE)//
  22. EVENT_FUN GetFunFromDriver(MODE_TYPE mod, EVENT_TYPE event, STATUS_TYPE status)//
  23. {
  24. int i = 0;
  25. for (i = 0; i < driversize; i ++)
  26. {
  27. if ((eventdriver[i].mod == mod) && (eventdriver[i].event == event) && (eventdriver[i].status == status))
  28. {
  29. return eventdriver[i].eventfun;
  30. }
  31. }
  32. return NULL;
  33. }

この方法の利点:1、プログラムの可読性を高めた.1つのメッセージがどのように処理されるかは、ドライバテーブルを見るだけでわかります.明らかです.2、重複コードを減らした.この方法のコード量は第1種より少ないに違いない.どうして?それはいくつかの重複するもの:switch分岐処理を抽象化し、その中の共通のもの--3つの要素の検索処理方法に基づいて1つの関数GetFunFromDriverに駆動テーブルを追加することを抽象化したからだ.3、拡張性.この関数ポインタに注意して、彼の定義は実は1種の契約で、javaの中のインタフェース、c++の中の純粋な虚関数に似ていて、この条件(パラメータに入って、値を返します)を満たしてこそ、1つのイベントの処理関数とすることができます.これはプラグイン構造の味がして、これらのプラグインを簡単に交換したり、追加したり、削除したりして、プログラムの動作を変えることができます.このような変更は,イベント処理関数の検索を分離する(分離変化と呼ぶこともできる),4、プログラムには明らかな主幹がある.5、複雑度を下げた.プログラムロジックの複雑さを人間がより扱いやすいデータに移すことで,複雑さを制御する目標を達成する. 
5継承と組合せ
イベント駆動を考慮
のモジュールで、このモジュールは多くのユーザーを管理し、各ユーザーは多くのイベントを処理する必要があります.では、私たちが作成したドライバテーブルはモジュールではなく、ユーザー向けであり、ユーザーがある状態で、あるモジュールのイベントを受信する処理であるべきである.さらに,ユーザは異なるレベルに分けることができ,各レベルは上述した処理に対して異なると仮定する.
オブジェクト向け
ユーザーのベースクラスを設計し、同じイベントの処理方法を実現することを考慮することができます.レベルに応じて、いくつかの異なるサブクラスを定義し、共通の処理を継承し、それぞれ異なる処理を実現します.これは最も一般的な考え方で、継承法と呼ぶことができます.
テーブル駆動法でどうやって実現しますか?ユーザーのクラスを直接設計し、サブクラスもなく、具体的なイベントの処理方法もありません.イベントを受信した後、すべてこのドライバテーブルに処理を依頼するメンバーがいます.ユーザーのレベルに応じて、複数の異なるドライバテーブルを定義して、異なるオブジェクトインスタンスをアセンブリできます.これは彼の組み合わせと呼ぶことができる.
継承と組み合わせは「デザインモデル」にも言及されている.組み合わせの利点は、その拡張性、弾力性、パッケージ性を強調することです.(継承と組合せは、この記事を参照してください:オブジェクト向け
の継承グループについて
この場合の駆動テーブルは、構造体を引き続き使用できます
を選択します. 
上記の方法のパフォーマンス最適化の推奨事項:
性能に対する要求が高くなければ、上記の方法は十分に対応できます.パフォーマンス要件が高い場合は、適切な最適化が可能です.たとえば、多次元配列を作成できます.
を選択します.これにより、これら3つの列挙に基づいて、テーブルを調べるのではなく、下付きスケールに基づいて処理関数に直接位置決めすることができる.(実はやはりデータ駆動の思想です:データ構造は静的なアルゴリズムです.) 
データ駆動プログラミングがもっと高度で抽象的なのは、プロセススクリプトかDSLであるはずです.xmlに寄生する簡単な寄生を書いたことがあります
で説明している手順を実行します.これは後で時間を割いて紹介します.