Menu
Coddy logo textTech

Внедрение зависимостей

Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 45 из 107.

Внедрение зависимостей (Dependency Injection) — это метод, при котором struct получает свои зависимости извне, а не создает их внутри себя. В Go интерфейсы делают этот паттерн естественным и мощным.

Вместо жесткого кодирования конкретной реализации внутри структуры, вы принимаете интерфейс. Это позволяет менять реализации без изменения кода структуры:

type Notifier interface {
    Send(message string) string
}

type EmailNotifier struct{}
func (e EmailNotifier) Send(message string) string {
    return "Email: " + message
}

type SMSNotifier struct{}
func (s SMSNotifier) Send(message string) string {
    return "SMS: " + message
}

Теперь создайте структуру, которая зависит от интерфейса, а не от конкретного типа:

type OrderService struct {
    notifier Notifier  // зависимость внедрена через интерфейс
}

func NewOrderService(n Notifier) *OrderService {
    return &OrderService{notifier: n}
}

func (o *OrderService) PlaceOrder(item string) string {
    return o.notifier.Send("Order placed: " + item)
}

OrderService не знает и не заботится о том, использует ли он email или SMS. Вы внедряете зависимость при создании сервиса:

func main() {
    emailService := NewOrderService(EmailNotifier{})
    fmt.Println(emailService.PlaceOrder("Book"))  // Email: Заказ размещен: Book
    
    smsService := NewOrderService(SMSNotifier{})
    fmt.Println(smsService.PlaceOrder("Phone"))   // SMS: Заказ размещен: Phone
}

Этот подход делает ваш код более гибким и тестируемым. Во время тестирования вы можете внедрить mock-уведомитель, который на самом деле не отправляет сообщения. В рабочей среде вы внедряете реальную реализацию. Структура (struct) остается неизменной в обоих сценариях.

challenge icon

Задание

Легко

Давайте создадим систему логирования, которая демонстрирует мощь внедрения зависимостей (dependency injection). Вы создадите сервис, который может записывать логи в разные места назначения, при этом сам сервис не будет знать и не будет заботиться о том, куда именно попадают эти логи.

Вы организуете свой код в трех файлах:

  • logger.go: Определите интерфейс Logger, который требует метод Log(message string) string. Затем создайте две различные реализации логгера:
    • ConsoleLogger с полем Prefix — его метод Log возвращает [CONSOLE] [Prefix]: [message]
    • FileLogger с полем Filename — его метод Log возвращает [FILE:[Filename]] [message]
  • service.go: Создайте структуру AppService, которая зависит от интерфейса Logger (а не от конкретного типа). Включите функцию-конструктор NewAppService, которая принимает Logger и возвращает указатель на AppService. Добавьте AppService метод DoWork(task string) string, который использует внедренный логгер для записи сообщения Processing: [task] и возвращает то, что возвращает логгер.
  • main.go: Считайте конфигурацию из входных данных, создайте оба типа логгеров, внедрите каждый из них в отдельные экземпляры AppService и вызовите DoWork для каждого сервиса с указанной задачей. Выведите результат каждого вызова сервиса.

Будут предоставлены следующие входные данные:

  • Строка 1: Префикс консольного логгера
  • Строка 2: Имя файла логгера
  • Строка 3: Задача для обработки

Например, если даны INFO, app.log и user authentication, ваш вывод должен быть:

[CONSOLE] INFO: Processing: user authentication
[FILE:app.log] Processing: user authentication

Обратите внимание, что AppService не знает, записывает ли он данные в консоль или в файл — он просто вызывает Log у того логгера, который был внедрен. Эта гибкость и есть суть внедрения зависимостей: один и тот же код сервиса работает с совершенно разными реализациями логирования.

Шпаргалка

Внедрение зависимостей (Dependency Injection) — это метод, при котором структура получает свои зависимости извне, а не создает их внутри себя. В Go интерфейсы делают этот паттерн естественным и мощным.

Определите интерфейс для зависимости:

type Notifier interface {
    Send(message string) string
}

Создайте конкретные реализации интерфейса:

type EmailNotifier struct{}
func (e EmailNotifier) Send(message string) string {
    return "Email: " + message
}

type SMSNotifier struct{}
func (s SMSNotifier) Send(message string) string {
    return "SMS: " + message
}

Создайте структуру, которая зависит от интерфейса, а не от конкретного типа:

type OrderService struct {
    notifier Notifier  // зависимость внедряется через интерфейс
}

func NewOrderService(n Notifier) *OrderService {
    return &OrderService{notifier: n}
}

func (o *OrderService) PlaceOrder(item string) string {
    return o.notifier.Send("Order placed: " + item)
}

Внедрите зависимость при создании сервиса:

emailService := NewOrderService(EmailNotifier{})
fmt.Println(emailService.PlaceOrder("Book"))  // Email: Order placed: Book

smsService := NewOrderService(SMSNotifier{})
fmt.Println(smsService.PlaceOrder("Phone"))   // SMS: Order placed: Phone

Этот подход делает код более гибким и тестируемым, позволяя заменять различные реализации без изменения кода самой структуры.

Попробуйте сами

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Чтение префикса консольного логгера
	prefix, _ := reader.ReadString('\n')
	prefix = prefix[:len(prefix)-1]
	
	// Чтение имени файла для файлового логгера
	filename, _ := reader.ReadString('\n')
	filename = filename[:len(filename)-1]
	
	// Чтение задачи для обработки
	task, _ := reader.ReadString('\n')
	if len(task) > 0 && task[len(task)-1] == '\n' {
		task = task[:len(task)-1]
	}
	
	// TODO: Создать ConsoleLogger с указанным префиксом
	
	// TODO: Создать FileLogger с указанным именем файла
	
	// TODO: Создать AppService с внедренным ConsoleLogger
	
	// TODO: Создать еще один AppService с внедренным FileLogger
	
	// TODO: Вызвать DoWork для каждого сервиса с задачей и вывести результаты
	fmt.Println("result1")
	fmt.Println("result2")
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование