Dependecy Injection

Сразу оговорюсь что здесь я буду пытаться структурировать свои познания, причём делать это в процессе своей же учёбы. Так что ошибок тут будет тьма, столько же моего недопонимания и неверных выводов, так что если вы каким-то чудом набрели на эту статью то не в коем случае не принимайте ничего из нижесказанного за правду. Я пишу всё это исключительно для себя.

О каких зависимостях идёт речь? О тех самых которые нужны программе и её частям чтобы работать, собственно это и есть её части. Возьмём простой пример:


class DB {
  posts = [];

  save(post){
    console.log(post);
    this.posts.push(post);
  }

  getPosts(){
    return this.posts;
  }
}

class Post{
  static id = 0;

  constructor(title, message){

    this.id = this.constructor.id++;
    this.message = message;
  }
}

db = new DB();
post = new Post('First Post', 'BLA-BLA-BLA');
db.save(post);

2 класса - Класс хранилища (DB) и класс сущности (Post), как видно из кода я просто хочу создавать объекты Post и складывать их в свою недоБД. Всё просто, 3 последних строчки это делают. И всё это в одном месте, дальше мне бы хотелось сгруппировать и разнести классы по какму-нибудь признаку, например отдельно сущность (Post) отдельно БД и отдельно сам код который создаёт экземпляры этих классов и отдельно код который уже начинает с ними работать, этот код в дальнейшем мы будем называть Сервис. Сервис является фактически интерфейсом доступа ко всем объектам(модулям, подсистемам). Он принимает сигналы и в соответствии со своей логикой оперирует сущностями на уровне ниже. Всё это нужно для того чтобы можно было легко добавить новую сущность, новый тип БД, и.т.д. На всякий случай сам себе напомню что объект можно рассматривать как функцию которая принимает сигналы(методы и данные) и выполняет диспетеризацию внутри себя. Добавлю в код сервис:


class PostService{
  constructor(DB, Post){
    this.db = new DB;
    this.Post = Post;
  }

  addPost(title, message){
    const post = new this.Post(title, message);
    this.db.save(post);
    return post.id;
  }

  removePost(postId){
    this.db.removePost(postId);
  }

  getPosts(){
    return this.db.getPosts();
  }
}

Ну и сам "пользовательский" код будет выглядеть так:


const app = new PostService(DB, Post);
app.addPost('First Post', 'Content of the First post');
app.addPost('Second Post', 'Content of the Second post');
app.getPosts();


Здесь можно заметить что в конечном итоге мы имеем глобальный объект app который знает о остальных "подсистемах" программы и обращается к ним. При таком виде код будет проще дополнять, менять и модифицировать.

Дальше я попробую расширить приложение чтобы оно начало работать с несколькими блогами. Значит надо ввести хранилище для блогов и сущность блога, а так-же можно выделить отдельно сервис для работы с блогами.


class BlogDB {
  blogs = [];

  save(blog){
    this.blogs.push(blog);
  }

  getBlogs(){
    return this.blogs;
  }

  removeBlog(id){
    this.blogs = this.blogs.filter(blog => blog.id !== id);
  }
}

class Blog{
  static id = 0;

  constructor(name){
    this.id = this.constructor.id++;
    this.name = name;
  }
}

class BlogService{
  constructor(BlogDB, PostDB, Blog){
    this.BlogDB = new BlogDB;
    this.PostDB = ///??????????????????
    this.Blog = Blog;
  }

  addBlog(name){
    const blog = new this.Blog(name);
    this.db.save(blog);
    return blog.id;
  }

  removeBlog(blogId){
    this.db.removeBlog(blogId);
  }

  getBlogs(){
    return this.db.getBlogs();
  }

  getBlogPosts(blogId){
    ///////////Как обратиться к Хранилищу постов???
  }
}

Конечно же первое что тут напрашивается это как-то следовать принципу DRY(Не повторяйся) и применить механизм наследования, т.к. больно уж сущности, хранилище и сервисы похожи друг на друга, но тут не это главное, а главное то как нам обратиться из сервиса работы с блогами к хранилищу постов. Для этого изменим код конструктора PostService таким образом чтобы передавать в него уже готовый объект, а не создавать его в конструкторе сервиса:


  constructor(postDB, Post){
    this.postDB = postDB;
    this.Post = Post;
  }
Ну и то же самое с BlogService:


  constructor(blogDB, postDB, Blog){
    this.blogDB = blogDB;
    this.postDB = postDB
    this.Blog = Blog;
  }

Ну и при инициализации программы создаём все необходимые объекты и передаём их в конструкторы сервисов:


const postDB = new PostDB();
const blogDB = new BlogDB();
const blogService = new BlogService(blogDB, postDB, Blog);
const postService = new PostService(postDB, Post);

const myOldBlog = blogService.addBlog('Old blog');

postService.addPost('First Post', 'Content of the First post', myOldBlog.id);
postService.addPost('Second Post', 'Content of the Second post', myOldBlog.id);

const myNewBlog = blogService.addBlog('New blog');

postService.addPost('This is my new blog', 'Megacontent', myNewBlog.id);
postService.addPost('Its me again', 'Supercontent', myNewBlog.id);

console.log(postService.getPostsByBlogId(myOldBlog.id));// Все посты блога myOldBlog
console.log(postService.getPostsByBlogId(myNewBlog.id));//Все посты блога myNewBlog

Также зависимости можно хранить в отдельном глобальном объекте:


const locator = {
  BlogKlas: Blog,
  PostKlas: Post,
  postDB: new PostDB(),
  blogDB: new BlogDB(),
};

И при описании сервисов ссылаться на locator.BlogKlas вместо this.Blog и locator.postDB вместо this.postDB соответственно, такой подход называется Service Locator.

Дальше я хотел бы разобрать использование паттерна Dependecy Injector Container на примере библиотеки bottle.js. Ну и еще раз написать что если вы каким-то образом это читаете не воспринимайте это всерьёз. В тексте могут быть фундаментальные заблуждения.

Продолжение

Комментарии

Популярные сообщения из этого блога

Загрузка CPU на Cisco Catalyst 4500 и Cat4k Mgmt LoPri

Пользовательские параметры в Zabbix (UserParameter)

Функторы в JavaScript