Подтвердить что ты не робот

Зависимость впрыска в функциональное программирование

Эта проблема лучше иллюстрируется примером. Я буду использовать Javascript (на самом деле Coffeescript для синтаксиса), но только потому, что Javascript - это еще один LISP, правильно?

Итак, предположим, что я пишу веб-приложение, которое делает (очевидно) ajax-запросы. Я реализую функцию для обработки этого:

ajaxRequest = (url, params, callback) ->
    # implementation goes here

Теперь предположим, что у меня есть сетка, которая извлекает данные с сервера. Где-то в моем коде я должен сделать что-то вроде этого:

userGrid.onMustFetch = ->
    ajaxRequest '/fetch/users', { surname: 'MacGyver' }, (data) ->
        # fill grid with data

В чем конкретно проблема? Если я хочу протестировать реализацию onMustFetch, я не смогу этого сделать, потому что внутри onMustFetch вызывается зависимость, и тестовая среда не может контролировать зависимость.

Чтобы решить эту проблему, я ввожу зависимость в функцию, которую я хочу проверить. Это означает изменение onMustFetch:

userGrid.onMustFetch = (ajaxRequest) ->
    ajaxRequest '/fetch/users', { surname: 'MacGyver' }, (data) ->
        # fill grid with data

Теперь тестовый код может передать макет ajaxRequest на onMustFetch и успешно протестировать поведение.

Вундербар, не так ли? Неправильно! Теперь у меня есть вторая проблема: проблема привязки правильного экземпляра ajaxRequest к правильному экземпляру onMustFetch.

На языке, подобном Java, я мог бы использовать инфраструктуру Injection Dependency для этого, и мой код будет выглядеть следующим образом:

class UserGrid {

    private AjaxService ajaxService;

    @Inject
    public UserGrid(AjaxService ajaxService) {
        this.ajaxService = ajaxService;
    }

    public void onMustFetch() {
        HashMap<String, String> params = new HashMap<String, String>();
        params.put("surname", "MacGyver");
        ajaxService.request("/fetch/users", params, new AjaxCallback(data) {
            // fill grid with data
        });
    }

}

Жутко, я знаю... но на самом деле каркас DI выполняет всю проводку, поэтому, по крайней мере, эта часть проблемы проще.

Теперь вернемся к нашему веб-приложению и Javascript. Даже если мне удастся всегда вызывать onMustFetch с правильной ссылкой ajaxRequest (ведь в этом случае это не так сложно сделать), должен быть более простой способ. Когда мой код растет, зависимости увеличиваются. Я могу представить передачу ссылки ajaxRequest, но как насчет того, когда у меня есть securityService, browserService, eventBusService и т.д. И т.д. И т.д.?

Теперь реальный вопрос: как lisp как языки решают эту проблему управления зависимостями? (Мне кажется, что зависимости должны быть переданы по всему приложению, но я уверен, что должен быть лучший способ...)

4b9b3361

Ответ 1

Обычно это делается с помощью закрытий. В JS вы можете сделать:

buildUserGrid = function(dependency){
    return {
        onMustFetch = function(){
            depenency.doSomething();
        },
        doSomethingElse = function(){
            dependency.doSomethingElse();
        }
    }
}

var userGrid = buildUserGrid(ajaxRequest);
userGrid.onMustFetch();

Ответ 2

В Javascript я не знаю, почему вы не могли использовать методы, подобные любому языку OO. Очень простая реализация в JS (извините, я не знаю Coffescript)

// expects a function 
var UserGrid = function(ajaxService) {
    this.dependencies = ["ajaxService"];
     // will be overwritten by the DI service, but can also be 
     // assigned manually as in java
    this.ajaxService = ajaxService;
};
UserGrid.prototype.onMustFetch=function() {
    var callback = function() { ... }
    this.ajaxService('/fetch/users',{ surname: 'MacGyver' }, callback);
};

var diController = {
    create: function(constr) {
        var obj = new constr();
        // just look at obj.dependencies to see what should be assigned, and map
        // the implemenations as properties of obj. this could be
        // as simple as a switch or a direct mapping of names to object types
        // ... assign implementations to obj
        return obj;
    }
};

Создать:

var userGrid = diController.create(UserGrid);

Итак, diController выполняет то же самое, что и ваш java-зависимый инжектор. В java он может просто выяснить, какой тип объекта нужен, используя отражение. В Javascript не так много отражения, поэтому создайте соглашение, чтобы сообщить системе, что нужно. В этом случае я использовал массив под названием "зависимости", но вы могли бы использовать любую конструкцию, которая вам нравится.