Jump to content

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

Схема типичного контейнера внедрения зависимостей для платформы .NET.
Внедрение зависимостей часто используется вместе со специализированными платформами, известными как «контейнеры», для облегчения композиции программ.

В разработке программного обеспечения внедрение зависимостей — это метод программирования, при котором объект или функция получает другие объекты или функции, которые ему необходимы, а не создают их внутри себя. Внедрение зависимостей направлено на разделение проблем создания объектов и их использования, что приводит к слабосвязанным программам. [1] [2] [3] Шаблон гарантирует, что объект или функция, желающие использовать данный сервис, не должны знать, как создавать эти сервисы. Вместо этого принимающий « клиент » (объект или функция) получает свои зависимости от внешнего кода («инжектора»), о котором он не знает. [4] Внедрение зависимостей делает неявные зависимости явными и помогает решить следующие проблемы: [5]

  • Как класс может быть независимым от создания объектов, от которых он зависит?
  • Как приложение и объекты, которые оно использует, могут поддерживать разные конфигурации?

Внедрение зависимостей часто используется для обеспечения соответствия кода принципу инверсии зависимостей . [6] [7]

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

Платформы приложений часто сочетают внедрение зависимостей с инверсией управления . При инверсии управления платформа сначала создает объект (например, контроллер), а затем передает поток управления ему . При внедрении зависимостей платформа также создает экземпляры зависимостей, объявленных объектом приложения (часто в параметрах метода конструктора), и передает зависимости в объект. [8]

Внедрение зависимостей реализует идею «инвертирования контроля над реализациями зависимостей», поэтому некоторые платформы Java в общем называют концепцию «инверсией управления» (не путать с инверсией потока управления ). [9]

Внедрение зависимости для пятилетних детей

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

Вам следует заявить о своей потребности: «Мне нужно что-нибудь выпить за обедом», и тогда мы позаботимся о том, чтобы у вас было что-нибудь, когда вы сядете, чтобы что-нибудь съесть.

Джон Манш, 28 октября 2009 г. [2] [10] [11]

Внедрение зависимостей включает в себя четыре роли: сервисы, клиенты, интерфейсы и инжекторы.

Услуги и клиенты

[ редактировать ]

Сервис — это любой класс, который содержит полезную функциональность. В свою очередь, клиент — это любой класс, использующий сервисы. Услуги, которые требуются клиенту, являются его зависимостями .

Любой объект может быть сервисом или клиентом; имена относятся только к той роли, которую объекты играют во время инъекции. Один и тот же объект может быть одновременно клиентом (он использует внедренные сервисы) и сервисом (он внедряется в другие объекты). клиента После внедрения сервис становится частью состояния , доступным для использования. [12]

Интерфейсы

[ редактировать ]

Клиенты не должны знать, как реализованы их зависимости, только их имена и API . служба, которая получает электронную почту Например, IMAP или POP3 , может незаметно использовать протоколы , но эта деталь, вероятно, не имеет отношения к вызывающему коду, который просто хочет получить электронное письмо. Игнорируя детали реализации, клиентам не нужно изменяться вместе с их зависимостями.

Форсунки

[ редактировать ]

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

Роль инжекторов заключается в построении и соединении сложных графов объектов, где объектами могут быть как клиенты, так и сервисы. Сам инжектор может представлять собой множество объектов, работающих вместе, но не должен быть клиентом, поскольку это создаст циклическую зависимость .

Поскольку внедрение зависимостей отделяет способ создания объектов от способа их использования, это часто снижает важность new Ключевое слово встречается в большинстве объектно-ориентированных языков . Поскольку платформа занимается созданием сервисов, программист склонен напрямую конструировать только объекты значений , которые представляют сущности в предметной области программы (например, Employee объект в бизнес-приложении или Order объект в приложении для покупок). [13] [14] [15] [16]

Аналогия

[ редактировать ]

По аналогии автомобили можно рассматривать как услуги, выполняющие полезную работу по перевозке людей из одного места в другое. Автомобильным двигателям может потребоваться бензин , дизельное топливо или электричество , но эта деталь не важна для клиента — водителя, которого волнует только то, сможет ли он доставить их к месту назначения.

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

Преимущества и недостатки

[ редактировать ]

Преимущества

[ редактировать ]

Основное преимущество внедрения зависимостей — уменьшение связи между классами и их зависимостями. [17] [18]

Если клиент не знает, как реализуются его зависимости, программы становятся более пригодными для повторного использования, тестирования и сопровождения. [19]

Это также приводит к повышению гибкости: клиент может действовать с чем угодно, что поддерживает внутренний интерфейс, который он ожидает. [20]

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

Наконец, внедрение зависимостей позволяет осуществлять параллельную разработку. Два разработчика могут независимо разрабатывать классы , использующие друг друга, при этом им нужно только знать интерфейс, через который классы будут взаимодействовать. Плагины часто разрабатываются сторонними организациями, которые даже не общаются с разработчиками исходного продукта. [21]

Тестирование

[ редактировать ]

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

Например, внедрение зависимостей можно использовать для переноса деталей конфигурации системы в файлы конфигурации, что позволяет переконфигурировать систему без перекомпиляции. Отдельные конфигурации могут быть написаны для разных ситуаций, требующих разных реализаций компонентов. [22]

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

Эта простота тестирования часто является первым преимуществом, которое замечают при использовании внедрения зависимостей. [23]

Недостатки

[ редактировать ]

Критики внедрения зависимостей утверждают, что это:

  • Создает клиентов, которым требуются подробности конфигурации, что может быть обременительно, если доступны очевидные значения по умолчанию. [21]
  • Усложняет отслеживание кода, поскольку он отделяет поведение от построения. [21]
  • Обычно реализуется с помощью отражения или динамического программирования, что затрудняет автоматизацию IDE . [24]
  • Обычно требуется больше предварительных усилий по разработке. [25]
  • Поощряет зависимость от фреймворка. [26] [27] [28]

Типы внедрения зависимостей

[ редактировать ]

Существует три основных способа получения клиентом инъекционных услуг: [29]

В некоторых средах клиентам вообще не нужно активно принимать внедрение зависимостей. В Java , например, отражение может сделать частные атрибуты общедоступными при тестировании и непосредственном внедрении сервисов. [30]

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

[ редактировать ]

В следующем Java примере Client класс содержит Service Переменная-член, инициализированная в конструкторе . Клиент напрямую создает и контролирует, какой сервис он использует, создавая жестко запрограммированную зависимость.

public class Client {
    private Service service;

    Client() {
        // The dependency is hard-coded.
        this.service = new ExampleService();
    }
}

Инъекция конструктора

[ редактировать ]

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

public class Client {
    private Service service;

    // The dependency is injected through a constructor.
    Client(Service service) {
        if (service == null) {
            throw new IllegalArgumentException("service must not be null");
        }
        this.service = service;
    }
}

Инъекция сеттера

[ редактировать ]

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

public class Client {
    private Service service;

    // The dependency is injected through a setter method.
    public void setService(Service service) {
        if (service == null) {
            throw new IllegalArgumentException("service must not be null");
        }
        this.service = service;
    }
}

Внедрение интерфейса

[ редактировать ]

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

Таким образом, зависимости становятся инжекторами. Ключевым моментом является то, что метод внедрения предоставляется через интерфейс.

Ассемблер по-прежнему необходим для представления клиента и его зависимостей. Ассемблер берет ссылку на клиента, передает ее интерфейсу установки, который устанавливает эту зависимость, и передает ее этому объекту зависимости, который, в свою очередь, передает ссылку на самого себя обратно клиенту.

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

public interface ServiceSetter {
    void setService(Service service);
}

public class Client implements ServiceSetter {
    private Service service;

    @Override
    public void setService(Service service) {
        if (service == null) {
            throw new IllegalArgumentException("service must not be null");
        }
        this.service = service;
    }
}

public class ServiceInjector {
	private final Set<ServiceSetter> clients = new HashSet<>();

	public void inject(ServiceSetter client) {
		this.clients.add(client);
		client.setService(new ExampleService());
	}

	public void switch() {
		for (Client client : this.clients) {
			client.setService(new AnotherExampleService());
		}
	}
}

public class ExampleService implements Service {}

public class AnotherExampleService implements Service {}

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

public class Program {

    public static void main(String[] args) {
        // Build the service.
        Service service = new ExampleService();

        // Inject the service into the client.
        Client client = new Client(service);

        // Use the objects.
        System.out.println(client.greet());
    }	
}

Ручное строительство может быть более сложным и требует участия строителей , фабрик или других строительных структур .

Диаграмма классов контейнеров внедрения зависимостей в .NET Framework.
Контейнеры, такие как Ninject или StructureMap, обычно используются в объектно-ориентированных языках программирования для реализации внедрения зависимостей и инверсии управления .

Ручное внедрение зависимостей часто утомительно и подвержено ошибкам для крупных проектов, поэтому рекомендуется использовать платформы, которые автоматизируют этот процесс. Ручное внедрение зависимостей становится платформой внедрения зависимостей , как только код создания больше не является индивидуализированным для приложения и вместо этого становится универсальным. [31] Хотя эти инструменты и полезны, они не требуются для выполнения внедрения зависимостей. [32] [33]

Некоторые фреймворки, такие как Spring , могут использовать внешние файлы конфигурации для планирования композиции программы:

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class Injector {

	public static void main(String[] args) {
		// Details about which concrete service to use are stored in configuration separate from the program itself.
		BeanFactory beanfactory = new ClassPathXmlApplicationContext("Beans.xml");
		Client client = (Client) beanfactory.getBean("client");
		System.out.println(client.greet());
	}
}

Даже при потенциально длинном и сложном графе объектов единственный класс, упомянутый в коде, — это точка входа, в данном случае Client. Client не претерпел никаких изменений для работы со Spring и остается POJO . [34] [35] [36] Благодаря тому, что специфичные для Spring аннотации и вызовы не распределяются по многим классам, система остается лишь слабо зависимой от Spring. [27]

В следующем примере показан компонент AngularJS, получающий службу приветствия посредством внедрения зависимостей.

function SomeClass(greeter) {
  this.greeter = greeter;
}

SomeClass.prototype.doSomething = function(name) {
  this.greeter.greet(name);
}

Каждое приложение AngularJS содержит локатор сервисов, отвечающий за создание и поиск зависимостей.

// Provide the wiring information in a module
var myModule = angular.module('myModule', []);

// Teach the injector how to build a greeter service. 
// greeter is dependent on the $window service.
myModule.factory('greeter', function($window) {
  return {
    greet: function(text) {
      $window.alert(text);
    }
  };
});

Затем мы можем создать новый инжектор, который предоставляет компоненты, определенные в myModule модуль, включая службу приветствия.

var injector = angular.injector(['myModule', 'ng']);
var greeter = injector.get('greeter');

Чтобы избежать антишаблона локатора сервисов , AngularJS допускает декларативную нотацию в шаблонах HTML, которая делегирует создание компонентов инжектору.

<div ng-controller="MyController">
  <button ng-click="sayHello()">Hello</button>
</div>
function MyController($scope, greeter) {
  $scope.sayHello = function() {
    greeter.greet('Hello World');
  };
}

The ng-controller Директива запускает инжектор для создания экземпляра контроллера и его зависимостей.

В этом образце представлен пример внедрения конструктора в C# .

using System;

namespace DependencyInjection;

// Our client will only know about this interface, not which specific gamepad it is using.
interface IGamepadFunctionality {
    string GetGamepadName();
    void SetVibrationPower(float power);
}

// The following services provide concrete implementations of the above interface.

class XBoxGamepad : IGamepadFunctionality {
    float vibrationPower = 1.0f;
    
    public string GetGamepadName() => "Xbox controller";
    
    public void SetVibrationPower(float power) => this.vibrationPower = Math.Clamp(power, 0.0f, 1.0f);
}

class PlaystationJoystick : IGamepadFunctionality {
    float vibratingPower = 100.0f;
    
    public string GetGamepadName() => "PlayStation controller";
    
    public void SetVibrationPower(float power) => this.vibratingPower = Math.Clamp(power * 100.0f, 0.0f, 100.0f);
}

class SteamController : IGamepadFunctionality {
    double vibrating = 1.0;
    
    public string GetGamepadName() => "Steam controller";
    
    public void SetVibrationPower(float power) => this.vibrating = Convert.ToDouble(Math.Clamp(power, 0.0f, 1.0f));
}

// This class is the client which receives a service.
class Gamepad {
    IGamepadFunctionality gamepadFunctionality;

    // The service is injected through the constructor and stored in the above field.
    public Gamepad(IGamepadFunctionality gamepadFunctionality) => this.gamepadFunctionality = gamepadFunctionality;

    public void Showcase() {
        // The injected service is used.
        var gamepadName = this.gamepadFunctionality.GetGamepadName();
        var message = $"We're using the {gamepadName} right now, do you want to change the vibrating power?";
        Console.WriteLine(message);
    }
}

class Program {
    static void Main() {
        var steamController = new SteamController();
        
        // We could have also passed in an XboxController, PlaystationJoystick, etc.
        // The gamepad doesn't know what it's using and doesn't need to.
        var gamepad = new Gamepad(steamController);
        
        gamepad.Showcase();
    }
}

Go не поддерживает классы, и обычно внедрение зависимостей абстрагируется специальной библиотекой, использующей отражение , или дженериками (последнее поддерживается с Go 1.18). [37] ). [38] Более простой пример без использования библиотек внедрения зависимостей иллюстрируется следующим примером веб-приложения MVC .

Сначала передаем необходимые зависимости маршрутизатору, а затем от маршрутизатора контроллерам:

package router

import (
	"database/sql"
	"net/http"

	"example/controllers/users"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"

	"github.com/redis/go-redis/v9"
	"github.com/rs/zerolog"
)

type RoutingHandler struct {
	// passing the values by pointer further down the call stack
	// means we won't create a new copy, saving memory
	log    *zerolog.Logger
	db     *sql.DB
	cache  *redis.Client
	router chi.Router
}

// connection, logger and cache initialized usually in the main function
func NewRouter(
	log *zerolog.Logger,
	db *sql.DB,
	cache *redis.Client,
) (r *RoutingHandler) {
	rtr := chi.NewRouter()

	return &RoutingHandler{
		log:    log,
		db:     db,
		cache:  cache,
		router: rtr,
	}
}

func (r *RoutingHandler) SetupUsersRoutes() {
	uc := users.NewController(r.log, r.db, r.cache)

	r.router.Get("/users/:name", func(w http.ResponseWriter, r *http.Request) {
		uc.Get(w, r)
	})
}

Затем вы можете получить доступ к закрытым полям структуры любым методом, который является получателем указателя , без нарушения инкапсуляции.

package users

import (
	"database/sql"
	"net/http"

	"example/models"

	"github.com/go-chi/chi/v5"
	"github.com/redis/go-redis/v9"
	"github.com/rs/zerolog"
)

type Controller struct {
	log     *zerolog.Logger
	storage models.UserStorage
	cache   *redis.Client
}

func NewController(log *zerolog.Logger, db *sql.DB, cache *redis.Client) *Controller {
	return &Controller{
		log:     log,
		storage: models.NewUserStorage(db),
		cache:   cache,
	}
}

func (uc *Controller) Get(w http.ResponseWriter, r *http.Request) {
	// note that we can also wrap logging in a middleware, this is for demonstration purposes
	uc.log.Info().Msg("Getting user")

	userParam := chi.URLParam(r, "name")

	var user *models.User
	// get the user from the cache
	err := uc.cache.Get(r.Context(), userParam).Scan(&user)
	if err != nil {
		uc.log.Error().Err(err).Msg("Error getting user from cache. Retrieving from SQL storage")
	}

	user, err = uc.storage.Get(r.Context(), "johndoe")
	if err != nil {
		uc.log.Error().Err(err).Msg("Error getting user from SQL storage")
		http.Error(w, "Internal server error", http.StatusInternalServerError)
		return
	}
}

Наконец, вы можете использовать соединение с базой данных, инициализированное в вашей основной функции на уровне доступа к данным:

package models

import (
"database/sql"
"time"
)

type (
	UserStorage struct {
		conn *sql.DB
	}

	User struct {
		Name     string `json:"name" db:"name,primarykey"`
		JoinedAt time.Time `json:"joined_at" db:"joined_at"`
		Email    string `json:"email" db:"email"`
	}
)

func NewUserStorage(conn *sql.DB) *UserStorage {
	return &UserStorage{
		conn: conn,
	}
}

func (us *UserStorage) Get(name string) (user *User, err error) {
	// assuming 'name' is a unique key
	query := "SELECT * FROM users WHERE name = $1"

	if err := us.conn.QueryRow(query, name).Scan(&user); err != nil {
		return nil, err
	}

	return user, nil
}

См. также

[ редактировать ]
  1. ^ Зееманн, Марк. «Внедрение зависимостей — это слабая связь» . blog.ploeh.dk . Проверено 28 июля 2015 г.
  2. ^ Перейти обратно: а б Симан, Марк (октябрь 2011 г.). Внедрение зависимостей в .NET . Публикации Мэннинга. п. 4. ISBN  9781935182504 .
  3. ^ Нико Шварц, Мирча Лунгу, Оскар Нирстраз, «Сьюз: разделение обязанностей от статических методов для детальной настройки», Журнал объектных технологий, том 11, вып. 1 (апрель 2012 г.), стр. 3:1–23.
  4. ^ «Голливудский принцип» . c2.com . Проверено 19 июля 2015 г.
  5. ^ «Шаблон проектирования внедрения зависимостей — проблема, решение и применимость» . w3sDesign.com . Проверено 12 августа 2017 г.
  6. ^ Эрез, Гай (09 марта 2022 г.). «Инверсия зависимостей против внедрения зависимостей» . Середина . Проверено 06 декабря 2022 г.
  7. ^ Мэтьюз, Саша (25 марта 2021 г.). «Вы просто внедряете зависимость, думая, что следуете инверсии зависимости…» . Середина . Проверено 06 декабря 2022 г.
  8. ^ «Spring IoC-контейнер» . Проверено 23 мая 2023 г.
  9. ^ Фаулер, Мартин. «Инверсия управляющих контейнеров и шаблон внедрения зависимостей» . Мартин Фаулер.com . Проверено 4 июня 2023 г.
  10. ^ «Внедрение зависимостей в NET» (PDF) . philkildea.co.uk . п. 4. Архивировано из оригинала (PDF) 21 июля 2015 г. Проверено 18 июля 2015 г.
  11. ^ «Как объяснить введение зависимости пятилетнему ребенку?» . stackoverflow.com . Проверено 18 июля 2015 г.
  12. ^ ИТ, Титаниум. «Джеймс Шор: демистификация внедрения зависимостей» . www.jamesshore.com . Проверено 18 июля 2015 г.
  13. ^ «К «новому» или не к «новому»…» . Архивировано из оригинала 13 мая 2020 г. Проверено 18 июля 2015 г.
  14. ^ «Как писать тестируемый код» . www.loosecouplings.com . Проверено 18 июля 2015 г.
  15. ^ «Написание чистого, тестируемого кода» . www.ethanresnick.com . Проверено 18 июля 2015 г.
  16. ^ Сирони, Джорджио. «Когда делать инъекции: различие между новыми и инъекционными препаратами – невидимо для глаза» . www.giorgiosironi.com . Проверено 18 июля 2015 г.
  17. ^ «Городской канук, ага: о внедрении зависимостей и нарушении проблем инкапсуляции» . www.bryancook.net . Проверено 18 июля 2015 г.
  18. ^ «Шаблон проектирования внедрения зависимостей» . msdn.microsoft.com . Проверено 18 июля 2015 г.
  19. ^ Перейти обратно: а б «Программа Java Community Process (SM) — JSR: запросы спецификаций Java — подробно JSR # 330» . jcp.org . Проверено 18 июля 2015 г.
  20. ^ «3.1. Внедрение зависимостей — Python 3: от отсутствия к машинному обучению» . Архивировано из оригинала 08 февраля 2020 г.
  21. ^ Перейти обратно: а б с «Как работает внедрение зависимостей (DI) при разработке Java-приложений Spring — DZone Java» .
  22. ^ «Внедрение зависимостей и инверсия управления в Python — документация Dependency Injector 4.36.2» .
  23. ^ «Как провести рефакторинг для внедрения зависимостей, часть 3: Большие приложения» .
  24. ^ «Краткое введение в внедрение зависимостей: что это такое и когда его использовать» . 18 октября 2018 г.
  25. ^ «Внедрение зависимостей | Professionalqa.com» .
  26. ^ «Каковы недостатки использования внедрения зависимостей?» . stackoverflow.com . Проверено 18 июля 2015 г.
  27. ^ Перейти обратно: а б «Инверсия внедрения зависимостей – чистый программист» . сайты.google.com . Проверено 18 июля 2015 г.
  28. ^ «Отделение вашего приложения от инфраструктуры внедрения зависимостей» . ИнфоQ . Проверено 18 июля 2015 г.
  29. ^ Мартин Фаулер (23 января 2004 г.). «Инверсия управляющих контейнеров и шаблон внедрения зависимостей — формы внедрения зависимостей» . Мартинфаулер.com . Проверено 22 марта 2014 г.
  30. ^ «ДоступныйОбъект (Платформа Java SE 7)» . docs.oracle.com . Проверено 18 июля 2015 г.
  31. ^ Риле, Дирк (2000), Каркасное проектирование: подход к ролевому моделированию (PDF) , Швейцарский федеральный технологический институт.
  32. ^ «Внедрение зависимостей!= с использованием DI-контейнера» . www.loosecouplings.com . Проверено 18 июля 2015 г.
  33. ^ «Паршивая овца» DIY-DI » Печать» . blacksheep.parry.org . Архивировано из оригинала 27 июня 2015 г. Проверено 18 июля 2015 г.
  34. ^ «Советы Spring: POJO с аннотациями не является простым» . Архивировано из оригинала 15 июля 2015 г. Проверено 18 июля 2015 г.
  35. ^ «Аннотации в POJO – благо или проклятие? | Techtracer» . 07.04.2007 . Проверено 18 июля 2015 г.
  36. ^ Динамические модули Pro Spring для сервисных платформ OSGi . Пресс. 17 февраля 2009 г. ISBN  9781430216124 . Проверено 6 июля 2015 г.
  37. ^ «Примечания к выпуску Go 1.18 — язык программирования Go» . go.dev . Проверено 17 апреля 2024 г.
  38. ^ «Потрясающий Go – внедрение зависимостей» . Гитхаб . 17 апреля 2024 г. . Проверено 17 апреля 2024 г.
[ редактировать ]
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: fb67f64eb8ef6f627edbcc02634a31c1__1714131300
URL1:https://arc.ask3.ru/arc/aa/fb/c1/fb67f64eb8ef6f627edbcc02634a31c1.html
Заголовок, (Title) документа по адресу, URL1:
Dependency injection - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)