【SOLID原則】依存性逆転の原則とは何か
依存性逆転の原則とは
依存性逆転の原則(DIP: Dependency Inversion Principle
SOLIDのDにあたる部分ですね。
高水準のモジュールは、低水準のモジュールに依存すべきではなく、両方のモジュールは、抽象化に依存すべきであるという原則です。
つまり、依存関係を逆転させ、低レベルのモジュールが高レベルのモジュールに依存するようにすることが重要です。
高レベルのモジュールは、その詳細な実装に依存することなく、抽象化されたインターフェースを使用して低レベルのモジュールとやり取りすることができます。
このようにすることで下位モジュールの変更が上位モジュールに影響をあたることを防ぐことができます。
悪いコード(原則に反してい
以下のコードでは、ArticleControllerがArticleService、そしてArticleServiceがArticleRdbRepositoryに依存しています。
これは依存性逆転の原則に違反しています。
class Article {}
class ArticleController {
private articleService = new ArticleService();
create(article: Article): Article {
return this.articleService.create(article);
}
findById(id: string): Article {
return this.articleService.findById(id);
}
}
class ArticleService {
private articleRepository = new ArticleRdbRepository();
create(article: Article): Article {
return this.articleRepository.create(article);
}
findById(id: string): Article {
return this.articleRepository.findById(id);
}
}
class ArticleRdbRepository {
create(article: Article): Article {
console.log("記事を登録");
return article;
}
findById(id: string): Article {
console.log(`${id}の記事を検索`);
return new Article();
}
}
上述したように原則に異版しているため、以下の問題を孕んでいます
- 拡張性の低下
依存性逆転の原則に違反したコードでは、新しい機能を追加する場合に影響を受ける範囲が広くなります。たとえば、ArticleController
がArticleService
に依存している場合、新しいサービスを追加する場合には、すべてのArticleController
を変更する必要があります。 - テストの困難さ
依存性逆転の原則に違反したコードでは、モジュールを単体でテストすることが困難になる場合があります。たとえば、ArticleController
を単体でテストする場合、ArticleService
とArticleRdbRepository
も含めてテストする必要があります。
良いコード(原則に則している)
以下の方法を用いることで抽象化した概念に対して依存するので、上位モジュールが下位モジュールに依存することがなくなる
-
インターフェースを使用する
上位モジュールは、抽象化されたインターフェースを使用して下位モジュールとやり取りするべきです。これにより、下位モジュールの具体的な実装に依存することなく、上位モジュールを拡張することができます。 -
依存関係注入を使用する
依存関係注入(DI: Dependency Injection)を使用することで、上位モジュールが下位モジュールに依存することを回避することができます。依存関係注入では、上位モジュールが下位モジュールを作成するのではなく、上位モジュールに下位モジュールのインスタンスを注入することにより、依存関係を解決します。
class Article {}
interface IArticleService {
create(article: Article): Article;
findById(id: string): Article;
}
class ArticleController {
constructor(private articleService: IArticleService) {
this.articleService = articleService;
}
create(article: Article): Article {
return this.articleService.create(article);
}
findById(id: string): Article {
return this.articleService.findById(id);
}
}
interface IArticleRepository {
create(article: Article): Article;
findById(id: string): Article;
}
class ArticleService implements IArticleService {
constructor(private articleRepository: IArticleRepository) {
this.articleRepository = articleRepository;
}
create(article: Article): Article {
return this.articleRepository.create(article);
}
findById(id: string): Article {
return this.articleRepository.findById(id);
}
}
class ArticleRdbRepository implements IArticleRepository {
create(article: Article): Article {
console.log("記事を登録");
return article;
}
findById(id: string): Article {
console.log(`${id}の記事を検索`);
return new Article();
}
}
Discussion