web-dev-qa-db-fra.com

Comment l'inversion de dépendance est-elle liée aux fonctions d'ordre supérieur?

Aujourd'hui, je viens de voir cet article qui décrivait la pertinence du principe SOLID dans F # Développement-

F # et principes de conception - solide

Et tout en abordant le dernier - "principe d'inversion de dépendance", l'auteur a déclaré:

D'un point de vue fonctionnel, ces conteneurs et concepts d'injection peuvent être résolus avec une simple fonction d'ordre supérieur ou un motif de type trou dans le milieu qui sont construits directement dans la langue.

Mais il n'a pas expliqué plus loin. Donc, ma question est de savoir comment l'inversion de dépendance est-elle liée aux fonctions d'ordre supérieur?

41
Gulshan

Inversion de dépendance IN OOP signifie que vous codez contre une interface qui est ensuite fournie par une implémentation dans un objet.

Les langues prenant en charge les fonctions de langue supérieure peuvent souvent résoudre des problèmes d'inversion de dépendance simples en passant le comportement en fonction d'une fonction au lieu d'un objet qui implémente une interface dans le sens de l'OO.

Dans de telles langues, la signature de la fonction peut devenir l'interface et une fonction est transmise au lieu d'un objet traditionnel pour fournir le comportement souhaité. Le trou dans le modèle de milieu est un bon exemple pour cela.

Il suffit d'obtenir le même résultat avec moins de code et plus d'expressivité, car vous n'avez pas besoin de mettre en œuvre une classe entière conforme à une interface (OOP) pour fournir le comportement souhaité à l'appelant. Au lieu de cela, vous pouvez simplement passer une définition de fonction simple. En bref: le code est souvent plus facile à maintenir, plus expressif et plus flexible lorsque l'on utilise des fonctions d'ordre supérieur.

n exemple en C #

Approche traditionnelle:

public IEnumerable<Customer> FilterCustomers(IFilter<Customer> filter, IEnumerable<Customers> customers)
{
    foreach(var customer in customers)
    {
        if(filter.Matches(customer))
        {
            yield return customer;
        }
    }
}

//now you've got to implement all these filters
class CustomerNameFilter : IFilter<Customer> /*...*/
class CustomerBirthdayFilter : IFilter<Customer> /*...*/

//the invocation looks like this
var filteredDataByName = FilterCustomers(new CustomerNameFilter("SomeName"), customers);
var filteredDataBybirthDay = FilterCustomers(new CustomerBirthdayFilter(SomeDate), customers);

Avec des fonctions d'ordre supérieur:

public IEnumerable<Customer> FilterCustomers(Func<Customer, bool> filter, IEnumerable<Customers> customers)
{
    foreach(var customer in customers)
    {
        if(filter(customer))
        {
            yield return customer;
        }
    }
}

Maintenant, la mise en œuvre et l'invocation deviennent moins lourdes. Nous n'avons plus besoin de fournir une implémentation ifilter. Nous n'avons plus besoin de mettre en œuvre des cours pour les filtres.

var filteredDataByName = FilterCustomers(x => x.Name.Equals("CustomerName"), customers);
var filteredDataByBirthday = FilterCustomers(x => x.Birthday == SomeDateTime, customers);

Bien sûr, cela peut déjà être fait par Linq en C #. Je viens d'utiliser cet exemple pour illustrer qu'il est plus facile et plus flexible d'utiliser des fonctions d'ordre supérieur au lieu d'objets qui implémentent une interface.

38
Falcon

Si vous voulez changer le comportement d'une fonction

doThis(Foo)

vous pourriez passer une autre fonction

doThisWith(Foo, anotherFunction)

qui implémente le comportement que vous voulez être différent.

"Dothiswith" est une fonction d'ordre supérieur car elle prend une autre fonction comme argument.

Par exemple, vous pourriez avoir

storeValues(Foo, writeToDatabase)
storeValues(Foo, imitateDatabase)
8
LennyProgrammers

Réponse courte:

Injection de dépendance classique/inversion du contrôle utilise une interface de classe en tant qu'artierholder pour une fonctionnalité dépendante. Cette interface est mise en œuvre par une classe.

Au lieu d'interface/classique, de nombreuses dépendances peuvent être faciles à mettre en œuvre avec une fonction déléguée.

Vous trouvez un exemple pour les deux en C # à IOC-usine-pros-et-contras-for-interface-interface-contre-délégués .

5
k3b

Piggy-Backing Off of LennyProgrammers Exemple ...

L'une des choses que les autres exemples manquaient est que vous pouvez utiliser des fonctions d'ordre supérieur avec une application de fonction partielle (PFA) pour lier (ou "injecter") dépendances dans une fonction (via sa liste d'arguments) pour créer une nouvelle fonction.

Si au lieu de:

doThisWith(Foo, anotherFunction)

nous (pour être conventionnel dans la manière dont p.f.a. habituellement est terminé) ont la fonction de travailleur de niveau bas comme (échange d'ordre argore):

doThisWith( anotherFunction, Foo )

Nous pouvons alors appliquer partiellement Dothisvoie comme si:

doThis = doThisWith( anotherFunction )  // note that "Foo" is still missing, argument list is partial

Qui nous permettent plus tard d'utiliser la nouvelle fonction comme si:

doThis(Foo)

Ou même:

doThat = doThisWith( yetAnotherDependencyFunction )
...
doThat( Bar )

Voir aussi: https://ramdajs.com/docs/#partial

... et, ouais, additionneurs/multiplicateurs sont des exemples inimaginants. Un meilleur exemple serait une fonction qui prend des messages et les enregistre ou les transmet en fonction de la fonction "consommateur" passée sous forme de dépendance.

Extension de cette idée, des listes d'arguments encore plus longues peuvent être progressivement réduites à des fonctions de plus en plus spécialisées avec des listes d'arguments plus courtes et plus courtes, et bien sûr, toutes ces fonctions peuvent être transmises à d'autres fonctions à titre de dépendances à appliquer partiellement.

OOP est agréable si vous avez besoin d'un paquet de choses avec de multiples opérations étroitement liées, mais cela se transforme en tâches pour faire un tas de classes chacune avec une méthode unique "FAIT" Public "Le Royaume des noms".

0
Roboprog

Comparez ceci:

String[] names = {"Fred", "Susan"};
List<String> namesBeginningWithS = new LinkedList<String>();
for (String name : names) {
    if (name.startsWith("S")) {
        namesBeginningWithS.add(name);
    }
}

avec:

String[] names = {"Fred", "Susan"};
List<String> namesBeginningWithS = names.stream().filter(n <- n.startsWith("S")).collect();

La deuxième version est Java 8 de la manière de réduire le code de la chaudrycique (boucle, etc.) en fournissant des fonctions de commande supérieure, telles que filter qui vous permet de passer le strict minimum (c'est-à-dire la dépendance à être injecté - l'expression de la Lambda).

0
Sridhar Sarnobat