ラベル ソフトIC の投稿を表示しています。 すべての投稿を表示
ラベル ソフトIC の投稿を表示しています。 すべての投稿を表示

2015年6月14日日曜日

オブジェクト指向からコンポーネント思考へ

「オブジェクト指向」と検索してざざっとでてくる用語。

「カプセル化」「多態性」「再利用」「リファクタリング」「プロトタイプベースオブジェクト指向」「クラスベースオブジェクト指向」「振る舞い」「UML」「抽象化」「デザインパターン」

上記のようないろんな用語以外にも良くわからない技術で表現していたりして不明瞭さたっぷりです。

つまりオブジェクト指向とは万人が同じ答えにならない、「不明瞭」な物を扱っている覚悟をしておく必要があります。

工業製品としてソフトウェアを作るなら誰もがある一定上の同じモノができるようにモノサシやテンプレートが必要になると思います。

そこで古くからあるコンポーネント思考の導入の始まりです。

ソフトウェアの「職人技、一品物」から「工業製品、量産品」のように扱える、そんなやり方を説明していきます。

~~~~~~~~~~~~~~~~~~

まずはおさらい、オブジェクト指向について

要点だけをまとめると、

・オブジェクト指向とは、オブジェクトを表現するものがクラスであること。
 クラスがオブジェクトを表現するんじゃなく、本質は逆なのです。

 例えば、目の前にある人物像のデッサンする場合、
 目の前にあるのがオブジェクトであって、キャンバスに描くのがクラスです。
 クラスを作ってから目の前のオブジェクトは作れないんです。
 「鳥取は島根の隣」と同じ次元では無いことがわかったでしょうか。
 補足するとデザインパターンにFactoryパターンというがあるのですが、命名した人は根本的な間違えに気づいてないと思います。
 


・インスタンスとは、元は図形をコピーするという意味。
 現在の言語仕様にあてはめるとクラス(設計図)のコピーにあたる。生成物ではない。
 オブジェクトは生成じゃなく、複製だからです。
 目の前にあったオブジェクト(人物像)をキャンバス(クラス)に描いて出来たものがオブジェクトなので、生成じゃなく複製が正しい表現となります。


・オブジェクト指向には多態性と振る舞いは不要。
 オブジェクト思考のメリットで騒がれてる事はすべて投げ捨ててしまいましょう。
 これらは後付の理由なので本質を知る上では不要です。

 そうすることでオブジェクトそのものをクラスに表現できるようになります。
 オブジェクトをクラスに表したものをModelクラスと呼んでいます。

例
----

class PenModel
{
//ペンに関する関心のあるプロパティを記述。
public int Length { get; set; }
public Color Color { get; set; }

//最初はメソッドは用意しない
//メソッド追加する場合は、オブジェクトの変化を与えるメソッドであって振る舞いでは無い。
//振る舞い=イベントなので動的、状態変化は静的、同じ用に見えて異なるからです。

//これはOK
public void ChangeColor(Color newColor)
{
this.Color = newColor;
}

//これはNG、後で説明するが、直感的な動詞はController向けの名称
public void Write(int count)
{
this.Length -= count;
}

//これはOK
public void DecLength(int count)
{
this.Length -= count;
}
}

----

class Main
{
public static void Main()
{
//求めているペンを生成する
var blackPen = new Pen { Length = 10, Color = Color.Black };
var redPen = new Pen { Length = 10, Color = Color.Red };
}
}

----

私はMVCフレームワークの影響を受けてしまっているのでModel部分のメソッド定義はController側に分離してしまっているのでそうなっています。
そうすることで、オブジェクトの本来の性質が表に出て分かりやすいと思うし、値の検証もしやすさと多態性の表現をControllerで表せれる点など思うことは沢山あります。
まあ独自の書き方ですが、

class Model
{
...(関心のあるプロパティと変化メソッド)
}

//多態性はコントローラクラスで分けて表現
//同じ動作する関数が増えていくのがキライって方はModelクラスをpartialしてインターフェイスで分けて
//内部情報の書き換えはPrivateにする事で解決します。
//(オブジェクト単体で見るとすべて丸見えだけど、インターフェイス経由なら制限できるので)
//ちなみにControllerクラスには操作するModel以外の内部変数は極力作らない事です。
class Controller1
{
Model model;
public Controller1(Model model)
{
this.model = model;
}

public void Write(string message)
{
Console.WriteLine(message);
model.DecLength(message.Length);
}
}

class Controller2
{
Model model;
public Controller2(Model model)
{
this.model = model;
}

public void ChangeColor(Color newColor)
{
this.model.ChangeColor(newColor);
}
}


雰囲気だけは伝わったかと思います。
あとは何回か書くと理解が進むんじゃないかな。
再利用についてはコンポーネント指向の回で説明します。

○オブジェクト指向の設計手法
 オブジェクトの表現について理解できたならば、
次はオブジェクト設計ですが、

間違えやすいポイントを最初に持ってくると、基本的な考え方は
対象となるオブジェクト同士の関係が

 1:1, 1:N, N:1, N:N

であるかがポイントになります。


例えて男女関係と見立てると複雑度が分かりやすいです。

1:1の関係はごく普通。

1:N、N:1の関係はおや?ってなりますね。ここは何とかしなくてはなりません。

    プログラム的にいえばModelクラスを複数束ねるCollectionクラスを用意するなどです。

N:Nはもはや分かりません。

つまりオブジェクト設計とは

1:1の関係に持っていく作業です。


設計の仕方は仕様の元となるクライアントとヒアリングして

ざっくりと全体図を書きます。

プログラムとは自動化するのが本来の目的なので

現状、人手でやることをすべて表すことと関係する役者をすべて並べること作業の事です。

これらをユースケース図を使って書きます。

全部書き表したらよく言われる「汎化」作業に入るのですが、ここで注意ポイント

 勝手に減らしたりくっつけたりしない事、です。

オブジェクト指向の説明でデッサンの例のように

聞いた内容=オブジェクト

なので、それをユースケース図で書き表すのが作業です。

勝手にアレンジを加えるのはNG、提案はOKですが仕様の構造を変えるような提案はNGです。

現状をそのまま書き表して確認する、これが重要であるからです。

なぜそこまでして現状そのままに書き表すのかというと

現実と1:1で合わせておくことで「実はこうだった」など後出しの変更に強いからです。

ユースケース図を描いてN:Nの箇所があれば、ヒアリングして別の担当者になる人を探したりする作業も

オブジェクト設計のうちの1つになります。

この時点でなるべく1:1になるようにするのが設計の最適化。

もしNのオブジェクトで違う作業があるなら再度ヒアリングして分割できるかチェック、

だめならばプログラム上、多態性で表現することになります(←これが後々の問題の種になる事を承知の上でね。)

もちろんユースケース図に間違いがあった場合、仕様バグなので論外です。

-------------

ここまでで
 オブジェクト指向とは、オブジェクト設計と実装方法について説明しました。


ここからコンポーネント指向についてを述べていきます。

おなじみとなったVB、C#の開発環境でデザイナー画面にアイテムを設置していくだけでプログラムが出来てしまう

あれがコンポーネント指向です。アイテムはコンポーネントと呼ばれているので気づいた方はいるんじゃないでしょうか。

あの機能はDelphiが元になっていて、MSが開発者を引き抜いたりお金の力で出来たのが今のVisualStudioとC#言語仕様になります。

http://ja.wikipedia.org/wiki/Visual_Component_Library

VLCの基本構造は最下層になるTObjectクラスがあって派生でトップに立ったものがコンポーネント、いわゆるソフトウェア部品・モジュールとなります。

底のオブジェクトがあって最上位になっていくほど高機能になっていく構造です。

車輪の再開発ですが、これをプログラムで表現しようという試みです。

全体ブロック図

+--------------------+----
| 業務画面                    |
+--------------------+  ビジネス領域(フォーム画面に部品ペタペタ、イベント広げて処理書いたり、、)
| ビジネスロジック          |
+---------+----------+----
|     Library |                  |  コンポーネント部品(.NETから派生するのでこの位置をキープ)
+---------+                 |
|       .NET                     |
+--------------------+----


今回着目する箇所はLibraryと書かれている部分です。

Libraryのブロック図

+--------------------+---
|Controller               |  共通操作
|                           |  Modelクラスには、Driverモデルと共通パラメータ、共通操作
+--------------------+---
|Driver(Model)           |  プロトコルレイヤー(外部と通信プロトコル(コマンド)で操作を行う)
|                           |  Modelクラスには、通信仕様上で必要になるパラメータ(IDなど)、コマンドメソッド
+--------------------+---
|外部IO(Model)              |  外部入出力レイヤー(シリアルポート、ファイル、TCP/IP 通信を行う)
|                                  |  Modelクラスには、ポート初期化パラメータ、Open/Close/Read/Write
+--------------------+---


こんな具合でしょうか。

大体は外部と通信するのでオブジェクトの体は通信インターフェイスにします。

その上に相手とのコミュニケーションする電文の作成・解析が必要になってきて

さらに上になると判断してコミュニケーションする頭脳という具合ですかね。

IOが足回り、Driverが手足、Controllerが頭脳、こうなります。


例でいうとTCP/IPで相手と通信する場合、通信仕様が必要になるので、先ほどのブロック図に当てはめていけばコンポーネントの完成。

つまり再利用を重視した構造であるということになります。

使うユーザーは最初に書いたとおり、VisualStudioを操作して画面に部品を設置するだけでプロパティウィンドウに書かれたプロパティを設定していくと
通信仕様とは一切知らなくても通信プログラムが完成します。

こういった部品を沢山作って行くことで

画面と状態遷移とビジネスロジックをプログラムにする作業に集中できるようになります。


大規模になってもオブジェクト指向で設計をがんばってもらって

足りないコンポーネントは作って、

ビジネスロジックと1:1で向き合える大きさまで派生させて、

画面単位にコンポーネントをくっつけて、

イベント広げて処理を書いてビジネスロジックを動かす、

画面遷移するなら遷移する、終了するなら終了で。

そんな作業の流れができてくればコーディング量も減って品質の向上につながるのではないでしょうか。

色々書いてきましたが、

オブジェクト指向は設計用で実装用ではないです。

設計と実装を1:1にするのもありですが、上記作業なら無理して合わせる必要もないですね。

ここでの注意ポイントはビジネスロジックにパラメータは持たない事です。


以上、思考の整理とともに長々と書いてみました。

2011年1月19日水曜日

ソフトウェアIC化考案(3)

3.モジュール構造を考える(2)

たたき台のソースが出来たので貼り付けておく。

基本構造はこんな感じにビジネスロジックだけ書き入れるスタイルにしておいて、
外部モジュールのプロパティアクセスはシングルトンでGetInstance経由でポインタ取得する。
プログラムの構造化は、ビジネスロジックの下位モジュールを別で作成してドライバなり扱えるようにする。
上位ロジックはイベント間通信でやり取りする。
とりあえずこれでシリアル通信でバーコードリーダとやり取り出来るプログラムを作って
どのレベルまでできるか確認しておきたい。

/*
ソース説明
objbase.h   ... オブジェクト基底モジュール
objects.h   ... ツールで自動生成するオブジェクトヘッダファイル
event.def   ... ツールで自動生成するイベント一覧
main.c    ... メインループ

sample_obj1.c  ... サンプル用オブジェクト実体

sample_obj1.def  ... サンプル用オブジェクトヘッダファイル
sample_obj2.def  ... サンプル用オブジェクトヘッダファイル

使い方
まず
オブジェクト作成コマンドを実行。 
コマンドイメージ)オブジェクト生成して、イベントを追加する。
./manage.py create_object sample_obj1
./manage.py add_event sample_obj1 evt1_1 evt1_2

*/

//**********************************************************************
// objbase.h
//**********************************************************************
#ifndef __OBJBASE_H__
#define __OBJBASE_H__
//オブジェクト構造体定義
typedef unsigned int uint32;
typedef signed int sint32;


typedef void (*event)(int* sid);
typedef struct {
    uint32 req_flag;          // 要求した側が操作できるフラグ
    uint32 act_flag;          // メインループ側が操作できるフラグ
    uint32 sid[32];           // イベント別シーケンス番号
    event* events;            // イベントリスト
    void* property;           // 変数ポインタ(プロパティ)
} object;


//シーケンス関係
#define dSEQ_END  (-1)
#define dSEQ_START  (0)

//イベント関係
#define SetEvent(id) objects[id >> 16].req_flag |= (1 << (id & 0x1f));
#define ClrEvent(id) objects[id >> 16].req_flag &= (0xffffffff ^ (1 << (id & 0x1f)));
#define isEventStart(id) (0 != (objects[id >> 16].act_flag & (1 << (id & 0x1f))))
#define isEventFin(id)   (0 == (objects[id >> 16].act_flag & (1 << (id & 0x1f))))

//オブジェクト関係
#define get_instance(id) (objects[id >> 16].property);


#endif//__OBJBASE_H__






//**********************************************************************
// sample_obj1.c
//**********************************************************************
#include "objbase.h"
#include "obj_test.h"

void evt1_1(int* );
void evt1_2(int* );
#include "sample_obj1.def"
#include "event.def"

void smaple_obj1_init()
{
 sample_obj1_property = 0;
}

//シーケンス1
void evt1_1(int* sid){
 switch(sid){
  case 0:
   SetEvent(EVT_OBJ1_TEST2);
   *sid++;
   break;
  case 1:
   if(isEventStart(EVT_OBJ1_TEST2)){
    ClrEvent(EVT_OBJ1_TEST2);
    *sid++;
   }
   break;
  case 2:
   if(isEventFin(EVT_OBJ1_TEST2)){
    *sid++;
   }
   break;
  case 3:
   int* _inctance = (int*)get_instance(EVT_OBJ1_INST);
   if(_instance == 1) *sid = dSEQ_END;
   break;
 }
}

//シーケンス2
void evt1_2(int* sid){
 *sid++;
 if(10req_flag & chk_flag)
#define ACT_FLG (obj->act_flag & chk_flag)



int main(){
    object* obj;
    event* evt;
    int* sid;
    int chk_flag;

    init_object();                   // 全体初期化

    for(;;){                         // メインループ
        chkEvent();
        for(obj = objects; NULL != obj; obj++){
            for(evt=obj->events, sid=obj->sid, chk_flag=0x1; NULL!=evt; evt++, sid++, chk_flag<<=1){
                if(0 != ACT_FLAG){
                    if(0 <= obj->sid){
                        evt(obj->property, sid);              //2.イベント処理実行
                    }else if (0 == REQ_FLG){
                        obj->act_flag ^= chk_flag;            //3.イベント処理終了
                    }
                }else if(0 != REQ_FLG){
                    obj->sid = 0;
                    obj->act_flg |= chk_flag;                 //1.イベント処理開始
                    evt(obj->property, sid);
                }
            }
        }
    }
    
    final_object();                             //全体開放

 return -1;
}

ソフトウェアIC化考案(1)

1.決められた定石について考える。

そもそも自分のスタート地点が組み込み屋なので、「ソフトウェアIC化」という考え自体が
ハードウェア寄りな気質がある。

1-1.現状について

Wikipediaによるとソフトウェアコンポーネントというのは、
http://ja.wikipedia.org/wiki/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88

抜粋)
ソフトウェアコンポーネントを効果的に再利用可能にするには多大な労力と注意が必要である。特に以下のような点が重要である:

* 完全に文書化すること
* テストをしっかり行うこと
* 特に各種入力値を入力したときの動作をしっかり確認すること
* 分かり易い(あるいは利用し易い)エラーメッセージを返すこと
* 想定していない状況で使われる可能性があることを念頭において開発すること

1つのプログラムを作るのに、これだけの労力を払って
「価値はあるのか。」
という問題がある。
Borland社のDelphiで成功したコンポーネント思考の開発手法はあるが、
.Net製品を見ると肥大化しすぎているように思う。
もし準拠して作るならば、
「自分でコンポーネント部品を作ってからメインロジックを組み込む」
この手順を踏まないといけなくなるので、これが1つの制約になって敷居が高くなってるように見える。

1-2.ソフトウェアの定石
プログラム言語とは、「機械語にするための言語」であって、
ソフトウェアは「人間のやっている事を自動化するためのロジック」である。
最近のフレームワークは、ソフトウェアの効率化であって、本来目的以外のロジックも含むデメリットがある。
そこで機械にやらせたいソフトウェア(ロジック)をどうすれば出来るのか、ものすごい基本的な部分を
定石と定義しておく。

まず揺れ動かない部分、スタートアップとメイン。
void main()
{
    x1_init();
    x2_init();

    for(;;){
        //他のモジュール内のメインを動かす
        x1_main();
        x2_main();
        ....
        //アプリケーション終了イベントで抜ける
    }
    x1_finalize();
    x2_finalize();
}

Web系などアプリ層をメインにしている人はフレームワークに任せていて
自分でMain処理をあまり書かないかもしれないが、本来これが基礎になっている。
この部分にやりたい事すべてを入れ込めるかがカギで、
できるだけ1周期内で分岐もせずに、必要な処理以外実行されない構造に出来るかが
ポイントだろう。

ここで、組み込みソフトの場合は電源モードなど動作状態を変える必要があって、
もし動作状態を変える場合は、他のメインループ処理を追加する形になる。

static int PowerMode = 1;

void main()
{
    //全体の初期化
    init();

    //メインループ
    switch(PowerMode)
    {
    case 1:
        x1_init();
        x2_init();

        for(;;){
            //他のモジュール内のメインを動かす
            x1_main();
            x2_main();
            ....
            //アプリケーション終了イベントで抜ける
        }
        x1_finalize();
        x2_finalize();
        break;
    case 2:
        x3_init();
        x4_init();

        for(;;){
            //他のモジュール内のメインを動かす
            x3_main();
            x4_main();
            ....
            //アプリケーション終了イベントで抜ける
        }
        x3_finalize();
        x4_finalize();
        break;
    }
    
    //全体の終了処理
    finalize();
}
ここでインライン化せずに関数に置き換えてしまうと、
コールスタックに戻り先が格納されてしまうので注意。
あえてベタ書きでサンプルコードを記述した。

上記メインループからどうやって一番最適なモジュールが作れるかを次までに考えてみる。


構想メモ:
 ソフトウェアの定石はテンプレートで作成できる構造を狙っている。
それを実現するにはまず、分岐処理の排除、ロジックはなるべく1パスの単純構造になるように設計する点。

ソフトウェアIC化考案(0)

連載の背景)
ソフトウェアの開発手法や概念は今までに沢山の種類が出回っていて、思いついたのだけでも
モジュールの流用やオブジェクト指向、アスペクト思考、構造化手法、フレームワークなど
沢山のテクニックがあって、それぞれ書籍になって売られてる。
これらの手法は使える場面で最も効果が発揮されるように設計されていて、
1つの成功で1つの考えを突き通して手法の1元化などやってしまうとデメリットばかり出てきて
結論として「最初から作り直した方が早かった」、みたいな事になってしまう場合もある。

ISO9001やEMMIなど、開発標準化やレベルなどを示す基準がある。
この基準のハードルをあげれば開発業務の品質が上がるか?という観点で見ると決してそうではない。
なぜならば、開発内容に関する細部の取り決め、厳しい制約についての記述は業務側で決めていくものだからである。
つまり自由な枠組みの中は決められた基準を満たしていれば、表向きの評価は高くする事ができる。
言い換えれば車の免許で、免許持ってるから運転がずば抜けてうまいかといえばそうではなく、
毎日運転してるドライバーからサンデードライバー、ペーパードライバまで幅広くあるわけで、
あればいいというものでもない。
もし改善するならば、人によって10人10色になる部分をどれだけシステマチックにできるかがテーマになると思う。
そこで、この問題に対する解決策はあるのか?というテーマを書いていく予定です。
言い換えて、ソフト開発(職人)としての自分探しの為に連載していきますw

Androider