← Назад к вопросам
Что такое dependency injection?
2.0 Middle🔥 192 комментариев
#JavaScript Core#Архитектура и паттерны
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое Dependency Injection
Dependency Injection (DI) — это паттерн проектирования, который решает проблему жестких связей между компонентами. Вместо того, чтобы создавать зависимости внутри компонента, мы передаем их извне.
Проблема без DI
Представь компонент, который создает свои зависимости:
// Плохо: зависимость создается внутри
class UserService {
constructor() {
this.database = new Database(); // Создание внутри
this.logger = new Logger(); // Создание внутри
}
getUser(id) {
const user = this.database.query(`SELECT * FROM users WHERE id = ${id}`);
this.logger.log(`User fetched: ${id}`);
return user;
}
}
const service = new UserService();
Проблемы:
- Сложно тестировать — нельзя подменить Database на тестовую версию
- Жесткая связь — UserService зависит от конкретных реализаций Database и Logger
- Сложно менять реализацию — если захочешь использовать другую БД, нужно менять UserService
Решение: Dependency Injection
Передаем зависимости извне:
// Хорошо: зависимости передаются
class UserService {
constructor(database, logger) {
this.database = database; // Получена как параметр
this.logger = logger; // Получена как параметр
}
getUser(id) {
const user = this.database.query(`SELECT * FROM users WHERE id = ${id}`);
this.logger.log(`User fetched: ${id}`);
return user;
}
}
// Использование
const realDatabase = new PostgresDatabase();
const realLogger = new ConsoleLogger();
const service = new UserService(realDatabase, realLogger);
Преимущества:
- Легко тестировать — подменяем зависимости на тестовые
- Слабая связь — UserService не знает о конкретных реализациях
- Гибкость — легко менять реализации
Три способа DI
1. Constructor Injection (через конструктор)
class PaymentService {
constructor(stripe, notificationService) {
this.stripe = stripe;
this.notificationService = notificationService;
}
processPayment(amount) {
const result = this.stripe.charge(amount);
this.notificationService.send("Payment processed");
return result;
}
}
// Использование
const paymentService = new PaymentService(
new StripeGateway(),
new EmailNotificationService()
);
2. Property Injection (через свойства)
class ReportGenerator {
generate() {
return this.database.fetchData();
}
}
const generator = new ReportGenerator();
generator.database = new MySQLDatabase(); // Установка после создания
const report = generator.generate();
3. Method Injection (через методы)
class FileProcessor {
process(data, logger) { // logger — зависимость
logger.info("Processing started");
// ...
logger.info("Processing finished");
}
}
const processor = new FileProcessor();
processor.process(data, new ConsoleLogger());
DI в React
Context API для DI
// Создаем контекст для зависимостей
const ApiContext = React.createContext();
// Провайдер зависимостей
function App() {
const apiService = new ApiService("https://api.example.com");
const authService = new AuthService();
return (
<ApiContext.Provider value={{ apiService, authService }}>
<MainContent />
</ApiContext.Provider>
);
}
// Компонент получает зависимости из контекста
function UserList() {
const { apiService } = useContext(ApiContext);
const [users, setUsers] = useState([]);
useEffect(() => {
apiService.fetchUsers().then(setUsers);
}, [apiService]);
return <div>{users.map(u => <div>{u.name}</div>)}</div>;
}
Custom Hook для DI
function useUserService() {
const [userService] = useState(() => {
return new UserService(
new ApiRepository(),
new LocalStorageCache()
);
});
return userService;
}
function UserProfile({ userId }) {
const userService = useUserService();
const [user, setUser] = useState(null);
useEffect(() => {
userService.getUser(userId).then(setUser);
}, [userId, userService]);
return <div>{user?.name}</div>;
}
Props Injection (самый простой способ)
function UserList({ userService }) {
const [users, setUsers] = useState([]);
useEffect(() => {
userService.fetchAll().then(setUsers);
}, [userService]);
return (
<ul>
{users.map(u => <li key={u.id}>{u.name}</li>)}
</ul>
);
}
// Использование
function App() {
const userService = new UserService();
return <UserList userService={userService} />;
}
Пример: Тестирование с DI
// Сервис
class OrderService {
constructor(paymentGateway, emailService) {
this.paymentGateway = paymentGateway;
this.emailService = emailService;
}
async createOrder(userId, items) {
const amount = items.reduce((sum, item) => sum + item.price, 0);
await this.paymentGateway.charge(userId, amount);
await this.emailService.sendConfirmation(userId);
return { id: "order-123", status: "confirmed" };
}
}
// Тест
describe("OrderService", () => {
it("should charge payment and send email", async () => {
// Mock зависимости
const mockPaymentGateway = {
charge: jest.fn().mockResolvedValue(true)
};
const mockEmailService = {
sendConfirmation: jest.fn().mockResolvedValue(true)
};
const service = new OrderService(
mockPaymentGateway,
mockEmailService
);
const result = await service.createOrder(1, [{price: 100}]);
expect(mockPaymentGateway.charge).toHaveBeenCalledWith(1, 100);
expect(mockEmailService.sendConfirmation).toHaveBeenCalledWith(1);
expect(result.status).toBe("confirmed");
});
});
DI контейнеры (Advanced)
Для больших приложений используют DI контейнеры:
class DIContainer {
constructor() {
this.services = {};
}
register(name, factory) {
this.services[name] = factory;
}
get(name) {
const factory = this.services[name];
if (!factory) throw new Error(`Service ${name} not found`);
return factory(this);
}
}
// Использование
const container = new DIContainer();
container.register("database", () => new PostgresDatabase());
container.register("logger", () => new ConsoleLogger());
container.register("userService", (c) => {
return new UserService(
c.get("database"),
c.get("logger")
);
});
const userService = container.get("userService");
Принципы DI
- Инверсия управления — компонент не создает свои зависимости
- Слабая связанность — компоненты независимы друг от друга
- Гибкость — легко менять реализации
- Тестируемость — можно подменять зависимости в тестах
Когда использовать DI
- Всегда — для сервисов, репозиториев, утилит
- В тестах — подменяй зависимости на mock и
- В больших приложениях — используй DI контейнеры
Заключение
Dependency Injection — это мощный паттерн, который делает код более гибким, тестируемым и легким в поддержке. Вместо того, чтобы компонент создавал свои зависимости, мы передаем их извне, что позволяет легко менять реализации и тестировать код.