はじめに
本記事は、プログラミング経験の浅い筆者がつまずき、理解に時間がかかったオブジェクト指向の考え方である「デメテルの法則」について解説したものです。
わかりにくい点、誤り等があれば教えていただけますと幸いです。
ダメなんですか!?
ToDoリストを作りたい筆者。
わたし「ToDoCollectionにToDoCollectionItemを追加する処理を書きたいな」
// ToDoを集約するクラス
public ToDoCollection
{
public IList<ToDoCollectionItem> ToDoList{get;set;}
...
// アイテムを追加
public void AddToDo (ToDoCollectionItem item)
{
...
}
...
}
わたし「あれ、ToDoCollectionクラスにAddメソッドあるけど、よく見たらプロパティでToDoListが公開されてるじゃん。IList型ならAddメソッドあるよね……」
わたし「じゃあ、ToDoList.Add()でよくない?」
こうして筆者はToDoList.Add()で処理を書き、レビューで指摘をもらってしまいました。
どうしてダメなのか
やりたいことは「ToDoCollection」への要素の追加です。
IList型のAddメソッドでも、ToDoCollectionクラスのAddToDoメソッドのどちらでも、実現できるように思えるでしょう。
しかし、ToDoCollectionに、以下のようなルールがあった場合はどうでしょうか。
「ToDoCollectionの最大の要素数は10個とする」
実は、ToDoCollectionクラスのAddToDoメソッドの中には、以下のように、ToDoCollectionが10個を超えないようにする処理が書かれていました。
// ToDoを集約するクラス
public ToDoCollection
{
public IList<ToDoCollectionItem> ToDoList{get;set;}
...
// アイテムを追加
public void AddToDo (ToDoCollectionItem item)
{
// ToDoが既に10個以上の場合
if(ToDoList.Count() >= 10)
{
// 古いToDoを消して新しいToDoを追加する
...
}
...
}
...
}
「じゃあ、ToDoList.Add()でよくない?」
と、IListのAddメソッドを利用してしまった場合はどうなるでしょうか。
わたし「ToDoCollectionは10個までしか入れちゃダメだからガードしなきゃ……」
public ProgramA()
{
...
if(ToDoCollection.ToDoList >= 10)
{
ToDoCollection.ToDoList.Add(targetToDo);
}
}
わたし「あ、こっちでもガード処理書かなきゃ……」
public ProgramB()
{
...
if(ToDoCollection.ToDoList >= 10)
{
ToDoCollection.ToDoList.Add(targetToDo);
}
}
「ToDoCollectionの最大の要素数は10個とする」 というルールを守るための処理を、ToDoCollectionのアイテム追加処理を行う場所すべてで行わなければならなくなります。
ToDoCollectionのルールなのに、ToDoCollectionの外でルールを検証している状態で、責務がクラスの外に漏れ出しています。
それに、自分がルールを忘れずに守れてるならまだ良いですが、たとえば他の開発者がこのToDoListの設計を引き継いだ時、
「ToDoCollectionの最大の要素数は10個とする」という、あちこちに散らばった、不文律的なルールは、果たして守られるでしょうか?
上記のAddToDoメソッドのように、オブジェクトの保持するオブジェクトを直接外から操作せず、保持しているオブジェクトに操作を依頼するようにすることで、変更が限られた範囲で済み、オブジェクトのメンテナンス性が上がります。
これをデメテルの法則といいます。
デメテルの法則
デメテルの法則では、メソッドを呼び出していいオブジェクトが以下の4つに限られています。
- オブジェクト自身
- オブジェクトに引数として渡されたオブジェクト
- オブジェクトの持つインスタンス変数(フィールド)
- オブジェクトが直接インスタンス化したオブジェクト
前章の例のAddToDoメソッドは、オブジェクト(ToDoCollection)自身が呼び出すメソッドなので、デメテルの法則に則っています。
逆に、ToDoListプロパティの持つIList型のAddメソッドを利用するのは、オブジェクト(ToDoCollection)の持つフィールド(ToDoList)のメソッドをオブジェクト外から呼び出すことになり、こちらはデメテルの法則に違反しています。
おわりに
DDDの勉強中にデメテルの法則が出てきて 「これ指摘された奴や!」 と思いましたし、それ以来設計ノウハウとして気を付けるようにしていました。れっきとした法則なのは知りませんでしたが……。
XXX.YYY.ZZZ.....みたいな感じでカンマが数珠つなぎになっているコードがあったら、「変な呼び出し方してない!?」と一度振り返ってみてもいいかもしれないな、と思いました。
参考文献
『ドメイン駆動設計入門(成瀬 允宣 著)』