目的
社内お勉強会で DDD 本を輪読することになったんですが、
改めて「〇〇ってわかりますか?」と言われると戸惑ってしまうことが多いので自分用にまとめました。
抽象クラス(abstract class)
他のクラスで継承してもらうことを前提としたクラス。
複数のクラスの共通処理の中に、一部異なる処理を使用したい場合などに定義する。
- 抽象クラス自体はインスタンス化できない
abstract function
で抽象メソッドを定義できる
インターフェース
クラスが実装するメソッドを定義することができるもの。
- メソッドの実体は持てない(定義のみ)
- 使用できる修飾子は
public
のみ - 定数を持てる。実装先のクラスでのオーバーライドはできない
不特定のクラスで、共通のメソッドを定義したい場合などに定義する。
ポリモーフィズム
「中に入るものによって、同じ関数でも違う処理を行える」というオブジェクト指向プログラミングの特徴のこと。
「モノが「そのモノ」らしく振る舞うこと」 =「呼び出した関数が、呼び出し元のオブジェクトに適した振る舞いをすること」
# 動物は必ず「鳴く」
interface Animal {
sound();
}
# 犬は「動物らしい振る舞い」をする(鳴く)
class dog implements Animal {
sound() { /**/ }
}
# 動物は「鳴く」ので、引数の動物ごとの異なる鳴き声を処理する
# 引数で与えられる動物によって、異なる処理(鳴き声)ができる
public function animalsound(animal) {
animal.sound()
}
継承
親クラスの振る舞いを引き継いで、子クラスを実装すること。 子クラス側で親クラスを拡張することができる。
委譲
実際の振る舞いを別のオブジェクトに委ねることで、別のオブジェクトの振る舞いを再利用する手法。(?)
カプセル化
関連のあるデータとその使い方を一まとめにしたもの。 内部のデータを直接いじることはさせずに、使い方だけを見せる。
GoF デザインパターン
23 種類あるヤツ(雑)
勉強します
3 分でわかるデザインパターン入門
SOLID 原則
SOLID 原則とは
Robert C. Martin によって提唱された 5 つのガイドライン。
これらのガイドライン(原則)に則ることで、開発者にとって読みやすく、メンテナンスが可能なプログラムを作成しやすくなる。
Robert C. Martin:
「ボブおじさん」の通称で知られる(?)ソフトフェア技術者・インストラクター。
アジャイル宣言の著者の一人。
「プログラマが知るべき 97 のこと」でもいくつかエッセイを書いている。
5 つの原則
- S:SRP、単一責任の原則
→ クラスその他諸々が負う責任は一つにしようね - O:OCP、解放閉鎖の原則
→ 修正時に既存のロジックを弄らないといけない実装は駄目だよ - L:LSP、リスコフの置換原則
→ サブクラスとスーパークラスを入れ替えても動くように設計してね - I:ISP、インタフェース分離の原則
→ 使用しないメソッドをクラスに強要するインターフェースを作らないでね - D:DIP、依存性逆転の原則
単一責任の原則(SRP)
クラスは1つのことだけ責任を負うべき。
もし複数の責任を負っている場合、それらは連動してしまう。
※この原則はクラスだけでなく、ソフトフェアコンポーネントやマイクロサービスにも当てはまる。
- 違反する例
# プロパティ管理とDB管理の二つの責任を持ってしまっているクラス
class Animal {
constructor(name: string) { }
getAnimalName() { }
saveAnimal(a: Animal) { }
- SRP に一致する改善案
class Animal {
constructor(name: string) { }
getAnimalName() { }
}
class AnimalDB {
getAnimal(a: Animal) { }
saveAnimal(a: Animal) { }
}
解放閉鎖の原則(OCP)
ソフトウェアのエンティティ(クラス、モジュール、関数)は、拡張に対して開き、修正に対して閉じていなければならない。(?)
→ ロジックのパターンが増える度に既存の処理を修正 or 追記しなくちゃいけないような書き方は避ける。
- 違反する例
class Animal {
constructor(name: string) { }
getAnimalName() { }
}
# animalリストを順に処理し、泣き声を出す
const animals: Array<Animal> = [
new Animal(“lion”),
new Animal(“mouse”)
];
# animalsが更新されるごとに修正を入れないといけない
# animalsの修正に対して「閉じていない」関数
function AnimalSound(a: Array<Animal>) {
for(int i = 0; i <= a.length; i++) {
if(a[i].name == “lion”)
log(“gaogao”)
if(a[i].name == “mouse”)
log(“chu”)
}
}
AnimalSound(animals);
- OCP に一致する例
# Animalに仮想メソッドmakeSound()を宣言
# 各々の動物に各々のmakeSound()が実装されるので、
# 新しい動物が増える度にAnimalSound()を変更する必要はない
class Animal {
makeSound();
# ...
}
class Lion extends Animal {
nameSound() {
return “gaogao”
}
}
class mouse extends Animal {
nameSound() {
return “chu”
}
}
# お好きなアニマルをanimalsに突っ込む
fanction AnimalSound(a: Array<Animal>) {
for(int i = 0; i <= a.length; i++) {
log(a[i].makeSound());
}
}
AnimalSound(animals);
リスコフの置換原則(LSP)
サブクラスは、そのスーパークラスで代用可能でなければならない。
- 違反している例
# 全部のAnimalの型を見て、それに関連するLegCountを呼び出す必要がある
# コードでクラスの型をチェックしていたら、LSPに違反している場合が多い
# OCPにも違反している
function AnimalLegCount(a: Array<animal>) {
for(int i = 0; i <= a.length; i++) {
if(typeof a[i] == Lion)
log(LionLegCount(a[i]));
if(typeof a[i] == Mouse)
log(MouseLegCount(a[i]));
if(typeof a[i] == Snake)
log(SnakeLegCount(a[i]));
}
}
AnimalLegCount(animals);
- LSP に一致した例
# OCPに一致させるために仮想メソッドを定義
class Animal {
LegCount();
# ...
}
# Animal型が通過しても気にせず、ただLegCountメソッドを呼び出すだけ
function AnimalLegCount(a: Array<Animal>){
for(let i = 0; i <= a.length; i++) {
a[i].LegCount()
}
}
AnimalLegCount(animals);
(この例だと OCP の解決法とほぼ一緒なのでは……)
インターフェース分離の原則(ISP)
顧客に特化した細粒度のインターフェースを作る。
顧客は、自分たちが使わないインターフェースに依存することを強いられるべきではない。
- 違反する例
interface ISape {
drawCircle();
drawSquare();
drawRectagle();
}
# 円しか書かないのにdrawSquare()、drawRectagle()を実装しないといけない
class Circle implements IShape {
drawCircle(){ /**/ }
drawSquare(){ /**/ }
drawRectangle(){ /**/ }
}
- ISP に一致する例
interface IShape {
draw();
}
interface Icicle() {
drawCicle();
}
interface ISquare {
drawSquare();
}
class Circle implements ICircle {
drawCicle() { /**/ }
}
# もしくは、IShapeを継承してお好きなdrawを組み立てる
依存性逆転の原則(DIP)
依存性は、具体化ではなく抽象化でなければならない。
具体的な依存性の問題点:
依存元を修正するとき、依存先も修正する必要がある
単体テストの範囲が大きくなりがち(場合によっては書けない)
テストの範囲が大きくなる。失敗時の原因特定が難しくなる。
違反している例(具体的な依存性)
Class Speaker() {
sound() { /**/}
}
# この書き方をすることで、RadioTunerはSpeakerに強く依存している
# (Speakerの事を直接「知ってしまっている」)
# Speakerを修正する場合、RadioTunerも修正が必要になる
class RadioTuner() {
$speaker = new Speaker();
$speaker->sound();
}
- DIP に一致する例
interface AudioOutput() {
sound();
}
# Speakerはinterfaceに依存
Class Speaker implements AudioOutput {
sound() { /**/}
}
# RadioTunerもinterfaceに依存
# RadioTuner自体は、自分が実際に渡されているクラスを知らない
# (ポリモーフィズムという)
class RadioTuner {
public function output(AudoiOutput $outputCircuit) {
# sound()があることはinterfaceで保証されている
$outputCircuit->sound();
}
}
サービスコンテナ
Laravel のサービスコンテナは、クラス間の依存を管理する強力な管理ツールです。依存注入というおかしな言葉は主に「コンストラクターか、ある場合にはセッターメソッドを利用し、あるクラスをそれらに依存しているクラスへ外部から注入する」という意味で使われます。
クラスのインスタンスを作ってくれるツール。 同じ文字列から、条件によって異なるクラスのインスタンスを生成することができる。
使用することで、DI と呼ばれるデザインパターンを実現できてクラスの依存性が薄まるので、 単体テストがしやすくなるなどの恩恵が得られる。
ファザード
ファサード(facade、「入り口」)はアプリケーションの サービスコンテナ に登録したクラスへ、「静的」なインターフェイスを提供します。Laravel のほとんどの機能に対して、ファサードが用意されています。Laravel の「ファサード」は、サービスコンテナ下で動作しているクラスに対し、“static proxy"として動作しています。これにより伝統的な静的メソッドよりもテストの行いやすさと柔軟性を保ちながらも、簡潔で記述的であるという利点があります。
Laravel くんが用意してくれている便利なメソッド集(曲解)。
DB ファザードであれば、DB の操作に便利なメソッドがたくさんある(DB::hogehoge
)
参考
開発者が知っておくべき SOLID の原則 | POSTD
Getting a SOLID start. - Clean Coder