SlideShare a Scribd company logo
と
オブジェクト指向設計
Kisaragi
SOLID原則入門
知っておきたい
前提とする知識
● クラスを実装したことがある
● インターフェースの概念を多少知っている
● 継承の概念を多少知っている
● 簡単なC#が読める
世の中には様々な要素があり
要素の間には依存関係がある
クラスの依存関係の例
class Actor {
int hp, mp;
string name;
}
ActorはBattleが
なくても動作する
class Battle {
int turnCount;
Actor[] actors;
}
BattleはActorが
ないと動作しない
クラスの依存関係の例
class Actor {
int hp, mp;
string name;
}
ActorはBattleが
なくても動作する
BattleがActorに依存していると表現される
class Battle {
int turnCount;
Actor[] actors;
}
BattleはActorが
ないと動作しない
Actor Battle
適切でない設計が何を引き起こすのか
アンチ設計パターン①循環依存
ClassA ClassB
ClassCClassD
依存関係が循環している
アンチ設計パターン①循環依存
ClassA ClassB
ClassCClassD
修正
いずれかのクラス1つに修正を行う
アンチ設計パターン①循環依存
ClassA ClassB
ClassCClassD
修正
そのクラスに依存するクラスも影響を受ける
修正
アンチ設計パターン①循環依存
ClassA ClassB
ClassCClassD
修正
更にそのクラスに依存するクラスにも伝播する
修正
修正
アンチ設計パターン①循環依存
ClassA ClassB
ClassCClassD
修正
ついには循環する全てのクラスの修正が必要に
修正
修正修正
アンチ設計パターン①循環依存
ClassA ClassB
ClassCClassD
修正
更にClassAはClassDに依存しているため再修正
修正
修正修正
修正
アンチ設計パターン①循環依存
ClassA ClassB
ClassCClassD
修正
全体の整合性が取れるまで循環的に修正を繰り返す
修正
修正修正
修正 修正
修正修正
修正 修正
アンチ設計パターン①循環依存
ClassA ClassB
ClassCClassD
修正
循環するクラスを参照するクラスも修正に追われる
修正
修正修正
修正 修正
修正修正
修正 修正
ClassE
修正
修正
修正
ClassF
修正
修正
アンチ設計パターン①循環依存
ClassA ClassB
ClassCClassD
修正
ClassAを修正するだけのはずが影響が広範になってしまう
修正
修正修正
修正 修正
修正修正
修正 修正
ClassE
修正
修正
修正
ClassF
修正
修正
アンチ設計パターン②不安定な要素への依存
ClassA
不安定
ClassB
安定
ClassC
安定
ClassD
不安定
不安定 変更が起こりやすい:=
アンチ設計パターン②不安定な要素への依存
ClassA
不安定
ClassB
安定
ClassC
安定
ClassD
不安定
修正
不安定な要素に変更があるたびに
アンチ設計パターン②不安定な要素への依存
ClassA
不安定
ClassB
安定
ClassC
安定
ClassD
不安定
修正
依存している要素に影響が出てしまう
修正 修正 修正
設計しましょう
もし依存の方向を自由にできるのなら......
循環依存は解消できる
ClassA ClassB
ClassCClassD
×
×
もし依存の方向を自由にできるのなら......
依存先を安定した要素にできる
ClassA
不安定
ClassB
安定
ClassC
安定
ClassD
不安定
不安定な要素に変更があっても
他の要素が影響を受けない
×
依存方向の逆転 できます
インターフェースを用いた依存性の逆転
class Actor {
int hp, mp;
string name;
}
class Battle {
int turnCount;
Actor[] actors;
}
インターフェースを用いた依存性の逆転
class Actor {
int hp, mp;
string name;
}
class Battle {
int turnCount;
Actor[] actors;
}
interface IActor {
void Act();
}
Battle側がインターフェースを用意
インターフェースを用いた依存性の逆転
class Actor: IActor {
int hp, mp;
string name;
}
class Battle {
int turnCount;
IActor[] actors;
}
interface IActor {
void Act();
}
×
インターフェースを
仲介する
インターフェースを用いた依存性の逆転
class Actor: IActor {
int hp, mp;
string name;
}
class Battle {
int turnCount;
IActor[] actors;
}
interface IActor {
void Act();
}
Actor
Battle
×
矢印の向きが逆転!
インターフェースを用いた依存性の逆転
class Actor: IActor {
int hp, mp;
string name;
}
class Battle {
int turnCount;
IActor[] actors;
}
interface IActor {
void Act();
}
Actor
Battle
矢印の向きが逆転!
×
インターフェースを用いることで
依存方向を任意に変更できる
依存方向をどう決定するのか
変換処理
モジュールには上下の関係がある
最終的な入出力に近いモジュールほど下位
入力
コアロジック
出力
上位
下位
下位モジュールは変更要請が多く不安定
入力 出力
コントローラーから入力したい!
コンソールからも入力したい!
入力フォームの
仕様を変えたい!
UI描画のデザインを
変えたい!
多言語対応したい!
入力ログから入力を
再現したい!
ファイル出力したい!
上位モジュールは変更要請が少なく安定
コアロジック
例:
入力値を3乗する
ロジック
3乗の値を返せればそれでいい
コアロジックへの新たな要請はまれ 安定
安定したモジュール(=上位)
に依存しよう
変換処理
上位のモジュールに依存せよ
入力
コアロジック
出力
上位
下位
入力や出力が変更されても
上位モジュールが影響を受けない形になった
変換処理
依存関係には罠がある
入力
コアロジック
出力
上位
下位
処理の流れと理想的な依存方向は
一致しないことに注意
処理の流れ
変換処理: IDataInput
依存関係に注意した実装の一例
入力: IInput
コアロジック
出力: IOutput
上位
下位
interface IInput
interface
IDataInput
interface
IOutput
Dependency Inversion
上位モジュールは
下位モジュールに依存してはならない
どちらも抽象に依存するべきである
依存性逆転の原則
変換処理: IDataInput
すべてが抽象に依存していることがわかる
入力: IInput
コアロジック
出力: IOutput
上位
下位
interface IInput
interface
IDataInput
interface
IOutput
抽象
抽象
抽象
入力モック: IDataInput
すべてが抽象に依存するとテストしやすい
コアロジック
出力モック: IOutput
interface
IDataInput
interface
IOutput
抽象 抽象
外部のコンポーネントを
テスト用のモックに置き換えられる
モック: 簡易にinterfaceを実装するテスト専用のコンポーネント
テスト対象
D
I
L
O
S
依存性逆転の原則
インターフェース分離の原則
リスコフの置換原則
オープンクローズド原則
単一責任の原則
Single Responsibility
Open-Closed
Liscov Substitution
Interface Segregation
Dependency Inversion
上位が下位に依存してはならない
どちらも抽象に依存するべきである
修正作業はどうなるか
変換処理: IDataInput
下位モジュールを修正する
入力2: IInput
コアロジック
出力: IOutput
上位
下位
interface IInput
interface
IDataInput
interface
IOutput
実装を修正した!
変換処理: IDataInput
下位モジュールを修正する
入力2: IInput
コアロジック
出力: IOutput
上位
下位
interface IInput
interface
IDataInput
interface
IOutput
実装を修正した!
他のモジュールは修正不要!
変換処理: IDataInput
上位モジュールを修正する
入力: IInput
新ロジックα
出力: IOutput
上位
下位
interface IInput
interface
IDataInput
interface
IOutput
変換処理: IDataInput
上位モジュールを修正する
入力: IInput
新ロジックα
出力: IOutput
上位
下位
interface IInput
interface
IDataInput
interface
IOutput
interfaceが変更されない限り
他のモジュールは修正不要!
修正が「閉じられている」
機能追加作業はどうなるか
出力モジュールを追加する
コアロジック
標準出力: IOutput
interface
IDataInput
interface
IOutput
出力モジュールを追加する
コアロジック
標準出力: IOutput
interface
IDataInput
interface
IOutput
ファイル出力: IOutput
音声出力: IOutput
画像出力: IOutput
出力モジュールを追加する
コアロジック
標準出力: IOutput
interface
IDataInput
interface
IOutput
ファイル出力: IOutput
音声出力: IOutput
画像出力: IOutput プラグインとして拡張できる!
既存のコードの修正作業なく機能を追加できる
拡張が「開かれている」
拡張について開いていなければならず
修正について閉じていなければならない
オープンクローズド原則
Open-Closed
D
I
L
O
S
依存性逆転の原則
インターフェース分離の原則
リスコフの置換原則
オープンクローズド原則
単一責任の原則
Single Responsibility
Open-Closed
Liscov Substitution
Interface Segregation
Dependency Inversion
上位が下位に依存してはならない
どちらも抽象に依存するべきである
拡張に対して開いており
修正に対して閉じているべきである
クラスの関係性を
考えられるようになった
クラス自体の作り方を考える
このようなクラスに修正の作業をするケースを考える
class Player {
int hp, mp;
Vector3 position;
bool jumpable;
void Damaged() { }
void Move() { }
void Jump() { }
}
ジャンプに関する制御の修正
1. hp, mpの動作が正しいままか
2. 位置のロジックは壊れていないか
3. ジャンプの挙動は修正されたか
余分なチェックが必要!
このクラスが何をしているクラスか考える
class Player {
int hp, mp; // パラメータ管理
Vector3 position; // 位置管理
bool jumpable; // ジャンプフラグ管理
void Damaged() { } // パラメータ制御
void Move() { } // 位置制御
void Jump() { } // ジャンプ制御
}
1. パラメータ管理
2. パラメータ制御
3. 位置管理
4. 位置制御
5. ジャンプ管理
6. ジャンプ制御
7. 機能の接続
少なくとも役割を7つ持っている
大まかな役割ごとに分類する
class Player {
int hp, mp; // パラメータ管理: パラメータ
Vector3 position; // 位置管理: 位置
bool jumpable; // ジャンプフラグ管理: 位置
void Damaged() { } // パラメータ制御: パラメータ
void Move() { } // 位置制御: 位置
void Jump() { } // ジャンプ制御: 位置
}
パラメータと位置に分けられそう
2つのクラスに分割できる
class PlayerParam {
int hp, mp;
void Damaged() { }
}
class PlayerMove {
Vector3 position;
void Move() { }
bool jumpable;
void Jump() { }
}
1. パラメータ管理
2. パラメータ制御
1. 位置管理
2. 位置制御
3. ジャンプフラグ管理
4. ジャンプ制御
ジャンプと移動で分割できる
class PlayerParam {
int hp, mp;
void Damaged() { }
}
class PlayerMove {
Vector3 position;
void Move() { }
}
1. パラメータ管理
2. パラメータ制御
1. ジャンプフラグ管理
2. ジャンプ制御
class PlayerJump {
bool jumpable;
void Jump() { }
}
1. 位置管理
2. 位置制御
管理と制御も分けられる
class PlayerBehavior {
void Damaged() { }
}
class PlayerMove {
void Move() { }
}
class PlayerJump {
void Jump() { }
}
class PlayerParam {
int hp, mp;
}
class JumpFlag {
bool jumpable;
}
class PlayerPos {
Vector3 position;
}
パラメータ管理
ジャンプ制御
位置制御
パラメータ制御
ジャンプフラグ管理
位置管理
class PlayerJump {
void Jump() { }
}
Player
Behaviour
Player
Move
Player
Pos
Player
Param
JumpFlag
ジャンプ制御の修正で
位置やパラメータを
考える必要がなくなった
クラスのコードを変更する理由は
ただ1つであるべき
単一責任の原則
Single Responsibility
class Player {
int hp, mp; // パラメータ管理
Vector3 position; // 位置管理
bool jumpable; // ジャンプフラグ管理
void Damaged() { } // パラメータ制御
void Move() { } // 位置制御
void Jump() { } // ジャンプ制御
}
1. パラメータ管理
2. パラメータ制御
3. 位置管理
4. 位置制御
5. ジャンプ管理
6. ジャンプ制御
7. 機能の接続
変更する理由が7つある
単一責任の原則に違反した状態
class PlayerBehavior {
void Damaged() { }
}
class PlayerMove {
void Move() { }
}
class PlayerJump {
void Jump() { }
}
class PlayerParam {
int hp, mp;
}
class JumpFlag {
bool jumpable;
}
class PlayerPos {
Vector3 position;
}
パラメータ管理
ジャンプ制御
位置制御
パラメータ制御
ジャンプフラグ管理
位置管理
それぞれクラスを変更する理由は1つとなり
単一責任の原則に即した状態
1つのクラスにメソッドや変数が1つという意味ではない
class JumpFlag {
int jumpWait; // ジャンプの硬直フレームを管理
// ジャンプのフラグに関する判定のロジック
public bool Jumpable {
get {
if (jumpWait > 0) return false;
return true;
}
}
// ジャンプのフラグ操作のためのメソッド
public void Update() => jumpWait--;
public void OnJump() => jumpWait = WaitFrame;
}
LCOM - Lack of Cohesion in Methods
すべてのインスタンス変数は
すべてのメソッドで用いられるべき
LCOMを用いたクラスの設計の検討
class PlayerParam {
int hp;
int mp;
public bool Alive => hp > 0; // mp を用いていないメソッド(プロパティ)
public void Recover() {
hp = HpMax;
mp = MpMax;
}
}
LCOMを用いたクラスの設計の検討
class PlayerParam {
HP hp;
int mp;
public void Recover() {
hp.Recover();
mp = MpMax;
}
}
LCOMの考え方に即した設計になった!
class HP { // HPの処理を切り出す
int hp;
public bool Alive => hp > 0;
public void Recover() => hp = HpMax;
}
プリミティブ型をラップしよう
class PlayerParam {
HP hp;
int mp; // PlayerはMPを持つ
void ReduceMp(int val) {
if (val <= 0) throw new Exception();
mp = Math.Max(0, mp - val);
}
// ...
}
MPを用いるクラスが
複数ある
class Enemy {
int mp; // EnemyはMPを持つ
void ReduceMp(int val) {
if (val <= 0) throw new Exception();
mp = Math.Max(0, mp - val);
}
// ...
}
class Weapon {
int mp; // WeaponはMPを持つ
void ReduceMp(int val) {
if (val <= 0) throw new Exception();
mp = Math.Max(0, mp - val);
}
// ...
}
class PlayerParam {
HP hp;
int mp; // PlayerはMPを持つ
void ReduceMp(int val) {
if (val <= 0) throw new Exception();
mp = Math.Max(0, mp - val);
}
// ...
}
MPに対するロジックが
重複する
class Enemy {
int mp; // EnemyはMPを持つ
void ReduceMp(int val) {
if (val <= 0) throw new Exception();
mp = Math.Max(0, mp - val);
}
// ...
}
class Weapon {
int mp; // WeaponはMPを持つ
void ReduceMp(int val) {
if (val <= 0) throw new Exception();
mp = Math.Max(0, mp - val);
}
// ...
}
class MP {
int mp;
public void ReduceMp(int val) {
if (val <= 0) throw new Exception();
mp = Math.Max(0, mp - val);
}
}
プリミティブ型をラップしたMPクラスを実装する
MPの処理はMPに任せる
class PlayerParam {
HP hp;
MP mp; // PlayerはMPを持つ
void ReduceMp(int val)
=> mp.ReduceMp(val);
// ...
}
MPに対する処理が
隠蔽され共通化した
class Enemy {
MP mp; // EnemyはMPを持つ
void ReduceMp(int val)
=> mp.ReduceMp(val);
// ...
}
class Weapon {
MP mp; // WeaponはMPを持つ
void ReduceMp(int val)
=> mp.ReduceMp(val);
// ...
}
class MP {
int mp; // 値のgetter, setterが存在しないクラスが理想
public void Reduce(int val) {
if (val <= 0) throw new Exception();
mp = Math.Max(0, mp - val);
}
public void Recover(int val) {
if (val <= 0) throw new Exception();
mp = Math.Min(MpMax, mp + val);
}
public void RecoverFull() => mp = MpMax;
public string Display() => mp.ToString();
}
クラス自体がドキュメントになる
MPクラスを見れば
期待されるふるまいがわかる
class MP {
int mp;
public void Reduce(int val) {
if (val <= 0) throw new Exception();
mp = Math.Max(0, mp - val);
}
public void Recover(int val) {
if (val <= 0) throw new Exception();
mp = Math.Min(MpMax, mp + val);
}
public void RecoverFull() => mp = MpMax;
public string Display() => mp.ToString();
}
クラス自体がドキュメントになる
内部を知らなくても
公開シグネチャから
できることがわかる
カプセル化
D
I
L
O
S
依存性逆転の原則
インターフェース分離の原則
リスコフの置換原則
オープンクローズド原則
単一責任の原則
Single Responsibility
Open-Closed
Liscov Substitution
Interface Segregation
Dependency Inversion
上位が下位に依存してはならない
どちらも抽象に依存するべきである
拡張に対して開いており
修正に対して閉じているべきである
クラスを変更する理由は
ただ1つであるべきである
インターフェースの接続を考える
Doorの機能を呼び出すクラスを考える
class Door {
public void Open();
public void Close();
public void OnButtonPushed()
=> Open();
public void OnTimeout()
=> Close();
}
class ButtonEvent {
Door door;
// ボタンを押したらDoorが開く
void OnPushed()
=> door.OnButtonPushed();
}
class TimerEvent {
Door door;
// 時間経過でDoorが閉じる
void OnTimeout()
=> door.OnTimeout();
}
Doorの機能を呼び出すクラスを考える
class Door {
public void Open();
public void Close();
public void OnButtonPushed()
=> Open();
public void OnTimeout()
=> Close();
}
class ButtonEvent {
Door door;
// ボタンを押したらDoorが開く
void OnPushed()
=> door.OnButtonPushed();
}
class TimerEvent {
Door door;
// 時間経過でDoorが閉じる
void OnTimeout()
=> door.OnTimeout();
}
具体への依存
具体への依存
Doorの機能を呼び出すクラスを考える
class ButtonEvent {
IPushedTimeout pushed;
void OnPushed()
=> pushed.OnButtonPushed();
}
class TimerEvent {
IPushedTimeout timeout;
void OnTimeout()
=> timeout.OnTimeout();
}
抽象への依存になり
IPushedTimeoutを実装すれば
置き換えられるようになった
interface IPushedTimeout {
public void OnButtonPushed();
public void OnTimeout();
}
class Door: IPushedTimeout {
public void Open();
public void Close();
public void OnButtonPushed()
=> Open();
public void OnTimeout()
=> Close();
}
Buttonの入力を受け取るクラスを新しく実装する
class GimmickSwitch: IPushedTimeout {
public void OnButtonPushed()
=> StartGimmick();
// インターフェースに実装を強制される
public void OnTimeout()
=> throw new NotSupportedException();
}
ButtonEventに必要ない
OnTimeout()を実装しなくては
ならなくなってしまった
interface IPushedTimeout {
public void OnButtonPushed();
public void OnTimeout();
}
class ButtonEvent {
IPushedTimeout pushed;
void OnPushed()
=> pushed.OnButtonPushed();
}
Buttonの入力を受け取るコンポーネントを実装する
class GimmickSwitch: IPushedTimeout {
public void OnButtonPushed()
=> StartGimmick();
// インターフェースに実装を強制される
public void OnTimeout()
=> throw new NotSupportedException();
}
さらにTimerEventから利用すると
IPushedTimeoutを実装しているのに
例外が投げられてしまう
interface IPushedTimeout {
public void OnButtonPushed();
public void OnTimeout();
}
class TimerEvent {
IPushedTimeout timeout;
void OnTimeout()
=> timeout.OnTimeout();
}
クライアントは
使用しないメソッドへの
依存を強制されない
インターフェース分離の原則Interface Segregation
Buttonの入力を受け取るコンポーネントを実装する
class GimmickSwitch: IPushedTimeout {
public void OnButtonPushed()
=> StartGimmick();
// インターフェースに実装を強制される
public void OnTimeout()
=> throw new NotSupportedException();
}
インターフェース分離の原則に
反した設計 interface IPushedTimeout {
public void OnButtonPushed();
public void OnTimeout();
}
Buttonの入力を受け取るコンポーネントを実装する
class GimmickSwitch: IPushed {
public void OnButtonPushed()
=> StartGimmick();
// OnTimeout()は実装する必要がない
}
インターフェースを
適切な粒度に切り分けることで
解決する interface ITimeout {
public void OnTimeout();
}
class TimerEvent {
ITimeout timeout;
void OnTimeout()
=> timeout.OnTimeout();
}
interface IPushed {
public void OnButtonPushed();
}
class ButtonEvent {
IPushed pushed;
void OnPushed()
=> pushed.OnButtonPushed();
}
×
インターフェースが切り分けられた
interface ITimeout {
public void OnTimeout();
}
class TimerEvent {
ITimeout timeout;
void OnTimeout()
=> timeout.OnTimeout();
}
interface IPushed {
public void OnButtonPushed();
}
class ButtonEvent {
IPushed pushed;
void OnPushed()
=> pushed.OnButtonPushed();
}
class Door: ITimeout, IPushed {
public void Open();
public void Close();
public void OnButtonPushed()
=> Open();
public void OnTimeout()
=> Close();
}
両方の機能を
サポートするクラスは
インターフェースを複数実装する
インターフェースも最小限の大きさに
class Door: ITimeout, IPushed {
public void Open();
public void Close();
public void OnButtonPushed()
=> Open();
public void OnTimeout()
=> Close();
}
ところで
class Door: ITimeout, IPushed {
public void Open();
public void Close();
public void OnButtonPushed()
=> Open();
public void OnTimeout()
=> Close();
}
ドアの機能ではない
メソッドを持っています
ドアは外部にButtonやTimerがあることを知るべきでない
class Door {
public void Open();
public void Close();
}
ドアをあるべき姿にします
class Door {
public void Open();
public void Close();
}
IPushedとITimeoutをサポートするクラスを作る
class DoorPushed: IPushed {
Door door;
public void OnButtonPushed()
=> door.Open();
}
class DoorTimeout: ITimeout {
Door door;
public void OnTimeout()
=> door.Close();
}
class Door {
public void Open();
public void Close();
}
IPushedとITimeoutをサポートするクラスを作る
class DoorPushed: IPushed {
Door door;
public void OnButtonPushed()
=> door.Open();
}
class DoorTimeout: ITimeout {
Door door;
public void OnTimeout()
=> door.Close();
}
interface ITimeout {
public void OnTimeout();
}
class TimerEvent {
ITimeout timeout;
void OnTimeout()
=> timeout.OnTimeout();
}
interface IPushed {
public void OnButtonPushed();
}
class ButtonEvent {
IPushed pushed;
void OnPushed()
=> pushed.OnButtonPushed();
}
橋渡しのクラスを設けることで
Doorが自身の機能に集中しながら
インターフェースに対応できた
class Door {
public void Open();
public void Close();
}
Adapter
class DoorPushed: IPushed {
Door door;
public void OnButtonPushed()
=> door.Open();
}
class DoorTimeout: ITimeout {
Door door;
public void OnTimeout()
=> door.Close();
}
interface ITimeout {
public void OnTimeout();
}
class TimerEvent {
ITimeout timeout;
void OnTimeout()
=> timeout.OnTimeout();
}
interface IPushed {
public void OnButtonPushed();
}
class ButtonEvent {
IPushed pushed;
void OnPushed()
=> pushed.OnButtonPushed();
}
委譲による分離
橋渡しするクラスを と呼ぶ
と呼ぶこれを
D
I
O
S
依存性逆転の原則
インターフェース分離の原則
オープンクローズド原則
単一責任の原則
Single Responsibility
Open-Closed
Interface Segregation
Dependency Inversion
上位が下位に依存してはならない
どちらも抽象に依存するべきである
拡張に対して開いており
修正に対して閉じているべきである
クラスを変更する理由は
ただ1つであるべきである
クライアントは
使わないメソッドへの依存を
強制されない
L リスコフの置換原則
Liscov Substitution
継承を考える
長方形と正方形の問題
正方形は長方形である
長方形クラスを継承した正方形を実装
class Rect { // 長方形
public virtual int Height { get; set; }
public virtual int Width { get; set; }
// Rectの実装を
// そのままSquareで利用可能!
public int Area => Height * Width;
public int InternalAngleSum
=> 90 * 4; // 内角の和を返す
/* … */
}
class Square: Rect { // 正方形は長方形
public override int Height {
set {
base.Height = value;
// 高さと幅を合わせる
if (Width != value) Width = value;
}
}
public override int Width {
set { /* Height同様の実装(省略) */ }
}
// Area プロパティを実装する必要がない!
// InternalAngleSumもRectで実装済
}
テストを書いてみる
Rect rect = new Rect();
rect.Height = 3;
rect.Width = 5;
Assert.Equal(15, rect.Area); // OK!
テストのインスタンスをSquareに置換してみる
Rect rect = new Square(); // SquareはRectである
rect.Height = 3;
rect.Width = 5;
Assert.Equal(15, rect.Area);
テストのインスタンスをSquareに置換してみる
Rect rect = new Square(); // SquareはRectである
rect.Height = 3; // Height:3, Width: 3
rect.Width = 5; // Height: 5, Width: 5
Assert.Equal(15, rect.Area); // NG!
処理が壊れてしまった
Rectの正体がわからない場合もある
Rect rect = GetRect(); // なんらかのRectを返す関数
rect.Height = 3;
rect.Width = 5;
Assert.Equal(15, rect.Area); // ?
GetRect()の振る舞いによって
壊れたり壊れなかったりする処理になった
派生型はその基底型と
交換可能でなくてはならない
リスコフの置換原則Liscov Substitution
SquareはRectを置換できない
Rect rect = new Square(); // SquareはRectである
rect.Height = 3;
rect.Width = 5;
Assert.Equal(15, rect.Area); // NG!
リスコフの置換原則に
違反した継承であったことを示している
長方形の簡単な例でさえ
タブーを犯すほど
継承は難しい
class Rect { // 長方形
public virtual int Height { get; set; }
public virtual int Width { get; set; }
// Rectの実装を
// そのままSquareで利用可能!
public int Area => Height * Width;
public int InternalAngleSum
=> 90 * 4; // 内角の和を返す
/* … */
}
class Square: Rect { // 正方形は長方形
public override int Height {
set {
base.Height = value;
// 高さと幅を合わせる
if (Width != value) Width = value;
}
}
public override int Width {
set { /* Height同様の実装(省略) */ }
}
}
修正箇所の割り出しに
基底クラスと派生クラスの両方を見る必要がある
実は継承を使うべき場面はほとんどない
Facebookでは、
何千というコンポーネントでReactを使用していますが、
コンポーネント継承による階層構造が推奨されるケースは
全く見つかっていません。
コンポジション vs 継承 – React
実は継承を使うべき場面はほとんどない
新たなクラスが別のクラスを継承すべきかどうか悩んだとき、あるいは
継承を使うことに強い確信が持てない場合は継承を使うのをやめてくだ
さい。自信を持って他人に説明できない継承は99%この原則に反して
います。 一般に誤った継承が将来的にもたらす損失にくらべて、継承
すべき箇所でそうしなかったことによる損失は無視できるくらい小さい
ので大丈夫です。
SOLIDの原則 - 現代プログラミング勉強会
多態性はインターフェースで実現できる
class Worker: IWorker {
public int Work() {
Health -= 100;
return 1;
}
}
class WorkBot: IWorker {
public int Work() {
Energy -= 1;
return 100;
}
}
var workers = new IWorker[] {
new Worker(), new WorkBot()
};
var outputSum = 0;
foreach(var worker in workers) {
outputSum += worker.Work();
}
ポリモーフィズム
機能の共通化は包含で実現できる
class Worker: IWorker {
private PC pc; // 仕事効率化処理を提供
public int Work() {
Health -= 100;
return pc.PromoteTask(1);
}
}
class WorkBot: IWorker {
private PC pc;
public int Work() {
Energy -= 1;
return pc.PromoteTask(100000000);
}
}
var workers = new IWorker[] {
new Worker(), new WorkBot()
};
var outputSum = 0;
foreach(var worker in workers) {
outputSum += worker.Work();
}
コンポジション
Q: 継承はどこで使うのか
Template Methodパターン※など
一部のデザインパターンで有用
※付録参照
D
I
L
O
S
依存性逆転の原則
インターフェース分離の原則
リスコフの置換原則
オープンクローズド原則
単一責任の原則
Single Responsibility
Open-Closed
Liscov Substitution
Interface Segregation
Dependency Inversion
上位が下位に依存してはならない
どちらも抽象に依存するべきである
派生型はその基底型と
交換可能でなくてはならない
拡張に対して開いており
修正に対して閉じているべきである
クラスを変更する理由は
ただ1つであるべきである
クライアントは
使わないメソッドへの依存を
強制されない
これはすべて理想論です
大切なのは理想を知り
目指すこと
書いて捨てる実装に対して保守性のための設計は
費用対効果がよくない
無理に遵守する必要はない
● とりあえず動かしてみようのコード
○ ゲーム開発ではよくあること
律儀に実行するとクラスやインターフェースが
無限に増える
無理に遵守する必要はない
コンポーネントの重要度に応じて簡単に済ませても良い
こまめなリファクタリング・リデザインが大切
無理に遵守する必要はない
既存のコードとうまく合わない場合だってある
できる限りで
やっていきましょう
オススメ書籍情報
ソフトウェアアーキテクチャを考えるなら
定期的に読み返しておきたい一冊
オススメ書籍情報
ボトムアップでわかりやすく
レイヤードアーキテクチャの
基礎が学べる
付録: Template Methodパターン
abstract class CarTemplete {
// 個々の処理を派生クラスが自由に実装できる
abstract protected CarFrame CreateFrame();
abstract protected Engine CreateEngine();
abstract protected Color GetColor();
// 全体の処理の実装は提供せず自身で行う
public Car CreateCar() {
var carFrame = CreateFrame();
var engine = CreateEngine();
var color = GetColor();
return new Car(carFrame, engine, color);
}
}
継承は密結合派生クラスに実装パターンを強制できる ∵
付録: Template Methodパターン
// 利用者側のクラス
class PlayerObject: MyBehaviour {
protected override void Start() {
// 最初に一度だけ実行される処理を書くだけ
}
protected override void Update() {
// 毎フレーム実行される処理を書くだけ
}
}
フレームワークを提供する時などに便利
例: ゲームオブジェクトの枠組みを利用する例

More Related Content

What's hot

Halo2 におけるHFSM(階層型有限状態マシン) 【ビヘイビアツリー解説】
Halo2 におけるHFSM(階層型有限状態マシン)  【ビヘイビアツリー解説】Halo2 におけるHFSM(階層型有限状態マシン)  【ビヘイビアツリー解説】
Halo2 におけるHFSM(階層型有限状態マシン) 【ビヘイビアツリー解説】
Youichiro Miyake
 
オブジェクト指向できていますか?
オブジェクト指向できていますか?オブジェクト指向できていますか?
オブジェクト指向できていますか?Moriharu Ohzu
 
メルカリ・ソウゾウでは どうGoを活用しているのか?
メルカリ・ソウゾウでは どうGoを活用しているのか?メルカリ・ソウゾウでは どうGoを活用しているのか?
メルカリ・ソウゾウでは どうGoを活用しているのか?
Takuya Ueda
 
概念モデリング再入門 + DDD
概念モデリング再入門 + DDD概念モデリング再入門 + DDD
概念モデリング再入門 + DDD
Hiroshima JUG
 
うちではこうやっています UI構築のルールとPlaymakerを使った画面遷移
うちではこうやっています UI構築のルールとPlaymakerを使った画面遷移うちではこうやっています UI構築のルールとPlaymakerを使った画面遷移
うちではこうやっています UI構築のルールとPlaymakerを使った画面遷移
まべ☆てっく運営
 
DeNAの最新のマスタデータ管理システム Oyakata の全容
DeNAの最新のマスタデータ管理システム Oyakata の全容DeNAの最新のマスタデータ管理システム Oyakata の全容
DeNAの最新のマスタデータ管理システム Oyakata の全容
sairoutine
 
ワタシはSingletonがキライだ
ワタシはSingletonがキライだワタシはSingletonがキライだ
ワタシはSingletonがキライだ
Tetsuya Kaneuchi
 
5分で出来る!イケてるconfluenceページ
5分で出来る!イケてるconfluenceページ5分で出来る!イケてるconfluenceページ
5分で出来る!イケてるconfluenceページ
CLARA ONLINE, Inc.
 
VRM 標準シェーダ MToon の使い方
VRM 標準シェーダ MToon の使い方VRM 標準シェーダ MToon の使い方
VRM 標準シェーダ MToon の使い方
VirtualCast, Inc.
 
UniRxでMV(R)Pパターン をやってみた
UniRxでMV(R)PパターンをやってみたUniRxでMV(R)Pパターンをやってみた
UniRxでMV(R)Pパターン をやってみた
torisoup
 
C# ゲームプログラミングはホントにメモリのことに無頓着でいいの?
C# ゲームプログラミングはホントにメモリのことに無頓着でいいの?C# ゲームプログラミングはホントにメモリのことに無頓着でいいの?
C# ゲームプログラミングはホントにメモリのことに無頓着でいいの?
京大 マイコンクラブ
 
イベント・ソーシングを知る
イベント・ソーシングを知るイベント・ソーシングを知る
イベント・ソーシングを知るShuhei Fujita
 
ゲーム開発とMVC
ゲーム開発とMVCゲーム開発とMVC
ゲーム開発とMVC
Takashi Komada
 
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考えるGoのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
pospome
 
テスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるなテスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるな
Kentaro Matsui
 
例外設計における大罪
例外設計における大罪例外設計における大罪
例外設計における大罪
Takuto Wada
 
Vim script と vimrc の正しい書き方@nagoya.vim #1
Vim script と vimrc の正しい書き方@nagoya.vim #1Vim script と vimrc の正しい書き方@nagoya.vim #1
Vim script と vimrc の正しい書き方@nagoya.vim #1cohama
 
C#とILとネイティブと
C#とILとネイティブとC#とILとネイティブと
C#とILとネイティブと
信之 岩永
 
Vimから見たemacs
Vimから見たemacsVimから見たemacs
Vimから見たemacsShougo
 

What's hot (20)

Halo2 におけるHFSM(階層型有限状態マシン) 【ビヘイビアツリー解説】
Halo2 におけるHFSM(階層型有限状態マシン)  【ビヘイビアツリー解説】Halo2 におけるHFSM(階層型有限状態マシン)  【ビヘイビアツリー解説】
Halo2 におけるHFSM(階層型有限状態マシン) 【ビヘイビアツリー解説】
 
オブジェクト指向できていますか?
オブジェクト指向できていますか?オブジェクト指向できていますか?
オブジェクト指向できていますか?
 
メルカリ・ソウゾウでは どうGoを活用しているのか?
メルカリ・ソウゾウでは どうGoを活用しているのか?メルカリ・ソウゾウでは どうGoを活用しているのか?
メルカリ・ソウゾウでは どうGoを活用しているのか?
 
概念モデリング再入門 + DDD
概念モデリング再入門 + DDD概念モデリング再入門 + DDD
概念モデリング再入門 + DDD
 
うちではこうやっています UI構築のルールとPlaymakerを使った画面遷移
うちではこうやっています UI構築のルールとPlaymakerを使った画面遷移うちではこうやっています UI構築のルールとPlaymakerを使った画面遷移
うちではこうやっています UI構築のルールとPlaymakerを使った画面遷移
 
DeNAの最新のマスタデータ管理システム Oyakata の全容
DeNAの最新のマスタデータ管理システム Oyakata の全容DeNAの最新のマスタデータ管理システム Oyakata の全容
DeNAの最新のマスタデータ管理システム Oyakata の全容
 
ワタシはSingletonがキライだ
ワタシはSingletonがキライだワタシはSingletonがキライだ
ワタシはSingletonがキライだ
 
5分で出来る!イケてるconfluenceページ
5分で出来る!イケてるconfluenceページ5分で出来る!イケてるconfluenceページ
5分で出来る!イケてるconfluenceページ
 
Map
MapMap
Map
 
VRM 標準シェーダ MToon の使い方
VRM 標準シェーダ MToon の使い方VRM 標準シェーダ MToon の使い方
VRM 標準シェーダ MToon の使い方
 
UniRxでMV(R)Pパターン をやってみた
UniRxでMV(R)PパターンをやってみたUniRxでMV(R)Pパターンをやってみた
UniRxでMV(R)Pパターン をやってみた
 
C# ゲームプログラミングはホントにメモリのことに無頓着でいいの?
C# ゲームプログラミングはホントにメモリのことに無頓着でいいの?C# ゲームプログラミングはホントにメモリのことに無頓着でいいの?
C# ゲームプログラミングはホントにメモリのことに無頓着でいいの?
 
イベント・ソーシングを知る
イベント・ソーシングを知るイベント・ソーシングを知る
イベント・ソーシングを知る
 
ゲーム開発とMVC
ゲーム開発とMVCゲーム開発とMVC
ゲーム開発とMVC
 
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考えるGoのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
 
テスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるなテスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるな
 
例外設計における大罪
例外設計における大罪例外設計における大罪
例外設計における大罪
 
Vim script と vimrc の正しい書き方@nagoya.vim #1
Vim script と vimrc の正しい書き方@nagoya.vim #1Vim script と vimrc の正しい書き方@nagoya.vim #1
Vim script と vimrc の正しい書き方@nagoya.vim #1
 
C#とILとネイティブと
C#とILとネイティブとC#とILとネイティブと
C#とILとネイティブと
 
Vimから見たemacs
Vimから見たemacsVimから見たemacs
Vimから見たemacs
 

Recently uploaded

JSAI_類似画像マッチングによる器への印象付与手法の妥当性検証_ver.3_高橋りさ
JSAI_類似画像マッチングによる器への印象付与手法の妥当性検証_ver.3_高橋りさJSAI_類似画像マッチングによる器への印象付与手法の妥当性検証_ver.3_高橋りさ
JSAI_類似画像マッチングによる器への印象付与手法の妥当性検証_ver.3_高橋りさ
0207sukipio
 
FIDO Alliance Osaka Seminar: PlayStation Passkey Deployment Case Study.pdf
FIDO Alliance Osaka Seminar: PlayStation Passkey Deployment Case Study.pdfFIDO Alliance Osaka Seminar: PlayStation Passkey Deployment Case Study.pdf
FIDO Alliance Osaka Seminar: PlayStation Passkey Deployment Case Study.pdf
FIDO Alliance
 
単腕マニピュレータによる 複数物体の同時組み立ての 基礎的考察 / Basic Approach to Robotic Assembly of Multi...
単腕マニピュレータによる 複数物体の同時組み立ての 基礎的考察 / Basic Approach to Robotic Assembly of Multi...単腕マニピュレータによる 複数物体の同時組み立ての 基礎的考察 / Basic Approach to Robotic Assembly of Multi...
単腕マニピュレータによる 複数物体の同時組み立ての 基礎的考察 / Basic Approach to Robotic Assembly of Multi...
Fukuoka Institute of Technology
 
FIDO Alliance Osaka Seminar: Welcome Slides.pdf
FIDO Alliance Osaka Seminar: Welcome Slides.pdfFIDO Alliance Osaka Seminar: Welcome Slides.pdf
FIDO Alliance Osaka Seminar: Welcome Slides.pdf
FIDO Alliance
 
FIDO Alliance Osaka Seminar: NEC & Yubico Panel.pdf
FIDO Alliance Osaka Seminar: NEC & Yubico Panel.pdfFIDO Alliance Osaka Seminar: NEC & Yubico Panel.pdf
FIDO Alliance Osaka Seminar: NEC & Yubico Panel.pdf
FIDO Alliance
 
FIDO Alliance Osaka Seminar: CloudGate.pdf
FIDO Alliance Osaka Seminar: CloudGate.pdfFIDO Alliance Osaka Seminar: CloudGate.pdf
FIDO Alliance Osaka Seminar: CloudGate.pdf
FIDO Alliance
 
LoRaWAN 4チャンネル電流センサー・コンバーター CS01-LB 日本語マニュアル
LoRaWAN 4チャンネル電流センサー・コンバーター CS01-LB 日本語マニュアルLoRaWAN 4チャンネル電流センサー・コンバーター CS01-LB 日本語マニュアル
LoRaWAN 4チャンネル電流センサー・コンバーター CS01-LB 日本語マニュアル
CRI Japan, Inc.
 
FIDO Alliance Osaka Seminar: LY-DOCOMO-KDDI-Mercari Panel.pdf
FIDO Alliance Osaka Seminar: LY-DOCOMO-KDDI-Mercari Panel.pdfFIDO Alliance Osaka Seminar: LY-DOCOMO-KDDI-Mercari Panel.pdf
FIDO Alliance Osaka Seminar: LY-DOCOMO-KDDI-Mercari Panel.pdf
FIDO Alliance
 
CS集会#13_なるほどわからん通信技術 発表資料
CS集会#13_なるほどわからん通信技術 発表資料CS集会#13_なるほどわからん通信技術 発表資料
CS集会#13_なるほどわからん通信技術 発表資料
Yuuitirou528 default
 
論文紹介:When Visual Prompt Tuning Meets Source-Free Domain Adaptive Semantic Seg...
論文紹介:When Visual Prompt Tuning Meets Source-Free Domain Adaptive Semantic Seg...論文紹介:When Visual Prompt Tuning Meets Source-Free Domain Adaptive Semantic Seg...
論文紹介:When Visual Prompt Tuning Meets Source-Free Domain Adaptive Semantic Seg...
Toru Tamaki
 
【DLゼミ】XFeat: Accelerated Features for Lightweight Image Matching
【DLゼミ】XFeat: Accelerated Features for Lightweight Image Matching【DLゼミ】XFeat: Accelerated Features for Lightweight Image Matching
【DLゼミ】XFeat: Accelerated Features for Lightweight Image Matching
harmonylab
 
ReonHata_便利の副作用に気づかせるための発想支援手法の評価---行為の増減の提示による気づきへの影響---
ReonHata_便利の副作用に気づかせるための発想支援手法の評価---行為の増減の提示による気づきへの影響---ReonHata_便利の副作用に気づかせるための発想支援手法の評価---行為の増減の提示による気づきへの影響---
ReonHata_便利の副作用に気づかせるための発想支援手法の評価---行為の増減の提示による気づきへの影響---
Matsushita Laboratory
 
This is the company presentation material of RIZAP Technologies, Inc.
This is the company presentation material of RIZAP Technologies, Inc.This is the company presentation material of RIZAP Technologies, Inc.
This is the company presentation material of RIZAP Technologies, Inc.
chiefujita1
 
TaketoFujikawa_物語のコンセプトに基づく情報アクセス手法の基礎検討_JSAI2024
TaketoFujikawa_物語のコンセプトに基づく情報アクセス手法の基礎検討_JSAI2024TaketoFujikawa_物語のコンセプトに基づく情報アクセス手法の基礎検討_JSAI2024
TaketoFujikawa_物語のコンセプトに基づく情報アクセス手法の基礎検討_JSAI2024
Matsushita Laboratory
 

Recently uploaded (14)

JSAI_類似画像マッチングによる器への印象付与手法の妥当性検証_ver.3_高橋りさ
JSAI_類似画像マッチングによる器への印象付与手法の妥当性検証_ver.3_高橋りさJSAI_類似画像マッチングによる器への印象付与手法の妥当性検証_ver.3_高橋りさ
JSAI_類似画像マッチングによる器への印象付与手法の妥当性検証_ver.3_高橋りさ
 
FIDO Alliance Osaka Seminar: PlayStation Passkey Deployment Case Study.pdf
FIDO Alliance Osaka Seminar: PlayStation Passkey Deployment Case Study.pdfFIDO Alliance Osaka Seminar: PlayStation Passkey Deployment Case Study.pdf
FIDO Alliance Osaka Seminar: PlayStation Passkey Deployment Case Study.pdf
 
単腕マニピュレータによる 複数物体の同時組み立ての 基礎的考察 / Basic Approach to Robotic Assembly of Multi...
単腕マニピュレータによる 複数物体の同時組み立ての 基礎的考察 / Basic Approach to Robotic Assembly of Multi...単腕マニピュレータによる 複数物体の同時組み立ての 基礎的考察 / Basic Approach to Robotic Assembly of Multi...
単腕マニピュレータによる 複数物体の同時組み立ての 基礎的考察 / Basic Approach to Robotic Assembly of Multi...
 
FIDO Alliance Osaka Seminar: Welcome Slides.pdf
FIDO Alliance Osaka Seminar: Welcome Slides.pdfFIDO Alliance Osaka Seminar: Welcome Slides.pdf
FIDO Alliance Osaka Seminar: Welcome Slides.pdf
 
FIDO Alliance Osaka Seminar: NEC & Yubico Panel.pdf
FIDO Alliance Osaka Seminar: NEC & Yubico Panel.pdfFIDO Alliance Osaka Seminar: NEC & Yubico Panel.pdf
FIDO Alliance Osaka Seminar: NEC & Yubico Panel.pdf
 
FIDO Alliance Osaka Seminar: CloudGate.pdf
FIDO Alliance Osaka Seminar: CloudGate.pdfFIDO Alliance Osaka Seminar: CloudGate.pdf
FIDO Alliance Osaka Seminar: CloudGate.pdf
 
LoRaWAN 4チャンネル電流センサー・コンバーター CS01-LB 日本語マニュアル
LoRaWAN 4チャンネル電流センサー・コンバーター CS01-LB 日本語マニュアルLoRaWAN 4チャンネル電流センサー・コンバーター CS01-LB 日本語マニュアル
LoRaWAN 4チャンネル電流センサー・コンバーター CS01-LB 日本語マニュアル
 
FIDO Alliance Osaka Seminar: LY-DOCOMO-KDDI-Mercari Panel.pdf
FIDO Alliance Osaka Seminar: LY-DOCOMO-KDDI-Mercari Panel.pdfFIDO Alliance Osaka Seminar: LY-DOCOMO-KDDI-Mercari Panel.pdf
FIDO Alliance Osaka Seminar: LY-DOCOMO-KDDI-Mercari Panel.pdf
 
CS集会#13_なるほどわからん通信技術 発表資料
CS集会#13_なるほどわからん通信技術 発表資料CS集会#13_なるほどわからん通信技術 発表資料
CS集会#13_なるほどわからん通信技術 発表資料
 
論文紹介:When Visual Prompt Tuning Meets Source-Free Domain Adaptive Semantic Seg...
論文紹介:When Visual Prompt Tuning Meets Source-Free Domain Adaptive Semantic Seg...論文紹介:When Visual Prompt Tuning Meets Source-Free Domain Adaptive Semantic Seg...
論文紹介:When Visual Prompt Tuning Meets Source-Free Domain Adaptive Semantic Seg...
 
【DLゼミ】XFeat: Accelerated Features for Lightweight Image Matching
【DLゼミ】XFeat: Accelerated Features for Lightweight Image Matching【DLゼミ】XFeat: Accelerated Features for Lightweight Image Matching
【DLゼミ】XFeat: Accelerated Features for Lightweight Image Matching
 
ReonHata_便利の副作用に気づかせるための発想支援手法の評価---行為の増減の提示による気づきへの影響---
ReonHata_便利の副作用に気づかせるための発想支援手法の評価---行為の増減の提示による気づきへの影響---ReonHata_便利の副作用に気づかせるための発想支援手法の評価---行為の増減の提示による気づきへの影響---
ReonHata_便利の副作用に気づかせるための発想支援手法の評価---行為の増減の提示による気づきへの影響---
 
This is the company presentation material of RIZAP Technologies, Inc.
This is the company presentation material of RIZAP Technologies, Inc.This is the company presentation material of RIZAP Technologies, Inc.
This is the company presentation material of RIZAP Technologies, Inc.
 
TaketoFujikawa_物語のコンセプトに基づく情報アクセス手法の基礎検討_JSAI2024
TaketoFujikawa_物語のコンセプトに基づく情報アクセス手法の基礎検討_JSAI2024TaketoFujikawa_物語のコンセプトに基づく情報アクセス手法の基礎検討_JSAI2024
TaketoFujikawa_物語のコンセプトに基づく情報アクセス手法の基礎検討_JSAI2024
 

オブジェクト指向とSOLID原則の入門