SlideShare a Scribd company logo
1 of 121
Download to read offline
と
オブジェクト指向設計
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

オブジェクト指向エクササイズのススメ
オブジェクト指向エクササイズのススメオブジェクト指向エクササイズのススメ
オブジェクト指向エクササイズのススメYoji Kanno
 
IncrediBuildでビルド時間を最大90%短縮! - インクレディビルドジャパン株式会社 - GTMF 2018 OSAKA
IncrediBuildでビルド時間を最大90%短縮! - インクレディビルドジャパン株式会社 - GTMF 2018 OSAKAIncrediBuildでビルド時間を最大90%短縮! - インクレディビルドジャパン株式会社 - GTMF 2018 OSAKA
IncrediBuildでビルド時間を最大90%短縮! - インクレディビルドジャパン株式会社 - GTMF 2018 OSAKAGame Tools & Middleware Forum
 
安全なWebアプリケーションの作り方2018
安全なWebアプリケーションの作り方2018安全なWebアプリケーションの作り方2018
安全なWebアプリケーションの作り方2018Hiroshi Tokumaru
 
FINAL FANTASY Record Keeperのマスターデータを支える技術
FINAL FANTASY Record Keeperのマスターデータを支える技術FINAL FANTASY Record Keeperのマスターデータを支える技術
FINAL FANTASY Record Keeperのマスターデータを支える技術dena_study
 
RPGにおけるイベント駆動型の設計と実装
RPGにおけるイベント駆動型の設計と実装RPGにおけるイベント駆動型の設計と実装
RPGにおけるイベント駆動型の設計と実装Koji Morikawa
 
【Unite 2017 Tokyo】もっと気軽に、動的なコンテンツ配信を ~アセットバンドルの未来と開発ロードマップ
【Unite 2017 Tokyo】もっと気軽に、動的なコンテンツ配信を ~アセットバンドルの未来と開発ロードマップ【Unite 2017 Tokyo】もっと気軽に、動的なコンテンツ配信を ~アセットバンドルの未来と開発ロードマップ
【Unite 2017 Tokyo】もっと気軽に、動的なコンテンツ配信を ~アセットバンドルの未来と開発ロードマップUnite2017Tokyo
 
新入社員のための大規模ゲーム開発入門 サーバサイド編
新入社員のための大規模ゲーム開発入門 サーバサイド編新入社員のための大規模ゲーム開発入門 サーバサイド編
新入社員のための大規模ゲーム開発入門 サーバサイド編infinite_loop
 
オブジェクト指向プログラミングのためのモデリング入門
オブジェクト指向プログラミングのためのモデリング入門オブジェクト指向プログラミングのためのモデリング入門
オブジェクト指向プログラミングのためのモデリング入門増田 亨
 
うちではこうやっています UI構築のルールとPlaymakerを使った画面遷移
うちではこうやっています UI構築のルールとPlaymakerを使った画面遷移うちではこうやっています UI構築のルールとPlaymakerを使った画面遷移
うちではこうやっています UI構築のルールとPlaymakerを使った画面遷移まべ☆てっく運営
 
Unityで始めるバージョン管理 Git LFS 入門編
Unityで始めるバージョン管理 Git LFS 入門編Unityで始めるバージョン管理 Git LFS 入門編
Unityで始めるバージョン管理 Git LFS 入門編NAKAOKU Takahiro
 
シェーダーを活用した3Dライブ演出のアップデート ~『ラブライブ!スクールアイドルフェスティバル ALL STARS』(スクスタ)の開発事例~​
シェーダーを活用した3Dライブ演出のアップデート ~『ラブライブ!スクールアイドルフェスティバル ALL STARS』(スクスタ)の開発事例~​シェーダーを活用した3Dライブ演出のアップデート ~『ラブライブ!スクールアイドルフェスティバル ALL STARS』(スクスタ)の開発事例~​
シェーダーを活用した3Dライブ演出のアップデート ~『ラブライブ!スクールアイドルフェスティバル ALL STARS』(スクスタ)の開発事例~​KLab Inc. / Tech
 
Unity開発で使える設計の話+Zenjectの紹介
Unity開発で使える設計の話+Zenjectの紹介Unity開発で使える設計の話+Zenjectの紹介
Unity開発で使える設計の話+Zenjectの紹介torisoup
 
もしも日露戦争のときに満州軍総参謀長児玉源太郎閣下がインセプションデッキを作っていたら
もしも日露戦争のときに満州軍総参謀長児玉源太郎閣下がインセプションデッキを作っていたらもしも日露戦争のときに満州軍総参謀長児玉源太郎閣下がインセプションデッキを作っていたら
もしも日露戦争のときに満州軍総参謀長児玉源太郎閣下がインセプションデッキを作っていたらDaiki Tanoguchi
 
ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門増田 亨
 
traitを使って楽したい話
traitを使って楽したい話traitを使って楽したい話
traitを使って楽したい話infinite_loop
 
MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」
MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」
MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」U-dai Yokoyama
 
ゲームエンジニアのためのデータベース設計
ゲームエンジニアのためのデータベース設計ゲームエンジニアのためのデータベース設計
ゲームエンジニアのためのデータベース設計sairoutine
 
UnityとVuforiaで始めるAR開発
UnityとVuforiaで始めるAR開発UnityとVuforiaで始めるAR開発
UnityとVuforiaで始めるAR開発Takashi Yoshinaga
 

What's hot (20)

オブジェクト指向エクササイズのススメ
オブジェクト指向エクササイズのススメオブジェクト指向エクササイズのススメ
オブジェクト指向エクササイズのススメ
 
IncrediBuildでビルド時間を最大90%短縮! - インクレディビルドジャパン株式会社 - GTMF 2018 OSAKA
IncrediBuildでビルド時間を最大90%短縮! - インクレディビルドジャパン株式会社 - GTMF 2018 OSAKAIncrediBuildでビルド時間を最大90%短縮! - インクレディビルドジャパン株式会社 - GTMF 2018 OSAKA
IncrediBuildでビルド時間を最大90%短縮! - インクレディビルドジャパン株式会社 - GTMF 2018 OSAKA
 
安全なWebアプリケーションの作り方2018
安全なWebアプリケーションの作り方2018安全なWebアプリケーションの作り方2018
安全なWebアプリケーションの作り方2018
 
FINAL FANTASY Record Keeperのマスターデータを支える技術
FINAL FANTASY Record Keeperのマスターデータを支える技術FINAL FANTASY Record Keeperのマスターデータを支える技術
FINAL FANTASY Record Keeperのマスターデータを支える技術
 
UE4とBlenderでランニングコストを抑えるモダンなワークフロー
UE4とBlenderでランニングコストを抑えるモダンなワークフローUE4とBlenderでランニングコストを抑えるモダンなワークフロー
UE4とBlenderでランニングコストを抑えるモダンなワークフロー
 
RPGにおけるイベント駆動型の設計と実装
RPGにおけるイベント駆動型の設計と実装RPGにおけるイベント駆動型の設計と実装
RPGにおけるイベント駆動型の設計と実装
 
【Unite 2017 Tokyo】もっと気軽に、動的なコンテンツ配信を ~アセットバンドルの未来と開発ロードマップ
【Unite 2017 Tokyo】もっと気軽に、動的なコンテンツ配信を ~アセットバンドルの未来と開発ロードマップ【Unite 2017 Tokyo】もっと気軽に、動的なコンテンツ配信を ~アセットバンドルの未来と開発ロードマップ
【Unite 2017 Tokyo】もっと気軽に、動的なコンテンツ配信を ~アセットバンドルの未来と開発ロードマップ
 
UE4 MultiPlayer Online Deep Dive 実践編2 (ソレイユ株式会社様ご講演) #UE4DD
UE4 MultiPlayer Online Deep Dive 実践編2 (ソレイユ株式会社様ご講演) #UE4DDUE4 MultiPlayer Online Deep Dive 実践編2 (ソレイユ株式会社様ご講演) #UE4DD
UE4 MultiPlayer Online Deep Dive 実践編2 (ソレイユ株式会社様ご講演) #UE4DD
 
新入社員のための大規模ゲーム開発入門 サーバサイド編
新入社員のための大規模ゲーム開発入門 サーバサイド編新入社員のための大規模ゲーム開発入門 サーバサイド編
新入社員のための大規模ゲーム開発入門 サーバサイド編
 
オブジェクト指向プログラミングのためのモデリング入門
オブジェクト指向プログラミングのためのモデリング入門オブジェクト指向プログラミングのためのモデリング入門
オブジェクト指向プログラミングのためのモデリング入門
 
うちではこうやっています UI構築のルールとPlaymakerを使った画面遷移
うちではこうやっています UI構築のルールとPlaymakerを使った画面遷移うちではこうやっています UI構築のルールとPlaymakerを使った画面遷移
うちではこうやっています UI構築のルールとPlaymakerを使った画面遷移
 
Unityで始めるバージョン管理 Git LFS 入門編
Unityで始めるバージョン管理 Git LFS 入門編Unityで始めるバージョン管理 Git LFS 入門編
Unityで始めるバージョン管理 Git LFS 入門編
 
シェーダーを活用した3Dライブ演出のアップデート ~『ラブライブ!スクールアイドルフェスティバル ALL STARS』(スクスタ)の開発事例~​
シェーダーを活用した3Dライブ演出のアップデート ~『ラブライブ!スクールアイドルフェスティバル ALL STARS』(スクスタ)の開発事例~​シェーダーを活用した3Dライブ演出のアップデート ~『ラブライブ!スクールアイドルフェスティバル ALL STARS』(スクスタ)の開発事例~​
シェーダーを活用した3Dライブ演出のアップデート ~『ラブライブ!スクールアイドルフェスティバル ALL STARS』(スクスタ)の開発事例~​
 
Unity開発で使える設計の話+Zenjectの紹介
Unity開発で使える設計の話+Zenjectの紹介Unity開発で使える設計の話+Zenjectの紹介
Unity開発で使える設計の話+Zenjectの紹介
 
もしも日露戦争のときに満州軍総参謀長児玉源太郎閣下がインセプションデッキを作っていたら
もしも日露戦争のときに満州軍総参謀長児玉源太郎閣下がインセプションデッキを作っていたらもしも日露戦争のときに満州軍総参謀長児玉源太郎閣下がインセプションデッキを作っていたら
もしも日露戦争のときに満州軍総参謀長児玉源太郎閣下がインセプションデッキを作っていたら
 
ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門
 
traitを使って楽したい話
traitを使って楽したい話traitを使って楽したい話
traitを使って楽したい話
 
MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」
MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」
MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」
 
ゲームエンジニアのためのデータベース設計
ゲームエンジニアのためのデータベース設計ゲームエンジニアのためのデータベース設計
ゲームエンジニアのためのデータベース設計
 
UnityとVuforiaで始めるAR開発
UnityとVuforiaで始めるAR開発UnityとVuforiaで始めるAR開発
UnityとVuforiaで始めるAR開発
 

Recently uploaded

AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdf
AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdfAWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdf
AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdfFumieNakayama
 
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?akihisamiyanaga1
 
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdf
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdfクラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdf
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdfFumieNakayama
 
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)NTT DATA Technology & Innovation
 
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察 ~Text-to-MusicとText-To-ImageかつImage-to-Music...
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察  ~Text-to-MusicとText-To-ImageかつImage-to-Music...モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察  ~Text-to-MusicとText-To-ImageかつImage-to-Music...
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察 ~Text-to-MusicとText-To-ImageかつImage-to-Music...博三 太田
 
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)Hiroshi Tomioka
 
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)UEHARA, Tetsutaro
 
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineerYuki Kikuchi
 

Recently uploaded (8)

AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdf
AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdfAWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdf
AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdf
 
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?
 
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdf
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdfクラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdf
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdf
 
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
 
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察 ~Text-to-MusicとText-To-ImageかつImage-to-Music...
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察  ~Text-to-MusicとText-To-ImageかつImage-to-Music...モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察  ~Text-to-MusicとText-To-ImageかつImage-to-Music...
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察 ~Text-to-MusicとText-To-ImageかつImage-to-Music...
 
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
 
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)
 
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer
 

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