Slide 1

Slide 1 text

【連続講座】ソフトウェア設計原則 【 SOLID 】を学ぶ #5 リスコフの置換原則(Liskov Substitution Principle ) パーソルクロステクノロジー株式会社 第1 技術開発本部 第4 設計部 設計2 課 阿部耕二

Slide 2

Slide 2 text

目次 自己紹介 SOLID について リスコフの置換原則(Liskov Substitution Principle )について 原則違反の例 サンプルコードについて 原則に則った例 今回の設計所感 設計についてのディスカッション・質問 参考資料 2

Slide 3

Slide 3 text

自己紹介 名前: 阿部 耕二(あべ こうじ) 所属: パーソルクロステクノロジー株式会社 第1 技術開発本部 第4 設計部 設計2 課 医療機器の組込みソフトウェア開発。C 言語。 趣味: 宇宙開発(リーマンサットプロジェクト広報メンバー) LAPRAS ポートフォリオ: https://lapras.com/public/k-abe Twitter: @juraruming 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 3

Slide 4

Slide 4 text

SOLID について 設計の5 原則の頭文字をとったもの。 S 単一責務の原則(Single Respomsibility Principle ) O オープン・クローズドの原則(Open Closed Principle L リスコフの置換原則( Liskov Substitution Principle ) I インターフェイス分離の原則(Interface Segregation Principle ) D 依存関係逆転の原則(Dependency Inversion Principle ) 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 4

Slide 5

Slide 5 text

SOLID 原則の重要性 凝集度が高くなる 他のモジュールと疎結合になる 各モジュールの目的が明確に分けられると、コード変更の際の影響 は局所化される。結果、テストしやすい設計になる。 上記の特徴を持つと再利用しやすいコードになる。 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 参考資料3 より引用 “ “ 5

Slide 6

Slide 6 text

リスコフの置換原則( Liskov Substitution Principle )について 出典: wikipedia サブタイプのオブジェクトはスーパータイプのオブジェクトの仕様 に従わなければならない 基底型オブジェクトを派生型オブジェクトで型安全に代替できるこ と ※この資料ではつぎの用語の定義とする スーパータイプ → 基底クラス・スーパークラス, サブタイプ → 派生 クラス・サブクラス 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 6

Slide 7

Slide 7 text

サンプルコードについて サンプルコードはこちらのGitHub リポジトリに格納している。 原則違反のサンプルコード サンプルコードコード内容 GitHub リポジトリのディレクトリ名 1. サブクラスに実装 no_lsp_add_impl_sub_class 2. 事前条件 ng_preconditions 3. 事後条件 ng_postconditions 4. 不変条件 ng_invaritants 5. 例外 ng_exception 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 7

Slide 8

Slide 8 text

原則に則ったサンプルコード サンプルコードコード内容 GitHub リポジトリのディレクトリ名 1. サブクラスに実装 ok_lsp_add_impl_sub_class 2. 事前条件 ok_preconditions 3. 事後条件 ok_postconditions 4. 不変条件 ok_invaritants 5. 例外 ok_exception 今回の設計所感 -> 継承で使われる意図がないことを明示する no_inheritance 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 8

Slide 9

Slide 9 text

サンプルコードの実行方法について make とC++ コンパイラが必要 GitHub リポジトリを自分のローカル環境にzip ダウンロードもしく はgit clone する。 確認したいサンプルコードのディレクトリでmake を実行する ディレクトリ名.app の実行ファイルができるので実行する 私の確認環境(MacOS, clang で確認) $ gcc -v Apple clang version 15.0.0 (clang-1500.0.40.1) Target: x86_64-apple-darwin22.6.0 Thread model: posix InstalledDir: /Library/Developer/CommandLineTools/usr/bin 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 9

Slide 10

Slide 10 text

原則違反の例 置換できない構造の例 1. サブクラスに実装している 2. 事前条件をサブクラスで強めている 3. 事後条件をサブクラスで弱めている 4. 不変条件をサブクラスで保持していない 5. サブクラスで独自の例外を投げている 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 10

Slide 11

Slide 11 text

1. サブクラスに実装している 長方形と正方形のサンプル コードを例にして説明す る。 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ 11

Slide 12

Slide 12 text

// Rectangle.hpp class Rectangle { protected: int width, height; public: Rectangle(const int width, const int height) : width(width), height(height) {} virtual void setWidth(const int w); virtual void setHeight(const int h); int getWidth() const ; int getHeight() const ; int area() const ; }; 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 12

Slide 13

Slide 13 text

// Square.hpp #include "Rectangle.hpp" class Square : public Rectangle { public: Square(int size) : Rectangle(size, size) {} void setWidth(const int w) override; void setHeight(const int h) override; }; 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 13

Slide 14

Slide 14 text

// Rectangle.cpp #include "Rectangle.hpp" void Rectangle::setWidth(const int w) { width = w; } void Rectangle::setHeight(const int h) { height = h; } int Rectangle::getWidth() const { return width; } int Rectangle::getHeight() const { return height; } int Rectangle::area() const { return width * height; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 14

Slide 15

Slide 15 text

// Square.cpp #include "Rectangle.hpp" #include "Square.hpp" void Square::setWidth(const int w) { width = height = w; } void Square::setHeight(const int h) { width = height = h; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 15

Slide 16

Slide 16 text

// no_lsp_add_impl_sub_class.cpp #include using namespace std; #include "Rectangle.hpp" #include "Square.hpp" void process(Rectangle& r) { int w = r.getWidth(); r.setHeight(10); std::cout << "expected area = " << (w * 10) << ", got " << r.area() << std::endl; } int main() { Rectangle r(5, 5); process(r); // expected area = 50, got 50 Square s(5); process(s); // expected area = 50, got 100, LSP violation! return 0; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 16

Slide 17

Slide 17 text

is-a 関係(〜は〜である)が破綻しているため置換できない 正方形は長方形である。 長方形は 4 つの角度が同じ 二組の対辺が同じ長さ という特徴がある。 正方形もこの特徴がある。正方形は前述の特徴に加えてすべての辺が 等しいという特徴がある。 正方形も長方形の一種という判断で、この例題では正方形は長方形を 継承した。 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 17

Slide 18

Slide 18 text

2. 事前条件をサブクラスで強めている 事前条件:メソッドの引数など 基底クラスの定義 // Parent.hpp #ifndef PARENT_HPP_ #define PARENT_HPP_ class Parent { public: virtual void doWork(int value); }; #endif // PARENT_HPP_ 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 18

Slide 19

Slide 19 text

基底クラスの実装 value < 0 か判定している。 // Parent.cpp #include #include "Parent.hpp" using namespace std; void Parent::doWork(int value) { if (value < 0) { throw std::invalid_argument("Parent requires value >= 0"); } // 作業をする cout << "Parent value = " << value << endl; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 19

Slide 20

Slide 20 text

サブクラスの定義 // Child.hpp #include "Parent.hpp" class Child : public Parent { public: void doWork(int value) override; }; 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 20

Slide 21

Slide 21 text

サブクラスの実装 value < 10 か判定している。基底クラスは value < 0 の判定だった。 事前条件を強化(条件が厳しく) しているため基底クラスとサブクラ スが置換不可になっている。 // Child.cpp #include #include "Child.hpp" using namespace std; void Child::doWork(int value) { if (value < 10) { throw std::invalid_argument("Child requires value >= 10"); // 事前条件を強化している } // 子クラス固有の作業をする cout << "Child value = " << value << endl; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 21

Slide 22

Slide 22 text

実行結果: 基底クラスと同じ引数をサブクラスに指定した場合 // ng_preconditions.cpp #include using namespace std; #include "Parent.hpp" #include "Child.hpp" int main() { Parent parent; parent.doWork(0); // Parent value = 0 Child child; child.doWork(10); // Child value = 10 // 例外発生 std::invalid_argument: Child requires value >= 10 child.doWork(0); return 0; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 22

Slide 23

Slide 23 text

3. 事後条件をサブクラスで弱めている 事後条件:メソッドの戻り値など 基底クラスの定義 // Parent.hpp class Parent { public: virtual int getValue(); }; 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 23

Slide 24

Slide 24 text

基底クラスの実装 // Parent.cpp #include #include "Parent.hpp" using namespace std; int Parent::getValue() { // 常に正の値を返す return 42; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 24

Slide 25

Slide 25 text

サブクラスの定義 // Child.hpp #include "Parent.hpp" class Child : public Parent { public: int getValue() override; }; 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 25

Slide 26

Slide 26 text

サブクラスの実装 // Child.cpp #include #include "Child.hpp" using namespace std; int Child::getValue() { int val = Parent::getValue(); // 事後条件を弱化している(負の値を返す可能性がある) return val - 50; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 26

Slide 27

Slide 27 text

実行結果: サブクラスが負の数を返している(基底クラスは正の数の戻 り値を想定している) int main() { int ret_val; Parent parent; ret_val = parent.getValue(); cout << "Parent return value = " << ret_val << endl; // Parent return value = 42 Child child; ret_val = child.getValue(); cout << "Child return value = " << ret_val << endl; // Child return value = -8 return 0; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 27

Slide 28

Slide 28 text

4. 不変条件をサブクラスで保持していない 基底クラスの条件をサブクラスで保持していない、条件を緩めるなど した場合 基底クラスの定義 // Parent.hpp class Parent { protected: int value; // 不変条件: 常に正の数 public: Parent(int val) : value(val >= 0 ? val : throw std::invalid_argument("value must be non-negative")) {} virtual void setValue(int val); }; 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 28

Slide 29

Slide 29 text

基底クラスの実装 // Parent.cpp #include #include "Parent.hpp" using namespace std; void Parent::setValue(int val) { if (val < 0) { throw std::invalid_argument("value must be non-negative"); } value = val; cout << "Parent value = " << value << endl; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 29

Slide 30

Slide 30 text

サブクラスの定義 // Child.hpp #include "Parent.hpp" class Child : public Parent { public: Child(int val) : Parent(val) {} void setValue(int val) override; }; 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 30

Slide 31

Slide 31 text

サブクラスの実装 #include #include "Child.hpp" using namespace std; void Child::setValue(int val) { if (val < -10) { // 親クラスよりも許容範囲を狭めている throw std::invalid_argument("Child requires value >= -10"); } // 基底クラスの不変条件「正の数」を破っている value = val; cout << "Child value = " << value << endl; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 31

Slide 32

Slide 32 text

実行結果: 基底クラスのメンバ変数は常に正の数というきまりをサブク ラスで破っている // ng_invaritants.cpp int main() { Parent parent(0); parent.setValue(1); // Parent value = 1 Child child(0); child.setValue(-10); // Child value = -10 return 0; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 32

Slide 33

Slide 33 text

5. サブクラスで独自の例外を投げている 基底クラスの定義 // Parent.hpp class Parent { public: virtual void doSomething(); }; 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 33

Slide 34

Slide 34 text

基底クラスの実装 // Parent.cpp #include #include "Parent.hpp" using namespace std; void Parent::doSomething() { cout << "Parent -> doSomething() execute." << endl; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 34

Slide 35

Slide 35 text

サブクラスの定義 // Child.hpp #include "Parent.hpp" class Child : public Parent { public: void doSomething() override; }; 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 35

Slide 36

Slide 36 text

サブクラスの実装 // Child.cpp #include #include "Child.hpp" using namespace std; void Child::doSomething() { throw std::runtime_error("Error occurred"); // 基底クラスが予期しない例外を投げる cout << "Child -> doSomething() execute." << endl; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 36

Slide 37

Slide 37 text

実行結果: 基底クラスは例外を投げないがサブクラスが例外を投げてい る // ng_exception.cpp int main() { Parent parent; parent.doSomething(); // Parent -> doSomething() execute. Child child; child.doSomething(); // 例外発生: std::runtime_error: Error occurred return 0; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 37

Slide 38

Slide 38 text

原則に則った例 つぎの原則違反を改善する。 1. サブクラスに実装している 2. 事前条件をサブクラスで強めている 3. 事後条件をサブクラスで弱めている 4. 不変条件をサブクラスで保持していない 5. サブクラスで独自の例外を投げている 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 38

Slide 39

Slide 39 text

1. 【原則違反改善例】サブクラスに実装している 長方形の基底クラスを継承した正方形サブクラスの件 正方形の幅もしくは高さを設定すると幅& 高さを変更するので長方形 の特徴を維持できなかった。 これを改善するには 正方形は長方形の継承をやめる 長方形、正方形の共通のインタフェースを定義する 共通のインターフェースを長方形、正方形で実装するようにする。 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 39

Slide 40

Slide 40 text

左: 変更前のクラス図 右: 変更後のクラス図 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 40

Slide 41

Slide 41 text

共通インターフェース: 長方形、正方形はこのクラスを実装する // Shape.hpp class Shape { public: virtual int area() const = 0; virtual int getWidth() const = 0; virtual int getHeight() const = 0; virtual void setWidth(const int w) = 0; virtual void setHeight(const int h) = 0; virtual ~Shape() {} }; 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 41

Slide 42

Slide 42 text

長方形の定義 // Rectangle.hpp #include "Shape.hpp" class Rectangle : public Shape { protected: int width, height; public: Rectangle(const int width, const int height) : width(width), height(height) {} int area() const override; int getWidth() const override; int getHeight() const override; void setWidth(const int w) override; void setHeight(const int h) override; }; 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 42

Slide 43

Slide 43 text

長方形の実装 // Rectangle.cpp #include "Rectangle.hpp" int Rectangle::area() const { return width * height; } int Rectangle::getWidth() const { return width; } int Rectangle::getHeight() const { return height; } void Rectangle::setWidth(const int w) { width = w; } void Rectangle::setHeight(const int h) { height = h; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 43

Slide 44

Slide 44 text

正方形の定義 // Square.hpp #include "Shape.hpp" class Square : public Shape { private: int size; public: Square(int size) : size(size) {} int area() const override; int getWidth() const override; int getHeight() const override; void setWidth(const int w) override; void setHeight(const int h) override; }; 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 44

Slide 45

Slide 45 text

正方形の実装 // Square.cpp #include "Square.hpp" int Square::area() const { return size * size; } int Square::getWidth() const { return size; } int Square::getHeight() const { return size; } void Square::setWidth(const int w) { size = w; } void Square::setHeight(const int h) { size = h; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 45

Slide 46

Slide 46 text

実行結果: 正方形・長方形は共通インターフェースを実装し、長方形・ 正方形それぞれ独自の実装を進めることができた。 // ok_lsp_add_impl_sub_class.cpp void process(Shape& shape) { int w = shape.area() / shape.getHeight(); shape.setHeight(10); std::cout << "expected area = " << (w * 10) << ", got " << shape.area() << std::endl; } int main() { Rectangle r(5, 5); process(r); // expected area = 50, got 50 Square s(5); process(s); // expected area = 100, got 100, LSP is not violation! return 0; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 46

Slide 47

Slide 47 text

2. 【原則違反改善例】事前条件をサブクラスで強めている サブクラスは基底クラスと同じ事前条件にするか、もしくは事前条 件を弱く(緩めれば)すれば置換可能。 // Child.cpp void Child::doWork(int value) { // if (value < 10) { // throw std::invalid_argument("Child requires value >= 10"); // 事前条件を強化している // } // 基底クラスの事前条件を維持 ( または下記を削除することで事前条件を緩める) Parent::doWork(value); // 子クラス固有の作業をする cout << "Child value = " << value << endl; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 47

Slide 48

Slide 48 text

3. 【原則違反改善例】事後条件をサブクラスで弱めている サブクラスは基底クラスと同じ事後条件にするか、もしくは事前条 件を強く(より大きな値に)すれば置換可能。 // Child.cpp int Child::getValue() { int val = Parent::getValue(); // LSP NG: 事後条件を弱化している(負の値を返す可能性がある) // return val - 50; // LSP OK: 事後条件を強化している(より大きな正の値を返す) return val + 10; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 48

Slide 49

Slide 49 text

4. 【原則違反改善例】不変条件をサブクラスで保持していない 基底クラスの不変条件を使えば置換可能 // Child.cpp void Child::setValue(int val) { // LSP NG: 親クラスよりも許容範囲を狭めている // if (val < -10) { // throw std::invalid_argument("Child requires value >= -10"); // } // // 基底クラスの不変条件「正の数」を破っている // value = val; // LSP OK: 親クラスの不変条件を保持する Parent::setValue(val); cout << "Child value = " << this->value << endl; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 49

Slide 50

Slide 50 text

5. 【原則違反改善例】サブクラスで独自の例外を投げている 基底クラスが予期しない例外をなげなければ置換可能 // Child.cpp void Child::setValue(int val) { void Child::doSomething() { // LSP NG: 基底クラスが予期しない例外を投げる // throw std::runtime_error("Error occurred"); // LSP OK: 基底クラスが予期しない例外は投げない cout << "Child -> doSomething() execute." << endl; } 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 50

Slide 51

Slide 51 text

今回の設計所感 1. 僕たちは何故リスコフの置換原則を破るのか? 2. リスコフの置換原則を破るとどんな未来が待っているか? 3. 継承以外の解決方法 4. 継承で使われる意図がないことを明示する 5. 継承は使ってはダメなのか? 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 51

Slide 52

Slide 52 text

1. 僕たちは何故リスコフの置換原則を破るのか? なぜリスコフの置換原則を破ってしまうのか、考えられる原因を推 測した。 似ているから基底クラスを継承し、サブクラスを独自に拡張する 基底クラスの機能を使いたいから継承し、サブクラスを独自に拡張 する 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 52

Slide 53

Slide 53 text

【似ているから基底クラスを継承する、基底クラスの機能を使いたい から】の背景にありそうなもの 基底クラスを再利用したい、共通化したいという思惑 基底クラスとこれからつくるクラスは似ていると勘違いする DRY 原則に反していると思ってしまう 開発対象が別物(違うシステム、違う装置)であれば素直にクラス を別にするという選択をした方が幸せになれそう。 違うシステム、違う装置はそれぞれ別の事情で変化する。 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 53

Slide 54

Slide 54 text

2. リスコフの置換原則を破るとどんな未来が待っているか? そもそも構造がおかしくなる(長方形を継承した正方形の例) クライアント(サブクラスを使う側)に条件分岐が発生する。 この場合は基底クラスを使う、こっちの場合はサブクラスを使う、 という感じ。 サブクラスが増えればクライアントの複雑度は増していく。 結果、修正に閉じることが出来なくてOpen closed 原則にも違反す る。 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 54

Slide 55

Slide 55 text

3. 継承以外の解決方法 継承を使わない解決方法として 抽象に実装する がひとつの解決方法である。 継承したサブクラスのみに実装を追加していくとクライアントコード に基底クラスとサブクラスの場合分けの条件分岐が発生する、だっ た。 クライアントコードからは使うクラスを同一視したい。これはOpen closed 原則にもでてきた抽象に実装するで解決できる。 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 55

Slide 56

Slide 56 text

長方形を継承した正方形の原 則違反コードの改善例を再掲 する 左: 変更前のクラス図 右: 変更後のクラス図 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ 56

Slide 57

Slide 57 text

4. 継承で使われる意図がないことを明示する 継承を使わないでクラスを追加したい、ということを仕組みで解決す ることはできないか? 例えばこんなケース 派生開発の案件 設計の経験が浅い後輩が追加機能を実装することになった 既存のコードに似ている機能の追加 先輩設計者であるあなたは継承を使って実装はしない方針にしたい という思惑がある 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 57

Slide 58

Slide 58 text

C++ であればfinal, C# であればsealed のキーワードで継承を禁止する ことができる。 クラス定義時にデフォルトfinal にしておくようにすれば(社内のコー ディング規約で決めるなど)継承によるリスコフ置換原則違反を防止 することができる。 設計者の意図(このクラスは継承することを前提にしていない)を明 示することもできる。 // Rectangle.hpp class Rectangle final { } Rectangle を継承するコードはコンパイルエラーになる。 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 58

Slide 59

Slide 59 text

5. 継承は使ってはダメなのか? 継承を使わないことを推奨するようなことばかり書いてきた。 新しい言語も継承の機能がなかったりする。 継承は使わないほうが良いのか? 継承が効力を発揮する場面はないのか? 継承の使い所についてつぎの動画でひとつの解を提示してくれてい る。 オブジェクト指向の原則2:リスコフの置換原則と継承以外の解決方 法 #48_ 共通要素をまとめる継承 #49_ 共通要素をまとめる継承の実装例 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 59

Slide 60

Slide 60 text

動画ではWindows Form アプリケーションのText のコントロール部品 のクラスを継承して拡張する例が紹介されていた。 Text に入力されていた文字数がxx 文字以上だったら入力文字の色を xx にする、みたいな動き アプリケーションの全体、複数画面で統一感のある挙動を設定できる などの効果がある。 継承も使いところによっては有効な場面があると思うので探していき たいと感じた。 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 60

Slide 61

Slide 61 text

継承の使いところとして個人的に良いかもしれないと思ったこと Windows Form のText の例のように画面の部品のクラスを継承して 拡張する サードパーティー製のライブラリを継承して自分たちの開発対象向 けに拡張する、みたいな場面 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 61

Slide 62

Slide 62 text

設計についてのディスカッション・質 問 自分以外の設計の視点が学びになると個人的に考えています。 ぜひぜひお気軽にフィードバックをよろしくお願いします こちらに学習の振り返りに使う目的でZenn のスクラップを用意しま した。 活用ください。 【SOLID 原則】#5 " リスコフの置換原則(Liskov Substitution Principle )" の勉強会後の振り返り 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 62

Slide 63

Slide 63 text

参考資料 1. オブジェクト指向の原則2:リスコフの置換原則と継承以外の解決 方法 Udemy の講座。作成者はピーコック アンダーソンさん。リスコフの 置換原則以外のSOLID 原則の講座もあり。 2. オブジェクト指向習得のための5ステップ【SOLID 原則】 3. テスト駆動開発による組み込みプログラミング―C 言語とオブジェク ト指向で学ぶアジャイルな設計 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 63

Slide 64

Slide 64 text

ご清聴ありがとうございました 【連続講座】ソフトウェア設計原則【SOLID 】を学ぶ #5 リスコフの置換原則 64