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

ソフトウェア設計原則【SOLID】を学ぶ #4 開放閉鎖の原則

k-abe
October 25, 2023

ソフトウェア設計原則【SOLID】を学ぶ #4 開放閉鎖の原則

2023/10/26(木)に実施する社内勉強会、X スペース 【連続講座】ソフトウェア設計原則【SOLID】を学ぶ #4 開放閉鎖の原則の資料です。

勉強会概要:
https://k-abe.connpass.com/event/297777/

Xスペース(録音)
https://twitter.com/i/spaces/1nAKEaOpWzVKL

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

k-abe

October 25, 2023
Tweet

More Decks by k-abe

Other Decks in Technology

Transcript

  1. 自己紹介 名前: 阿部 耕二(あべ こうじ) 所属: パーソルクロステクノロジー株式会社 第1 技術開発本部 第4 設計部 設計2

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

    オープン・クローズドの原則( Open Closed Principle ) L リスコフの置換原則(Liskov Substitution Principle ) I インターフェイス分離の原則(Interface Segregation Principle ) D 依存関係逆転の原則(Dependency Inversion Principle ) 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 4
  3. 日常にある【開放閉鎖】の例 USB 日常にある【開放閉鎖の原則】の考えたときに一番はじめにイメージ したのがUSB 。参考資料3 の説明がわかりやすかったので引用する。 参考資料3: テスト駆動開発による組み込みプログラミング―C 言語とオ ブジェクト指向で学ぶアジャイルな設計

    11.1.2 オープン・クローズド の原則(OCP : Open Closed Principle ) 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 USB ポートは規格に準拠したデバイスなら何でも挿せるよう拡張 可能だが、新しいデバイスを受け付けるために変更する必要はな い。したがってUSB ポートのあるコンピュータは、デバイス追加 による機能の拡張に対して開いているが、デバイスがUSB に準拠 している限り機器構成の変更に対しては閉じていると言える。 “ “ 9
  4. 家庭のコンセント コンセント側はコンセントに挿される機器を気にしない。 何がさされても変わらずAC100V を供給する。 機器側はコンセントから供給されるAC100V を機器側の仕様に従って 利用する。 例)ヘアドライヤーは交流で動作するものが多い印象 例)AC アダプタは交流→

    直流へ変換し所望の電圧にしている(12V, 5V, 3.3V, その他) 【AC100V が供給される】ことに従えば様々な機器を動作させること ができる。 様々な機器を動作させるためにコンセントの動作に変更は発生しな い。 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 14
  5. マクドナルド ハッピーセッ ト リカちゃん こどもたちが大好きマクドナ ルド ハッピーセットの玩具 リ カちゃんに注目した。 全キャラの共通事項

    ポージングが同じ メイクが同じ ヘアスタイルはパーツ化し てあり変更可能 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ 15
  6. 日常にある【開放閉鎖】の例 日常にある【開放閉鎖】はつぎのような特徴がある。 拡張しやすくするためのポイントが設計され、用意されている。 例)Apple AC アダプタはプラグの取り外しで様々な国のコンセント に対応可能 例)リカちゃんであればポージング・メイク・ヘアスタイルのパー ツ化の共通事項 拡張はそれ以外の本体部分に影響しないように設計されている

    例)Apple AC アダプタはプラグがどの形状になっても本体は変化し ない 例)リカちゃんはどのバリエーションでもお人形のベースは変わら ない(と思う。製造者ではないので確証はない) 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 19
  7. 原則違反の例 前回の#3 依存性逆転の原則と同じテーマで説明をしていきます。 ▪ テーマ: 仮空の医療モニタ。患者の生体情報をモニタリングできる。 今回は設定値の書込み・読込み機能について注目する。 ▪ テーマの要件: 画面から装置の設定ができる

    設定値の例 表示エリア選択、表示テキストの名称・色、画面の輝度、音量、音 の種類、センサの校正値(ゲイン・オフセット)、その他いろいろ 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 21
  8. ▪ テーマの要件(続き): 今回は前回設定値の反映機能のみを対象とする。 起動時に前回設定値を装置に反映する。 設定値は持ち運べる(装置の設定状態をPC で見れる) ▪ テーマを実現する要素技術: • 前回設定値の反映

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

    でバックアップ電池不要へ • 設定値の持ち運び (現状)SD カード書込み・読込み ⇒USB メモリへの変更 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 23
  10. 説明のシナリオ: 現在はBoot クラスと SettingValueRAM クラスが 実装済みとする。 仕様変更(将来的にRAM が 生産中止になることがわか った)で次ロットからSPI

    の RAM に変更することになっ た、というストーリーとし ます。 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ 24
  11. GitHub URL: no_ocp_principle // Boot.cpp #include "Boot.h" // コンストラクタの実装 Boot::Boot()

    { _settingValue = new SettingValueRam(); } Boot::~Boot() { delete _settingValue; } int Boot::readSettingValue() { return _settingValue->read(); } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 25
  12. // 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 】を学ぶ #4 開放閉鎖の原則 26
  13. // SettingValueRam.cpp #include "SettingValueRam.h" // コンストラクタの実装 SettingValueRam::SettingValueRam() { } void

    SettingValueRam::write() { } int SettingValueRam::read() { return 123; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 27
  14. // SettingValueRam.h #ifndef _H_SETTINGVALUERAM_ #define _H_SETTINGVALUERAM_ class SettingValueRam { private:

    public: SettingValueRam(); void write(); int read(); }; #endif // _H_SETTINGVALUERAM_ 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 28
  15. #include <iostream> using namespace std; #include "Boot.h" int main() {

    Boot* boot = new Boot(); cout << "SettingValue = " << boot->readSettingValue() << endl; delete boot; return 0; } 実行結果 $ ./no_ocp_principle.app SettingValue = 123 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 29
  16. 既存の機能提供側のコードに新仕様を実装する場合 機能を使う側で選択する場合 no_ocp_principle_server_dirty //SettingValueRam.cpp #include "SettingValueRam.h" // コンストラクタの実装 SettingValueRam::SettingValueRam(eSettingValueRamType eSettingValueRamType)

    { _eSettingValueRamType = eSettingValueRamType; } void SettingValueRam::write() { if (_eSettingValueRamType == eSettingValueRamType::Ram) { // SettingValueRam のロジック } else { // SettingValueSpiRam のロジック } } int SettingValueRam::read() { if (_eSettingValueRamType == eSettingValueRamType::Ram) { // SettingValueRam のロジック return 123; } else { // SettingValueSpiRam のロジック return 456; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 32
  17. // SettingValueRam.h #ifndef _H_SETTINGVALUERAM_ #define _H_SETTINGVALUERAM_ enum class eSettingValueRamType {

    Ram, SpiRam, }; class SettingValueRam { private: eSettingValueRamType _eSettingValueRamType; public: SettingValueRam(eSettingValueRamType eSettingValueRamType); void write(); int read(); }; #endif // _H_SETTINGVALUERAM_ 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 33
  18. // Boot.cpp #include "Boot.h" // コンストラクタの実装 Boot::Boot() { // _settingValue

    = new SettingValueRam(eSettingValueRamType::Ram); _settingValue = new SettingValueRam(eSettingValueRamType::SpiRam); } Boot::~Boot() { delete _settingValue; } int Boot::readSettingValue() { return _settingValue->read(); } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 34
  19. // Boot.h #ifndef _H_BOOT_ #define _H_BOOT_ #include "SettingValueRam.h" class Boot

    { private: SettingValueRam* _settingValue; public: Boot(); ~Boot(); int readSettingValue(); }; #endif // _H_BOOT_ // 実行結果 $ ./no_ocp_principle_server_dirty.app SettingValue = 456 // eSettingValueRamType::SpiRam 選択時 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 35
  20. 機能を使う側で選択する場合 no_ocp_principle_client_dirty // Boot.cpp #include "Boot.h" // コンストラクタの実装 Boot::Boot() {

    if (settingValueSelect) { _settingValue = new SettingValueRam(); } else { _settingValueSpiRam = new SettingValueSpiRam(); } } Boot::~Boot() { if (settingValueSelect) { delete _settingValue; } else { delete _settingValueSpiRam; } } int Boot::readSettingValue() { if (settingValueSelect) { return _settingValue->read(); } else { return _settingValueSpiRam->read(); } } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 38
  21. // Boot.h #ifndef _H_BOOT_ #define _H_BOOT_ #include "SettingValueRam.h" #include "SettingValueSpiRam.h"

    class Boot { private: SettingValueRam* _settingValue; SettingValueSpiRam* _settingValueSpiRam; public: int settingValueSelect = 0; Boot(); ~Boot(); int readSettingValue(); }; #endif // _H_BOOT_ 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 39
  22. // SettingValueSpiRam.cpp #include "SettingValueSpiRam.h" // コンストラクタの実装 SettingValueSpiRam::SettingValueSpiRam() { } void

    SettingValueSpiRam::write() { } int SettingValueSpiRam::read() { return 456; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 40
  23. // SettingValueSpiRam.h #ifndef _H_SETTINGVALUESPIRAM_ #define _H_SETTINGVALUESPIRAM_ class SettingValueSpiRam { private:

    public: SettingValueSpiRam(); void write(); int read(); }; #endif // _H_SETTINGVALUESPIRAM_ 実行結果 $ ./no_ocp_principle_client_dirty.app SettingValue = 456 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 41
  24. 同一視できていない(SettingValueRam とSettingValueSpiRam を同じ ように扱えない)ために機能を利用する側でどちらの機能を利用する かの分岐が必要になる。 結果、修正に閉じることができなくなり、開放閉鎖の原則に則ること ができない。 同一視するための技術として次がある(他にもあるかも・・・)。 C++: virtual

    でInterface のような動きを実現する。 C: 関数ポインタで動作する関数を動的に切り替える C#: Interface, abstrct (抽象クラス) 今回はC++ でvirttual を使い、Interface のような動きでクラスを同一視 する。 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 44
  25. 原則に則った例 ocp_principle_factories // 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 】を学ぶ #4 開放閉鎖の原則 46
  26. // 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 】を学ぶ #4 開放閉鎖の原則 47
  27. // SettingValueSpiRam.h #ifndef _H_SETTINGVALUESPIRAM_ #define _H_SETTINGVALUESPIRAM_ #include "ISettingValue.h" class SettingValueSpiRam

    : public ISettingValue { private: public: SettingValueSpiRam(); ~SettingValueSpiRam(); void write(); int read(); }; #endif // _H_SETTINGVALUESPIRAM_ 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 48
  28. // 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 】を学ぶ #4 開放閉鎖の原則 49
  29. // Boot.cpp #include "Boot.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 】を学ぶ #4 開放閉鎖の原則 50
  30. // Factories.cpp #include "Factories.h" #include "ISettingValue.h" #include "SettingValueRam.h" #include "SettingValueSpiRam.h"

    ISettingValue* Factories::CreateSettingValue() { // return new SettingValueRam(); return new SettingValueSpiRam(); } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 51
  31. #ifndef _H_FACTORIES_ #define _H_FACTORIES_ #include "ISettingValue.h" class Factories { public:

    static ISettingValue* CreateSettingValue(); }; #endif // _H_FACTORIES_ // 実行結果 $ ./ocp_principle_factories.app SettingValue = 456 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #4 開放閉鎖の原則 52
  32. 参考資料 1. オブジェクト指向の原則1:単一責務の原則とオープンクローズド の原則 Udemy の講座。作成者はピーコック アンダーソンさん。オープンク ローズドの原則以外のSOLID 原則の講座もあり。 2.

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