Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ソフトウェア設計原則【SOLID】を学ぶ #3 依存性逆転の原則

k-abe
September 27, 2023

ソフトウェア設計原則【SOLID】を学ぶ #3 依存性逆転の原則

2023/9/28(木)に実施する社内勉強会、X スペース 【連続講座】ソフトウェア設計原則【SOLID】を学ぶ #3 依存性逆転の原則の資料です。

※URLリンクが有効な資料はこちらを参照ください。
https://www.docswell.com/s/juraruming/5EN9ME-2023-09-28-065704

※X スペースの勉強会告知ページ
https://k-abe.connpass.com/event/294870/

k-abe

September 27, 2023
Tweet

More Decks by k-abe

Other Decks in Technology

Transcript

  1. 目次 自己紹介 SOLID について 依存性逆転の原則(dependency inversion principle )について テーマについて 原則違反の例

    原則に則った例 依存性注入 今回の設計所感 設計についてのディスカッション・質問 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 2
  2. 自己紹介 名前: 阿部 耕二(あべ こうじ) 所属: パーソルクロステクノロジー株式会社 第1 技術開発本部 第4 設計部 設計2

    課 医療機器の組込みソフトウェア開発。C 言語。 趣味: 宇宙開発(リーマンサットプロジェクト広報メンバー) LAPRAS ポートフォリオ: https://lapras.com/public/k-abe Twitter: @juraruming 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 4
  3. SOLID について 設計の5 原則の頭文字をとったもの。 S 単一責務の原則(Single Respomsibility Principle ) O

    オープン・クローズドの原則(Open Closed Principle ) L リスコフの置換原則(Liskov Substitution Principle ) I インターフェイス分離の原則(Interface Segregation Principle ) D 依存関係逆転の原則( Dependency Inversion Principle ) 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 5
  4. ▪ テーマの要件(続き): 起動時に前回設定値を装置に反映する。 設定値は持ち運べる(装置の設定状態をPC で見れる) ▪ テーマを実現する要素技術: • 前回設定値の反映 SRAM

    の設定値を電池でバックアップ • 設定値の持ち運び SD カード書込み・読込み 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 10
  5. ▪ 今後想定される要素技術の変更: • 前回設定値の反映 (現状)SRAM を電池にてバックアップ ⇒SPI 接続のシリアルRAM へ ⇒MRAM

    でバックアップ電池不要へ • 設定値の持ち運び (現状)SD カード書込み・読込み ⇒USB メモリへの変更 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 11
  6. ▪ 設定値読み・書きの上位・下位モジュールは? 【下位モジュール】 設定値を書く、読むを実現する具体的手段 • 前回設定値の反映 (現状)SRAM への書込み・読込み SPI 接続のシリアルRAM

    へ MRAM • 設定値の持ち運び (現状)SD カードへの書込み・読込み USB メモリへの書込み・読込み 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 13
  7. ▪ 設定値読み・書きの上位・下位モジュールは? 【上位モジュール】 抽象:設定値を書く・読むことを利用するモジュール • 前回設定値の反映 上位モジュールは起動時に抽象を呼び出し、設定値を反映する 上位モジュールは設定値変更をSRAM に書き込む •

    設定値の持ち運び 上位モジュールはSD カード書込みの画面メニューを選択・実行でSD カード書込みを行なう 上位モジュールはSD カード読込みの画面メニューを選択・実行でSD カード読込みを行なう 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 14
  8. • 前回設定値の反映 上位:Boot 起動時に前回設定値を反映し たい 抽象:設定値 設定値の読み・書きの抽象 下位:RAM, MRAM, SpiRAM

    実際に設定値を読み・書きす る手段を提供する 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ 15
  9. 前ページのクラス図のコード GitHub URL: no_dip_principle // Boot.cpp #include "Boot.h" // コンストラクタの実装

    Boot::Boot() { _settingValue = new SettingValueRam(); } Boot::~Boot() { delete _settingValue; } int Boot::readSettingValue() { return _settingValue->read(); } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 18
  10. // Boot.h #ifndef _H_BOOT_ #define _H_BOOT_ #include "SettingValueRam.h" class Boot

    { private: SettingValueRam* _settingValue; public: Boot(); ~Boot(); int readSettingValue(); }; #endif // _H_BOOT_ 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 19
  11. // SettingValueRam.cpp #include "SettingValueRam.h" // コンストラクタの実装 SettingValueRam::SettingValueRam() { } void

    SettingValueRam::write() { } int SettingValueRam::read() { return 123; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 20
  12. // SettingValueRam.h #ifndef _H_SETTINGVALUERAM_ #define _H_SETTINGVALUERAM_ class SettingValueRam { private:

    public: SettingValueRam(); void write(); int read(); }; #endif // _H_SETTINGVALUERAM_ 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 21
  13. #include <iostream> using namespace std; #include "Boot.h" int main() {

    Boot* boot = new Boot(); cout << "SettingValue = " << boot->readSettingValue() << endl; delete boot; return 0; } 実行結果 $ ./no_dip_principle.app SettingValue = 123 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 22
  14. コードが汚れる例 GitHub URL: no_dip_principle_dirty // Boot.cpp #include "Boot.h" // コンストラクタの実装

    Boot::Boot() { if (settingValueSelect) { _settingValue = new SettingValueRam(); } else { _settingValueRamFake = new SettingValueRamFake(); } } Boot::~Boot() { if (settingValueSelect) { delete _settingValue; } else { delete _settingValueRamFake; } } int Boot::readSettingValue() { if (settingValueSelect) { return _settingValue->read(); } else { return _settingValueRamFake->read(); } } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 26
  15. // Boot.h #ifndef _H_BOOT_ #define _H_BOOT_ #include "SettingValueRam.h" #include "SettingValueRamFake.h"

    class Boot { private: SettingValueRam* _settingValue; SettingValueRamFake* _settingValueRamFake; public: int settingValueSelect = 0; Boot(); ~Boot(); int readSettingValue(); }; #endif // _H_BOOT_ 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 27
  16. // SettingValueRamFake.cpp #include "SettingValueRamFake.h" // コンストラクタの実装 SettingValueRamFake::SettingValueRamFake() { } void

    SettingValueRamFake::write() { } int SettingValueRamFake::read() { return 456; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 28
  17. // SettingValueRamFake.h #ifndef _H_SETTINGVALUERAMFAKE_ #define _H_SETTINGVALUERAMFAKE_ class SettingValueRamFake { private:

    public: SettingValueRamFake(); void write(); int read(); }; #endif // _H_SETTINGVALUERAMFAKE_ 実行結果 $ ./no_dip_principle_dirty.app SettingValue = 456 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 29
  18. 上位・下位も抽象に依存する 前ページのクラス図を実装する。 GitHub URL: dip_principle ポイント: 抽象はC++ の仮想関数でInterface のように使う(ISettingValue.h) 下位モジュールはISettingValue

    を使う(SettingValueRam.h) 上位モジュールは下位モジュールではなく、ISettingValue をメンバ 変数として持つ 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 32
  19. // ISettingValue.h #ifndef _H_ISETTINGVALUE_ #define _H_ISETTINGVALUE_ #include <iostream> using namespace

    std; class ISettingValue { public: virtual void write() = 0; virtual int read() = 0; // 仮想デストラクタ virtual ~ISettingValue(){ cout << "ISettingValue destructor" << endl; } }; #endif // _H_ISETTINGVALUE_ 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 33
  20. // SettingValueRam.h #ifndef _H_SETTINGVALUERAM_ #define _H_SETTINGVALUERAM_ #include "ISettingValue.h" class SettingValueRam

    : public ISettingValue { private: public: SettingValueRam(); ~SettingValueRam(); void write(); int read(); }; #endif // _H_SETTINGVALUERAM_ 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 34
  21. // Boot.h #ifndef _H_BOOT_ #define _H_BOOT_ #include "ISettingValue.h" class Boot

    { private: ISettingValue* _settingValue; public: Boot(); ~Boot(); int readSettingValue(); }; #endif // _H_BOOT_ 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 35
  22. // Boot.cpp #include "Boot.h" #include "SettingValueRam.h" #include <iostream> using namespace

    std; // コンストラクタの実装 Boot::Boot() { cout << "Boot constructor" << endl; _settingValue = new SettingValueRam(); } Boot::~Boot() { cout << "Boot destructor" << endl; delete _settingValue; } int Boot::readSettingValue() { return _settingValue->read(); } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 36
  23. 実行結果 $ ./dip_principle.app Boot constructor SettingValueRam constructor SettingValue = 123

    Boot destructor SettingValueRam decstructor ISettingValue destructor 余計なプリント文が出力されているが、実行結果は原則違反コードの 時と同じ。 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 37
  24. // Boot.h #include "Boot.h" #include "SettingValueRam.h" #include <iostream> using namespace

    std; // コンストラクタの実装 Boot::Boot() { cout << "Boot constructor" << endl; _settingValue = new SettingValueRam(); // ★ ここで下位モジュールに依存している } Boot::~Boot() { cout << "Boot destructor" << endl; delete _settingValue; } int Boot::readSettingValue() { return _settingValue->read(); } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 39
  25. // Boot.cpp #include "Boot.h" //#include "SettingValueRam.h" #include "Factories.h" #include <iostream>

    using namespace std; // コンストラクタの実装 Boot::Boot() { cout << "Boot constructor" << endl; _settingValue = Factories::CreateSettingValue(); } Boot::~Boot() { cout << "Boot destructor" << endl; delete _settingValue; } int Boot::readSettingValue() { return _settingValue->read(); } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 41
  26. // Factories.h #ifndef _H_FACTORIES_ #define _H_FACTORIES_ #include "ISettingValue.h" #include "SettingValueRam.h"

    class Factories { public: static ISettingValue* CreateSettingValue(); }; #endif // _H_FACTORIES_ 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 42
  27. // Factories.cpp #include "Factories.h" #include "ISettingValue.h" #include "SettingValueRam.h" #include "SettingValueRamFake.h"

    ISettingValue* Factories::CreateSettingValue() { return new SettingValueRam(); // return new SettingValueRamFake(); } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 43
  28. 実行結果 $ ./dip_principle_factories.app SettingValue = 123 ファクトリで生成する下位モジュールをSettingValueRamFake に切り 替えた場合 実行結果

    $ ./dip_principle_factories.app SettingValue = 456 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 44
  29. 下位モジュールSettingValueRamFake の実装 // SettingValueRamFake.cpp #include "SettingValueRamFake.h" #include <iostream> using namespace

    std; // コンストラクタの実装 SettingValueRamFake::SettingValueRamFake() { cout << "SettingValueRamFake constructor" << endl; } SettingValueRamFake::~SettingValueRamFake() { cout << "SettingValueRamFake decstructor" << endl; } void SettingValueRamFake::write() { } int SettingValueRamFake::read() { return 456; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 45
  30. // Boot.h #ifndef _H_BOOT_ #define _H_BOOT_ #include "ISettingValue.h" class Boot

    { private: ISettingValue* _settingValue; public: Boot(ISettingValue* settingValue); // ★ ここに注目!!! ~Boot(); int readSettingValue(); }; #endif // _H_BOOT_ 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 47
  31. // Boot.cpp #include "Boot.h" //#include "SettingValueRam.h" //#include "Factories.h" #include "ISettingValue.h"

    #include <iostream> using namespace std; // コンストラクタの実装 Boot::Boot(ISettingValue* settingValue) { cout << "Boot constructor" << endl; // _settingValue = Factories::CreateSettingValue(); _settingValue = settingValue; } Boot::~Boot() { cout << "Boot destructor" << endl; delete _settingValue; } int Boot::readSettingValue() { return _settingValue->read(); } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 48
  32. GitHub: dip_and_di_test_easy_env // SettingValueValidation.cpp 下位モジュールが読み出した値の検証 #include "SettingValueValidation.h" #include "ISettingValue.h" #include

    <iostream> using namespace std; // コンストラクタの実装 SettingValueValidation::SettingValueValidation(ISettingValue* settingValue) { cout << "SettingValueValidation constructor" << endl; _settingValue = settingValue; } SettingValueValidation::~SettingValueValidation() { cout << "SettingValueValidation destructor" << endl; delete _settingValue; } bool SettingValueValidation::validate() { int value = _settingValue->read(); if (value >= 100) return true; return false; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 51
  33. // SettingValueValidation.h #ifndef _H_SETTINGVALUEVALIDATION_ #define _H_SETTINGVALUEVALIDATION_ #include "ISettingValue.h" class SettingValueValidation

    { private: ISettingValue* _settingValue; public: SettingValueValidation(ISettingValue* settingValue); ~SettingValueValidation(); bool validate(); }; #endif // _H_SETTINGVALUEVALIDATION_ 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 52
  34. // SettingValueMock.cpp 下位モジュールの設定値読み・書きをモックする #include "SettingValueMock.h" #include <iostream> using namespace std;

    // コンストラクタの実装 SettingValueMock::SettingValueMock() { cout << "SettingValueMock constructor" << endl; } SettingValueMock::~SettingValueMock() { cout << "SettingValueMock decstructor" << endl; } void SettingValueMock::write() { } int SettingValueMock::read() { return 100; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 53
  35. // SettingValueExampleTest.cpp テスト #include "CppUTest/TestHarness.h" #include <iostream> using namespace std;

    #include "SettingValueValidation.h" #include "SettingValueMock.h" #include "ISettingValue.h" TEST_GROUP(SettingValueExampleTest) { SettingValueValidation* settingValueValidation; void setup() { // モック(SettingValueMock) を設定値チェックのロジック(SettingValueValidation) に依存性注入している settingValueValidation = new SettingValueValidation(new SettingValueMock()); } void teardown() { delete settingValueValidation; } }; TEST(SettingValueExampleTest, SettingValueValid) { CHECK(settingValueValidation->validate()); } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 54
  36. // テストの結果表示 $ ./bin/dip_and_di_test_easy_env -v TEST(SettingValueExampleTest, SettingValueValid) - 0 ms

    OK (1 tests, 1 ran, 1 checks, 0 ignored, 0 filtered out, 1 ms) 依存性注入を使うことでテストコード、本番コードの切り替えも簡 単に行える 下位モジュールはデバイス制御に特化したコードにする( 今回の例で は設定値の読み出しのみ) 。下位モジュールを使う上位モジュールの ロジックをテストしやすくなる。 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 55
  37. 参考資料 1. オブジェクト指向の原則3:依存関係逆転の原則とインタフェース 分離の原則 Udemy の講座。作成者はピーコック アンダーソンさん。依存関係逆 転の原則以外のSOLID 原則の講座もあり。 2.

    オブジェクト指向習得のための5ステップ【SOLID 原則】 3. テスト駆動開発による組み込みプログラミング―C 言語とオブジェク ト指向で学ぶアジャイルな設計 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #3 依存性逆転の原則 58