この記事を書く理由
オブジェクト指向について勉強していると、DIPについて記載があったのですが、いまいち理解できなかったので、絵で理解してみようと思いました。
結論(自論)
依存性の逆転というより、依存先を具体から抽象へ切り替える法則なのでは??
DIPについて調べていくうちに、このような結論に至りました。
こんなかたにおすすめ
・この結論を見て興味を持ってくれたかた
・よくあるDIPの説明で納得できていないかた
では、さっそく説明していきます。
よくあるDIPの説明
DIP(Dependency Inversion Principle:依存性逆転の原則)は、オブジェクト指向設計のSOLID原則の1つで、上位モジュール(ビジネスロジック)が下位モジュール(具体的な実装)に直接依存せず、両者が「抽象(インターフェース)」に依存する設計原則です。これにより、部品の差し替えやテストが容易になります。
AIに聞くとこのような答えが返ってきました。
上位モジュール → 下位モジュール となっていたのが
上位モジュール → 抽象 ← 下位モジュール となるよ ということなんですが、
これを見て、私は 依存関係が逆転???してなくない?? と感じてしまいましたが、私だけでしょうか?
また、DIPについて初めて調べたときは文字だけの説明でよく分からない・・となったので、これから絵で説明していきます。
DIPを絵で理解してみる
今回は、机の発注という例でDIPについて説明します。
絵で理解しやすいよう、Service層をサラリーマン、Repository層を職人と例えることにしました。
最後に、ちゃんとコードを書くのでまずはイラストから概念を押さえていきましょう!
木の机を発注しよう
とあるサラリーマンが机を発注しようとしています。
木の机が欲しいので、職人名簿から木材加工が得意な人を調べます。
今回は田中さんに依頼することに決めました。
すると田中さんは、快く机を作って納品してくれました。
めでたし。めでたし。
ステンレスの机を発注しよう
今度は、ステンレスの机を発注する必要があるようです。
また、サラリーマンは職人名簿を引っ張ってきて、職人を探します。
今回は、鈴木さんに依頼することに決めました。
サラリーマン「鈴木さん。机を作ってください!」
すると鈴木さんは、快く机を作って納品してくれました。
めでたし。めでたし
サラリーマン思う・・
ここでサラリーマンは、思います。
毎回、毎回職人を調べて発注するの面倒くさいな と。
これだと、机の種類が変わるたびにサラリーマンは発注先を変えないといけないので、とても手間がかかります。
何か良い方法はないのでしょうか???
ここで出てくるのが、DIP(Dependency Inversion Principle:依存性逆転の原則) です。
ここが便利だよDIP
DIPとは、具体から抽象へ依存を切り替えるという手法です。
抽象というのは、ただ仕様を決めるもので、発注書のようなものです。
発注書(抽象)を追加することで、サラリーマンは 素材を指定して机を発注する、 これだけを行えばいいことになります。
もう職人を調べる手間から解放されます!
そして、発注書に該当する職人が机を作成して納品してくれます。
こうして、無事にサラリーマンは職人を調べることなく机の発注ができるようになりました。
なんとなくDIPの仕組みやメリットについて理解いただけたでしょうか?
実際のコードとして理解する
イラストだけだとぼんやりとした理解になってしまうので、最後にコードとして理解しましょう。
DIPしていない場合(職人に直接依存)
まずは、木材の机を注文します。
<?php
class Desk
{
public function __construct(
public string $material
) {}
}
// 木材職人
class WoodDeskRepository
{
public function createDesk(): Desk
{
return new Desk('wood');
}
}
class SalarymanService
{
private WoodDeskRepository $repository;
public function __construct()
{
// 今は木材職人に固定
$this->repository = new WoodDeskRepository();
}
public function orderDesk(): Desk
{
return $this->repository->createDesk();
}
}
// 実行
$service = new SalarymanService();
$desk = $service->orderDesk();
echo "素材: {$desk->material}\n"; //wood
次に、ステンレスの机を発注しましょう。
class SalarymanService
{
- private WoodDeskRepository $repository;
+ private MetalDeskRepository $repository;
public function __construct()
{
- // 木材職人に依頼
- $this->repository = new WoodDeskRepository();
+ // 金属職人に依頼(ステンレスに変更)
+ $this->repository = new MetalDeskRepository();
}
public function orderDesk(): Desk
{
return $this->repository->createDesk();
}
}
// 金属職人
class MetalDeskRepository implements DeskRepositoryInterface
{
public function createDesk(): Desk
{
return new Desk('stainless');
}
}
金属職人に変更したいだけなのに、Service層を変更しないといけません。
これは、上位のモジュール(Service層)が下位のモジュール(Repository層)に直接依存してしまっています。
この結果、素材を変更するたびにService層を変更する必要があり、変更コストが高い設計となってしまいます。
そこで、interfaceを挟み依存を具体から抽象に変更しましょう。
DIPした場合
<?php
class Desk
{
public function __construct(
public string $material
) {}
}
// 発注書(Interface)
interface DeskRepositoryInterface
{
public function createDesk(): Desk;
}
// 木材職人
class WoodDeskRepository implements DeskRepositoryInterface
{
public function createDesk(): Desk
{
return new Desk('wood');
}
}
// 金属職人
class MetalDeskRepository implements DeskRepositoryInterface
{
public function createDesk(): Desk
{
return new Desk('stainless');
}
}
class SalarymanService
{
private DeskRepositoryInterface $repository;
public function __construct(DeskRepositoryInterface $repository)
{
$this->repository = $repository;
}
public function orderDesk(): Desk
{
return $this->repository->createDesk();
}
}
// 実行(木材)
$service = new SalarymanService(new WoodDeskRepository());
$desk = $service->orderDesk();
echo "Material: {$desk->material}\n"; //wood
// 実行(金属)
$service = new SalarymanService(new MetalDeskRepository());
$desk = $service->orderDesk();
echo "Material: {$desk->material}\n"; //stainless
こうすることで、使用するRepositoryが変わってもService層の変更は不要になり、Servie本来の責務に集中することができるようになりました。
まとめ
DIPについて調べたときは、依存関係逆転してないんじゃない?どういうこと??と思ってしまいました。ただ、調べていくうちに 依存先を具体から抽象へ切り替える法則 という理解をすると自分の中ではスッキリと理解ができたような気がします。
正確な例えでもないかもしれませんが、DIP何それ?分からない!となっている方への理解の手助けになれば嬉しいですし、自分自身への理解を深めることができてよかったです。


