デメテルの法則を整理してみた。
デメテルの法則について調べたことを自分なりに整理。
デメテルの法則(LoD)とは
「オブジェクトは、直接の友達(直接関係するオブジェクト)とだけやりとりするべき」という設計原則。
何段階も経由しないで、直接必要な情報を持っているオブジェクトとやりとりできるようにしようという考え方。
→ そのオブジェクトが本来知らないはずの知識を使った処理を作ってはいけない
→ カプセル化を保つための設計原則とも言える
デメテルの法則のメリット
- カプセル化が守られる
クラスの内部構造を隠すことで、安全で分かりやすい設計になる。 - 修正がしやすくなる(変更に強い)
依存関係が少なくなるため、関連するクラスが変わっても影響範囲が小さくなる。 - テストがしやすくなる
直接の友達だけを扱うことで、モックやスタブを使った単体テストが容易になる。
しかし、デメテルの法則を意識して実装しても、意図せず破ってしまうケースも...
下記のコードは、一見、適切にカプセル化されているように見えても、実は内部構造を外部に漏らしてしまっている。
class Purchase {
private _userId: string
private _productId: string
constructor(userId: string, productId: string) {
this._userId = userId
this._productId = productId
}
public get userId() {
return this._userId
}
public set userId(id) {
this._userId = id
}
public get productId() {
return this._productId
}
public set productId(id) {
this._productId = id
}
}
内部構造が漏れてしまっていると保守性が下がる
getterを使うこと自体が悪いわけではないが、デメテルの法則は「直接の友達とだけやり取りすること」なので、getter を使って外部のオブジェクトが userId
や productId
にアクセスできてしまうと結局内部構造が漏れていることになる。
→ 内部構造を直接公開してしまうと、その構造に依存したコードが増えてしまう。
-
Purchase
のuserId
を別の形で管理するように変更した場合、userId
を直接使っているすべてのコードを修正しなければならないという問題が発生する
クラスの責務が曖昧になる。
外部で purchase.userId
を使って User
の情報を取得するようなコードが増えてしまうと、
User
を扱う処理があちこちに散らばり、「誰が何を責任を持って管理するのか?」が曖昧になってしまう。
意図せずデータが変更される危険性がある
setter
を使うことで userId
や productId
が外部から自由に変更できるようになり、Purchase
の一貫性を保つのが難しくなる。
本来、購入してユーザーのidが書き換わる様なことはなさそう
Userの仕事を Purchase でやろうとしてしまっているので、User を使って必要な情報を取得する処理を追加するようにする。
class Purchase {
private user: User;
private product: Product;
constructor(user: User, product: Product) {
this.user = user;
this.product = product;
}
introduceUser(): string {
return this.user.introduce(); // `User` の情報を取得する方法をカプセル化
}
}
結果的にオープンクローズドの原則にもつながる
内部構造が漏れると変更時の影響範囲が広がってしまい、OCPの実践が難しくなる。
例えば、userId の取得方法を変更するたびに Purchase
側のコードも変更しなければならない設計だと、OCP に反する。
User
に適切なメソッドを用意し、Purchase
はそれを呼び出すようにすれば、変更を小さく抑えることができる。
→ OCPを満たしやすい
まとめ
- デメテルの法則を意識することでカプセル化を保った設計ができる
→ カプセル化が崩れると責務の分離が曖昧になり、保守性や安全性が低下してしまう - 意識することで、変更時の影響範囲が小さくなり、オープンクローズドの原則(OCP)を満たし安くなる
Discussion