SOLID原則:4つ目 インターフェース分離の原則(ISP:Interface Segregation Principle)
〇インターフェース分離の原則(ISP:Interface Segregation Principle)
クライアントにクライアント利用しないメソッドへの依存を強制してはならない
あなたがある機能を実現するモジュールを作成しているとして、そのモジュールが外部に公開しているIFを見るとメソッドが100個以上あって、複数クライアントから利用されているといったことはよくあると思います。
各クライアントはすべてのインターフェースを利用しているわけでなく、自分がほしい機能に関係するインターフェースを利用しているだけなので、機能ごとにインターフェースをグループ化して分けることで、クライアント利用するインターフェースを分離することができます。
こうすることで、クライアントは自分に関係あるインターフェースに対する変更した影響しなくなります。
SOLID原則:5つ目 依存関係逆転の原則(DIP:The Dependency Inversion Principle)
〇依存関係逆転の原則(DIP:The Dependency Inversion Principle)
上位のモジュールは下位のモジュールに依存してはならない。どちらのモジュールも「抽象」に依存すべきである
「抽象」は実装の詳細に依存してはならない。実装の詳細が「抽象」に依存すべきである。
構造化分析に従うと、システムをトップダウンで分析していき、主要機能を実現するのに必要なサブ機能を見つけていきます。これをそのままSW設計と実装に適用するとサブ機能の組み合わせで上位ができるため、上位は下位に依存する構成になります。
余談
こうなった時のデメリットとしては、些細な実装の変更が上位のモジュールに伝わってしまうため、すぐ変更が必要になってしまうことです。自分が作っている範囲ならいいのですが、上位と下位モジュールを別組織で作成していたりすると、変更自体が難しくなります。
例えば、下位担当が楽な変更を考えたとしても、上位への影響が大きいとか言われて、下位担当は楽じゃない変更をすることが起こりえます。また、上位からすると下位がちょっと変えてくれればいいのに他の上位モジュールにも影響があるから下位は変更できないとなります。
つまり、最終的に望むのは上位と下位が直接依存しないことになります。
以下の図では、Policy(方針)がMechanism、MechanismがUtilityに依存している。ここまでダイレクトな依存関係だとUtilityを変更することPolicyへ影響することになってしまいます。(そして、余談にかいてあるようなやりとりが発生します。)
上位は自信が期待する振る舞いを定義したInterfaceを用意し、下位はInterfaceに沿った実装をする(下位が上位へ依存する)。この結果、上位はInterfaceに、下位はInterfaceに依存する構造のため、Interfaceで決めた範囲の変更では、余談に書いたようなことは起こりません。
アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技
- 作者: ロバート・C・マーチン,Robert C. Martin,瀬谷啓介
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2008/07/01
- メディア: 単行本
- 購入: 18人 クリック: 586回
- この商品を含むブログ (71件) を見る
GenICam:ノードについて
GenICam規格のノード説明のメモ
http://www.emva.org/wp-content/uploads/GenICam_Standard_v2_0.pdf
■ノードについて
・
それぞれのノードにはName属性があり、 名前はカメラデバイス内で一意でなければならない。あと、NameSpace属性で識別される名前空間内に名前属性が存在する。
例:
名前空間はCustomかStandardの2つの名前を持つことができる。名前はカメラデバイス内で一意な値である必要はあるが、名前空間がStandardのときはSFNCにも従う必要がある。
・
拡張領域として用意できる。無視される。
・
ノードの簡単な説明を記載
・
ノードのより詳細な説明を記載
・
機能の名前。
・
Beginner, Expert, GuruとInvisibleと可視性を制御できるパラメータ。
・
非同期イベント通信で使用するIDです。
SOLID原則:3つ目 リスコフの置換原則(LSP:Liskov-Substitution Principle)
〇リスコフの置換原則(LSP:Liskov-Substitution Principle)
派生型は基本型と置換可能でなければならない
上の意味だけ聞いてもなんのこっちゃとなるとおもうのですが、
リスコフの置換原則に反したケースを皆さん見たことがあると思います。
class MainScreen : public Screen { virtual void Disp(); ~省略~ }; class SubScreen : public Screen { virtual void Disp(); ~省略~ }; void Display(Screen* p) { if (typeid(p) == typeid(MainScreen))※RTTIはイメージのために使っているので実際にこのコードを入れても動作しないと思います。 { static_cast<MainScreen* >(p)->Disp(); } else if (typeid(p) == typeid(SubScreen )) { static_cast<SubScreen * >(p)->Disp(); } }
クラスの型を見て処理を変える、コードをみたことはあるんじゃないでしょうか。。。。
ここでは、MainScreen とSubScreen がScreenの代わりに使えていません。こういうときにリスコフの置換原則に反しているといえます。
ここでDisp関数はvirtual関数なんだからif文はいらないじゃないかという方は正しいです。
上の例のコードにリアリティを付けるとこんな感じになると思います。
void Display(Screen* p) { if (typeid(p) == typeid(MainScreen)) { PreFuncMain(); //当初は予定していませんでしたが、度重なる仕様変更でMainの前にはDispに関係ない処理がどうしても必要です。 //この処理はMainScreenクラスで実行するのはムリです。もしくは変更にコストがかかります。 static_cast<MainScreen* >(p)->Disp(); } else if (typeid(p) == typeid(SubScreen )) { static_cast<SubScreen * >(p)->Disp(); } }
■リスコフの置換原則を守るためには
リスコフの置換原則を守るためには、契約による設計を導入するということが
挙げられています。
契約による設計とは、クラスに不変条件、メソッドに事前条件と事後条件を決めることです。
もうこれを破れば継承関係を持たせるべきでないかもしれないという尺度になると思います。
アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技
- 作者: ロバート・C・マーチン,Robert C. Martin,瀬谷啓介
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2008/07/01
- メディア: 単行本
- 購入: 18人 クリック: 586回
- この商品を含むブログ (71件) を見る
SOLID原則:2つ目 オープン・クローズドの原則(OCP:Open-Closed Principle)
〇オープン・クローズドの原則(OCP:Open-Closed Principle)
1.拡張に対して開かれている
2.修正に対して閉じている
1つ目はモジュールの振る舞いを変更できること、2つ目はモジュールの振る舞いを拡張しても、
そのモジュールのソースコードやバイナリは影響を受けないことを示します。
簡単にいうと、ソースコードをいじらなくてもモジュールの振る舞いを変更できるということを
いっています。
OCPを守ってないケースから、守っているケースへ設計を修正していきながら
説明していきます。
例えば、GUIで複数画面を表示する設計を考えます。
画面の例:商品画面と商品の説明画面を表示する
このソフトウェアには以下のような画面追加が拡張として予測できます。
〇OCPに従わない設計
単純にクラス設計すると以下のような感じになり、コードはそれぞれに対して
createを呼ぶという感じになると思うのですが、これは予測した拡張に対して、
コードの修正が必要です。
//すべてのWindowを表示する void Display::Create() { //Picture部分の生成 picture.Create(); //Detaile部分の生成 detaile.Create(); }
//すべてのWindowを表示する void Display::Create() { //Picture部分の生成 picture.Create(); //Detaile部分の生成 detaile.Create(); //Review部分の生成 ← 画面追加の度に追加が必要 review.Create(); }
〇OCPに従った設計
WindowIFというInterfaceクラスを用意し、Displayはこのクラスに依存するようにします。
すると以下のようにコードすることができます。
このコードは今後どれだけWindowが追加拡張されても、修正は不要です。
//すべてのWindowを表示する void Display::Create() { typedef vector< WindowIF* > Container; for (Container::iterator p = container.begin(); p != container.end(); ++p) { (*p)->create() } }
最後にですがOCPの原則を適用するにはモジュールに対しての共通性と可変性の分析が必要になってきます。
分析した可変性に対してInterfaceのような抽象を用いることで、変更に対しての修正が発生しないように
するのですが、抽象自体の意味が後々変わった場合は修正が必要になります。
これを避けるためには、しっかりとした共通性分析を可変性分析が必要になってきます。
アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技
- 作者: ロバート・C・マーチン,Robert C. Martin,瀬谷啓介
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2008/07/01
- メディア: 単行本
- 購入: 18人 クリック: 586回
- この商品を含むブログ (71件) を見る
SOLID原則:1つ目 単一責任の原則(SRP:Single Responsibility Principle)
〇まえおき
ソフトウェアの開発に携わっっている方なら、ひどいコードの保守にあたって、
心身がとても疲れた経験はあると思うし、自分が作ったコードが人を大変な目に
会わせたことはあるんじゃないかと思います。
それらのコードは作ったときはヒドイコードではなかったんですが、
その後、数年をかけて徐々にひどくなっていったんじゃないかと思っています。
「ひどくならないようにオブジェクト指向というやつを利用したのに」
ということを言ったり聞いたりすることがありますが、中々に難しいので、
オブジェクト指向の有名な原則であるSOLID原則をまとめてみようかと思います。
ちなみに難しくなる理由なんですが、最初にソフトウェアを作ったときに
予期していなかった機能をむりやり追加することがほとんどです。
だから、変更を許容する設計が必要になるのですが、そのためのコツが
SOLID原則にまとめられていると思います。
〇単一責任の原則(SRP:Single Responsibility Principle)
クラスを変更する理由は1つ以上存在してはならない。
これはクラスは1つの役割を持つように作りましょうということです。1つの役割のみ持っているなら、
変更の理由も(1つの)役割の変更になるので、自然と上記の原則は守られます。
例えば、データを取得して、画面に表示するようなソフトを作りたいとします。
乱暴にやると次のように設計できます。
こうなると画面表示に変更が入るパターンは次の3つだと思います。
・画面表示の仕組みが変わった
例:今までは白黒だったけど、カラーにも対応してね
・計算のルールが変わった
例:法律、組織改革、社長の気紛れで、やり方が変わったから、
ソフトの方も変化に追従してね
・データベースが変わった
例:最近のはやりに合わせてRDB使おうよ。
このクラスをどれか1つの理由で変更するときは、他2つの役割に影響がないように
細心の注意をもって変更する必要があり、影響範囲の再試験が必要です。
(不具合だしてもいいなら、適当にやればいいんですが、不具合が許容される
ソフトウェアはありません。
金をとっていなくても不具合だすとクレームが来ます)
もし、元からクラス設計がSRPの原則に従っていたならば、面倒な影響範囲調査と
再試験の気苦労がなくなっていたはずです。
アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技
- 作者: ロバート・C・マーチン,Robert C. Martin,瀬谷啓介
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2008/07/01
- メディア: 単行本
- 購入: 18人 クリック: 586回
- この商品を含むブログ (71件) を見る
placement new(確保済みアドレスを指定する)について
placement newという通常のnewと異なるメモリアロケート方法があり、
通常のnewに比べて、以下の利点がある。
・メモリアロケートの時間が短縮される
・メモリアロケートの例外が発生しないように制御できる
■使い方
通常のnewと違い明示的にデストラクタを呼ぶ必要があります。
#include <cstdlib> #include <cstdio> #include <iostream> class Sample { char a[256]; }; int main() { //①メモリアロケート char* p_memory = new char[1024]; //②オブジェクトの初期化 Sample* p = new(p_memory) Sample(); //③オブジェクトの破壊 p->~Sample(); //④メモリ解放 sth::operator delete(p, p); return 0; }
最後に利点について補足します
・メモリアロケートの時間が短縮される
通常newの処理はざっくりいうと、使用できるメモリを探す→オブジェクトへ割り当る→コンストラクタで初期化
初めの、使用できるメモリを探す処理は結構ややこしく、メモリの断片化を防ぐために効率よくメモリ配置が行わ
れるため、時間がかかります。
確保済みアドレスを指定する場合は、①のメモリアロケートを事前に行うことで、
その後の処理時間を短縮できます。
・メモリアロケートの例外が発生しないように制御できる
①のメモリアロケートを事前に行い成功すると、②の処理で失敗することはない。
(失敗する理由がない、また、C++14から例外投げないことが明示されたと思います)
このことからシステム設計段階で各モジュールが利用できるメモリサイズを決めて置くと、
以下のことができます。
・システム起動時にメモリ確保
→普通成功する。万が一失敗した場合は、システム自体起動しない。
・実行中のnewの処理でplacement newを使用していると、実行時にエラーが
発生しない。
→メモリアロケート失敗時のめんどくさい例外処理を考えなくてよい
これを戦闘機のシステムのように実行時エラーが許されない(実行時エラー起こしたら墜落します)
ものに適用すると、リスクが減らせるから推奨されるようです。
確かF35から適用されたコーディングルールには適用されていたかと思う。
http://www.stroustrup.com/JSF-AV-rules.pdf