классу A нужен экземпляр класса B. Должен ли я создать экземпляр B внутри конструктора A или создать экземпляр, в котором инициируется класс A, и передать экземпляр конструктору A?
План А:
class A { constructor() { this.b = new B(); } }
План B:
const b = new B();
class A() {
constructor(b) {
this.b = b;
}
}
const a = new A(b);
Я бы сказал план Б, так как он будет полезен для управления инъекциями зависимостей
план Б очень полезен]
Как было сказано, это всего лишь вопрос мнения. Я действительно зависит от того, как эти классы будут использоваться. Например, будет ли когда-нибудь полезно для вызывающего A передавать что-либо, кроме B? Нужно ли им вообще взаимодействовать с B?
На этот вопрос невозможно ответить должным образом, потому что вы спрашиваете о чем-то, где подход будет полностью зависеть от контекста.
Так что, пожалуйста, вы можете привести примеры с разными контекстами и сценариями.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Ваш план Б - определенно лучший подход, потому что он делает ваш class A более гибким и устраняет зависимости от того, как работает class B.
План А подходит, если
Класс B не следует заменять на класс C в будущем.
Класс B не использует какую-либо внешнюю зависимость, такую как ajax, база данных или что-то еще.
ИЛИ
План Б хорош во всех остальных случаях.
Это даст вам гибкость для изменения поведения вашего класса A на лету, предоставит большую универсальность, поможет с тестированием, высмеивая эту зависимость.
Итак, я бы сказал, что план А - о простоте, план Б - о гибкости и тестировании. Выбирайте то, что вам нужно в каждом конкретном случае
Надеюсь это поможет.
Первая стратегия подходит, когда у обоих классов есть отношения, в которых они бессмысленны друг без друга в их точной форме, а дочерние экземпляры не имеют особых черт, которые в противном случае сделали бы их ручное предоставление каких-либо преимуществ.
Последняя стратегия внедрения зависимостей класса гораздо более гибкая. Это более очевидно в типизированном языке, где вы можете четко определить абстракции классов или интерфейсы, которые описывают, как объект «выглядит», а не фактическую реализацию, например с TypeScript:
class Process {
private storage: StorageInterface;
constructor(storage: StorageInterface) {
this.storage = storage;
}
}
interface StorageInterface {
store(data: any): Promise<void>;
}
Здесь ясно, что Process ожидает экземпляр чего-то, что предоставляет функцию store, но не заботится о том, как выглядит фактическая реализация - это остается на усмотрение класса, реализующего StorageInterface:
class PostgresStorage implements StorageInterface {
public async store(data: any): Promise<void> {
// do some postgres query
}
}
class FilesystemStorage implements StorageInterface {
public async store(data: any): Promise<void> {
// store in the filesystem
}
}
Теперь у нас есть полная гибкость в отношении того, какой тип «хранилища» мы хотим использовать для экземпляра Process, а не управление самим хранилищем, которое он может использовать, возможность создавать бесконечные типы хранилищ без изменения остальной части кодовой базы и многое другое. ясность относительно того, с чем взаимодействует Process.
// A process that stores something in a Postgres instance.
const processA: Process = new Process(new PostgresStorage());
// A process that stores something on the filesystem.
const processB: Process = new Process(new FilesystemStorage());
Объектно-ориентированный подход немного отличается в мире JS от строго типизированных классических языков. Возможно, это изменение платформы - отличная возможность пересмотреть свое мышление о том, как повторно использовать и подумать более нестандартно, имея в виду композицию.
В идеале класс A должен создать экземпляр класса B внутри, если он этого требует. Коду, который использует класс A, не нужно знать, какая зависимость нужна этому классу.