Pytanie Wstrzyknięcie zależności w programowaniu funkcjonalnym


Ten problem lepiej ilustruje przykład. Użyję JavaScript (w rzeczywistości Coffeescript dla sake składni), ale tylko dlatego, że JavaScript jest po prostu inny SEPLENIENIE, dobrze?

Załóżmy więc, że piszę aplikację internetową, która ma (oczywiście) prośby ajaxowe. Wdrażam funkcję do obsługi tego:

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

Teraz załóżmy, że mam siatkę, która pobiera dane z serwera. Gdzieś w moim kodzie muszę zrobić coś takiego:

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

Jaki jest dokładnie problem? Jeśli chcę przetestować wdrożenie onMustFetch, Nie będę w stanie tego zrobić, ponieważ w środku onMustFetch, wywoływana jest zależność, a środowisko testowe nie może kontrolować zależności.

Aby rozwiązać ten problem, wprowadzam zależność do funkcji, którą chcę przetestować. To oznacza zmianę onMustFetch do tego:

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

Teraz kod testowy może przekazać fałszywy przykład ajaxRequest do onMustFetch i pomyślnie przetestować zachowanie.

Wunderbar, prawda? Źle! Teraz mam drugi problem, problem związany z właściwym wystąpieniem ajaxRequest na właściwy przykład onMustFetch.

W języku takim jak Java, mógłbym użyć Dependency Injection framework, aby zrobić to dla mnie, a mój kod wyglądałby tak:

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
        });
    }

}

Creepy, wiem ... ale w rzeczywistości struktura DI wykonuje całe okablowanie, więc przynajmniej ta część problemu jest łatwiejsza.

Wróćmy teraz do naszej aplikacji internetowej i do Javascript. Nawet jeśli uda mi się zawsze wywołać onMustFetch z prawą ajaxRequest odniesienie (w tym przypadku nie jest to takie trudne), musi istnieć łatwiejszy sposób. Kiedy mój kod rośnie, zwiększają się zależności. Mogę sobie wyobrazić przekazanie odniesienia ajaxRequest dookoła, ale co, kiedy mam securityService, a browserService, a eventBusServiceitp. itp.?

Teraz prawdziwe pytanie: w jaki sposób języki typu "lisp" rozwiązują ten problem zarządzania zależnościami? (Wydaje mi się, że zależności muszą być przekazywane w całej aplikacji, ale jestem pewien, że musi być lepszy sposób ...)


12
2018-04-17 19:04


pochodzenie


Języki podobne do Lispa różnią się od siebie. Mają różne systemy obiektowe, itp. To naprawdę dotyczy Javascript: jak zaimplementować wzorzec projektowania Java (Dependency Injection) w JavaScript. Usuwam tagi niezwiązane z tematem [clojure] [lisp] i [scheme]. - Kaz
Tu nie chodzi o javascript. JavaScript jest po prostu taki, jaki znam, aby wyrazić problem. Interesuje mnie sposób, w jaki języki lisp radzą sobie z problemem, więc byłbym najbardziej wdzięczny, gdybyś włożył tagi z powrotem. Zapraszam do przeformułowania pytania w sposób, który ma sens dla Lisp, Clojure lub Scheme. - RobotFoo


Odpowiedzi:


Zazwyczaj robi się to przy użyciu zamknięć. W JS możesz:

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

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

5
2017-09-17 06:28





W JavaScript nie wiem, dlaczego nie można używać technik podobnych do języka OO. Bardzo prosta implementacja w JS (przepraszam, nie znam 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;
    }
};

Stwórz:

var userGrid = diController.create(UserGrid);

Więc diController robi to samo, co wtryskiwacz zależności Java. W języku Java może po prostu dowiedzieć się, jaki typ obiektu jest potrzebny przy użyciu odbicia. Nie ma wiele refleksji do zrobienia w JavaScript, więc stwórz konwencję, aby powiedzieć systemowi, co jest potrzebne. W tym przypadku użyłem tablicy o nazwie "dependencies", ale możesz użyć dowolnego konstruktu, który ci się podoba.


1
2018-04-17 20:01



Tak, mógłbym użyć techniki, którą chciałbym zastosować w OO. Ale moje pytanie - i zainteresowanie - dotyczy tego, w jaki sposób języki funkcjonalne, w szczególności języki lisp, zarządzają zależnościami. Mam świadomość, że może to nie było wystarczająco jasne w moim pytaniu, ale przeczytaj mój komentarz do tego pytania i zrozumiesz dlaczego. W każdym razie dzięki... - RobotFoo
W takim przypadku myślę, że początkowy komentarz jest bardzo istotny. Lub, czy pytasz, jak zarządzać zależnościami bez, powiedzmy, pojęcia globaliów lub fabryki? Dlaczego chcesz to zrobić? Dostępne narzędzia do tworzenia takich konstrukcji są bardzo specyficzne dla języka. Bez jakiejkolwiek globalnej konstrukcji, to już odpowiedziałeś na własne pytanie, będziesz przekazywać rzeczy dookoła. - Jamie Treworgy
Rzeczywiście, opcje, które otrzymuję do tej pory, to zastrzyk zależności, wspólne globale, fabryka lub przekazywanie rzeczy. Próbowałem dowiedzieć się, co zwykle robi się w językach podobnych do Lispa. Więc może masz rację, może nie ma innej odpowiedzi na moje pytanie w kontekście, w którym ją stawiam. - RobotFoo