О внедрении зависимостей (машинопись)

внешний интерфейс

1. Концепции: внедрение зависимостей (DI), инверсия управления (IOC), контейнеры IOC.

Внедрение зависимостей (DI) и инверсия управления (IOC) в основном означают одно и то же, потому что они неотделимы друг от друга. Говоря простым языком, класс A зависит от класса B, но A не контролирует создание и уничтожение B, а только использует B, затем управление B передается A для обработки, что называется Inversion of Control (IOC) . Поскольку A зависит от B, экземпляр B должен использоваться в A. Мы можем внедрить экземпляр B через конструктор A, например:

class B { }
class A {
  constructor(b: B) { 
      console.log(b);
  }
}
const b = new B();
// 将B的实例注入到a中
const a = new A(b);

Этот процесс называется внедрением зависимостей (DI). Так что же такое IOC-контейнер? В только что приведенном примере процесс внедрения экземпляра B в конструктор A выполняется вручную, что довольно хлопотно, особенно когда отношения между классами становятся более сложными и трудными в обслуживании. Таким образом, IOC-контейнер должен решить эту проблему.Контейнер IOC отвечает за управление жизненным циклом, зависимостями и т. д. объекта, а также за реализацию поиска зависимостей и внедрения зависимостей объекта. Например, Java Spring и инжектор зависимостей (DI) внешнего интерфейса @Angular принадлежат контейнеру IOC.

Далее я сравню преимущества использования внедрения зависимостей по сравнению с внедрением без зависимостей в виде кода.

2. Код внедрения без зависимостей

Давайте сначала посмотрим на традиционный код реализации (не DI). автомобиль.тс

// 引擎 
export class Engine {
  public cylinders = '引擎发动机1';
}
// 轮胎
export class Tires {
  public make = '品牌';
}
export class Car {
  public engine: Engine;
  public tires: Tires;
  public description = 'No DI'; 
  constructor() {
    this.engine = new Engine();
    this.tires = new Tires();
  }
  // Method using the engine and tires
  drive() {
    return `${this.description} car with ` +
      `${this.engine.cylinders} cylinders and ${this.tires.make} tires.`;
  }
}

В приведенном выше коде класс Car не использует сторонний контейнер, а создает двигатель и некоторые шины.Этот код имеет высокую степень связанности, что вызовет следующие проблемы:

Вопрос 1: Если двигатель будет обновлен в один прекрасный день, код выглядит следующим образом:

// 引擎  
export class Engine {
  public cylinders = '';
  constructor(_cylinders:string) {
    this.cylinders = _cylinders;
  }
}

При создании двигателя вам нужно передать параметр, затем вам нужно изменить новый двигатель (параметр) в классе автомобилей, что приведет к уничтожению класса автомобилей.Вот, пожалуйста, подумайте над вопросом: как обновить двигатель Когда не нужно модифицировать класс Car? (Ответ: ДИ)

Вопрос 2: Если вы хотите использовать на своем автомобиле шины другой марки, используйте следующий код:

// 轮胎
export class Tires {
  public make = '品牌';
}
export class Tires1 extends Tires {
  public make = '品牌1';
}
export class Tires2 extends Tires {
  public make = '品牌2';
}
export class Car {
   //。。。。。。其他代码省略。。。。。。。
  public tires: Tires;
  constructor() {
    this.tires = new Tires1();
  }
}

В это время код Автомобиля должен быть повторно изменен.Пожалуйста, подумайте над вопросом здесь: что можно сделать, чтобы Автомобилю не нужно было модифицировать класс Авто при смене шин других разных марок? (Ответ: ДИ)

Вопрос 3: Как реализовать совместное использование данных, например Интернет транспортных средств, создать центр обработки данных Службы, различные автомобили осуществляют передачу данных и обмен данными через Службу, если это осуществляется посредством новой Службы в автомобиле, это невозможно реализовать обмен данными и связь Да, потому что Услуги в разных Автомобилях не являются одним и тем же экземпляром.

Здесь, пожалуйста, подумайте над вопросом: как реализовать передачу данных и совместное использование разных автомобилей?

Вопрос 4: Тест сложнее и его вообще невозможно протестировать. В примере кода класс Car зависит от класса Engine и класса Tyres, а Engine и Tires могут зависеть от других классов, а другие классы могут иметь больше собственных зависимостей. Поскольку вы не можете контролировать скрытые зависимости позади Car, его сложно тестировать, или следует сказать, что такой код вообще невозможно протестировать. Например, если вы хотите протестировать работоспособность автомобилей с разными марками колес одновременно, потому что нового в машине прописано до смерти, этого делать нельзя. Например, если вы хотите проверить работоспособность автомобиля двигателя с разными параметрами одновременно, потому что новое в машине прописано до смерти, этого делать нельзя. Если вы не тестируете только один случай за раз, вот пример тестирования колес разных марок: Сначала протестируйте колеса Марки 1: автомобиль.тс

export class Tires {
  public make = '品牌';
}
export class Tires1 extends Tires {
  public make = '品牌1';
}
export class Tires2 extends Tires {
  public make = '品牌2';
}
export class Car {
  public tires: Tires;
  public description = 'No DI'; 
  constructor() {
    // new 一个品牌1的轮子
    this.tires = new Tires1();
  }
  // Method using the engine and tires
  drive() {
    return `${this.description} car with ` + ` ${this.tires.make} tires.`;
  }
}

Программа испытаний car.spec.ts

import { Car } from './car.ts';

describe('Car类单元测试', function () {
  it('测试品牌1轮子的Car的驾驶性能', function () {
    const car = new Car();
    car.drive().should.equal('No DI car with 品牌1 tires.');
  })
})

Вышеприведенный код проверяет марку колес 1 и выводит ходовые качества автомобиля с маркой колес 1. Затем протестируйте марку колеса 2: измените класс Car и измените this.tires = new Tires1(); на this.tires = new Tires2(); В это время выводятся ходовые качества автомобиля с колесной маркой 2.

Эффективность такого теста очень низкая, так как вручную одновременно можно тестировать только один случай, а если добавить движковый тест, то будет больше смешанных случаев, и автоматическое тестирование вообще нельзя будет проводить. Тестирование заключается в одновременной записи всех условий в модульный тест, и после запуска все условия будут проверены.Когда тест пройден, это означает, что код соответствует ожиданиям.

В ответ на вышеупомянутые проблемы, давайте взглянем на преимущества использования DI.

3. Используйте внедрение зависимостей (DI)

Далее мы продемонстрируем использование DI для решения вышеуказанных 4 проблем. Сначала взгляните на код car.ts, реализованный с использованием DI: автомобиль.тс

export class Engine {
  public cylinders = '引擎发动机1';
}
export class Tires {
  public make = '品牌';
}
export class Tires1 extends Tires {
  public make = '品牌1';
}
export class Tires2 extends Tires {
  public make = '品牌2';
}
export class Car {
  public description = 'DI'; 
  // 通过构造函数注入Engine和Tires
  constructor(public engine: Engine, public tires: Tires) {}  
  // Method using the engine and tires
  drive() {
    return `${this.description} car with ` +
      `${this.engine.cylinders} cylinders and ${this.tires.make} tires.`;
  }
}

В приведенном выше коде автомобиль создается путем передачи двигателя и шин в конструктор. Класс Car больше не создает двигатель и шины сам по себе, а потребляет их. Самым большим преимуществом на данный момент является то, что двигатель и шины отделены от автомобиля. . Когда новый автомобиль, вы можете указать любой тип двигателя и шин, т.е. пусть автомобиль = новый автомобиль(новый двигатель(),новые шины());

Решите проблему 1: если движок будет обновлен в один прекрасный день, код будет следующим:

export class Engine {
  public cylinders = '';
  constructor(_cylinders:string) {
    this.cylinders = _cylinders;
  }
}

При создании движка нужно передать параметр, на данный момент модифицировать класс Car не нужно, нужно модифицировать только основную программу:

Основной программный код:

main(){
    const car = new Car(new Engine('引擎启动机2'), new Tires1());
    car.drive();
}

Решите проблему 2: Если вы хотите использовать шины разных марок на автомобиле, код выглядит следующим образом:

export class Tires {
  public make = '品牌';
}
export class Tire1 extends Tires {
  public make = '品牌1';
}
export class Tire2 extends Tires {
  public make = '品牌2';
}
export class Car {
   //。。。。。。其他代码省略。。。。。。。
  constructor(public engine: Engine, public tires: Tires) {}  
}

В настоящее время нет необходимости изменять класс Car, необходимо изменить только основную программу: Основной программный код:

main(){
  // 使用品牌2的轮胎
  const car = new Car(new Engine('引擎启动机2'), new Tires2());
  car.drive();
}

Решите проблему 3: как реализовать совместное использование данных, такое как Интернет транспортных средств, создать центр обработки данных службы (например, уровень службы angular, который может совместно использоваться несколькими компонентами), и различные автомобили реализуют обмен данными и совместное использование данных через службу. код показывает, как показано ниже: Сервис.тс

export class Service {
  public data = '';
  // 向Service存数据
  setData(_data: string) {
    this.data = _data;
  }
  // 从Service中取数据
  getData() {
    return this.data;
  }
}

car.ts

export class Car {
  constructor(public service: Service) { }
  // 向Service存数据
  setDataToService(_data: string) {
    this.service.setData(_data);
  }
  // 从Service中取数据
  getDataFromService() {
    return this.service.getData();
  }
}

Основная программа выглядит следующим образом: Основной программный код:

main(){
  // 创建一个共享服务中心Service
  const shareService = new Service();
  const car1 = new Car(shareService);
  const car2 = new Car(shareService);
  // car1向服务中心存数据
  car1.setDataToService('this data is from car1.');
  // car2从服务中心取数据
  car2.getDataFromService();
}

Решение проблемы 4: тестовые случаи В примере кода класс Car зависит от класса Engine и класса Tyres, а Engine и Tires могут зависеть от других классов, а другие классы могут иметь свои собственные зависимости.В такой послойной зависимости используйте DI Код для тестирования относительно прост. Процедура тестирования следующая: Программа испытаний car.spec.ts

import { Car,Engine,Tires1, Tires2} from './car.ts';
// 测试程序入口
describe('Car类单元测试', function () {
  const engine1 = new Engine('引擎发动机1');
  const engine2 = new Engine('引擎发动机2');
  const tires1 = new Tires1();
  const tires2 = new Tires2();

  it('测试引擎1 轮胎品牌1', function () {
    const car = new Car(engine1, tires1);
    car.drive().should.equal('DI car with 引擎发动机1 cylinders and 品牌1 tires.');
  });
  it('测试引擎1 轮胎品牌2', function () {
    const car = new Car(engine1, tires2);
    car.drive().should.equal('DI car with 引擎发动机1 cylinders and 品牌2 tires.');
  });
  it('测试引擎2 轮胎品牌1', function () {
    const car = new Car(engine2, tires1);
    car.drive().should.equal('DI car with 引擎发动机2 cylinders and 品牌1 tires.');
  });
  it('测试引擎2 轮胎品牌2', function () {
    const car = new Car(engine2, tires2);
    car.drive().should.equal('DI car with 引擎发动机2 cylinders and 品牌2 tires.');
  });
    
})

В это время я чувствую, что это здорово и нет дров.Идея автоматического тестирования такова.После настройки кода для всех ситуаций, один раз запустив, все ситуации можно протестировать.

На данный момент, если вы понимаете вышеизложенное, идея DI и почему вы должны использовать DI должны быть понятны.