Maison >interface Web >js tutoriel >Explication détaillée de la programmation de modèles de stratégie JavaScript_Connaissances de base
J'aime le modèle de conception de stratégie. J'essaie de l'utiliser autant que possible. À la base, le modèle Strategy utilise des délégués pour découpler les classes d’algorithmes qui les utilisent.
Il y a plusieurs avantages à faire cela. Cela empêche l'utilisation d'instructions conditionnelles volumineuses pour décider quels algorithmes utiliser pour des types spécifiques d'objets. La séparation des préoccupations réduit ainsi la complexité du client tout en facilitant le sous-classement. Il améliore la modularité et la testabilité. Chaque algorithme peut être testé individuellement. Chaque client peut simuler des algorithmes. N'importe quel client peut utiliser n'importe quel algorithme. Ils peuvent intermoduler. Tout comme les Lego.
Pour mettre en œuvre le modèle de stratégie, il y a généralement deux participants :
L'objet de cette stratégie encapsule l'algorithme.
Objet client (contextuel) qui peut utiliser n'importe quelle stratégie de manière plug-and-play.
Voici une introduction à la façon dont j'utilise le modèle de stratégie en Javascript et comment l'utiliser dans un environnement chaotique pour diviser la bibliothèque en petits plug-ins et packages plug-and-play.
Les fonctions comme stratégies
Une fonction constitue un excellent moyen d'encapsuler un algorithme et peut être utilisée comme stratégie. Transmettez simplement une fonction au client et assurez-vous que votre client peut appeler la politique.
Utilisons un exemple pour prouver. Supposons que nous souhaitions créer une classe Greeter. Il suffit de dire bonjour aux gens. Nous voulons que la classe Greeter connaisse les différentes manières de saluer les gens. Pour mettre en œuvre cette idée, nous créons différentes stratégies de salutations.
// Greeter is a class of object that can greet people. // It can learn different ways of greeting people through // 'Strategies.' // // This is the Greeter constructor. var Greeter = function(strategy) { this.strategy = strategy; }; // Greeter provides a greet function that is going to // greet people using the Strategy passed to the constructor. Greeter.prototype.greet = function() { return this.strategy(); }; // Since a function encapsulates an algorithm, it makes a perfect // candidate for a Strategy. // // Here are a couple of Strategies to use with our Greeter. var politeGreetingStrategy = function() { console.log("Hello."); }; var friendlyGreetingStrategy = function() { console.log("Hey!"); }; var boredGreetingStrategy = function() { console.log("sup."); }; // Let's use these strategies! var politeGreeter = new Greeter(politeGreetingStrategy); var friendlyGreeter = new Greeter(friendlyGreetingStrategy); var boredGreeter = new Greeter(boredGreetingStrategy); console.log(politeGreeter.greet()); //=> Hello. console.log(friendlyGreeter.greet()); //=> Hey! console.log(boredGreeter.greet()); //=> sup.
Dans l'exemple ci-dessus, Greeter est le client et a trois stratégies. Comme vous pouvez le constater, Greeter sait comment utiliser l’algorithme, mais n’a aucune idée des détails de l’algorithme.
Pour les algorithmes complexes, une fonction simple ne peut souvent pas satisfaire. Dans ce cas, la meilleure approche est de le définir en termes d’objets.
Les cours comme stratégies
Les stratégies peuvent également être des classes, surtout lorsque le calcul est plus complexe que l'artificiel (politique/algorithme) utilisé dans l'exemple ci-dessus. L'utilisation de classes permet de définir une interface pour chaque stratégie.
Dans l'exemple ci-dessous, cela est confirmé.
// We can also leverage the power of Prototypes in Javascript to create // classes that act as strategies. // // Here, we create an abstract class that will serve as the interface // for all our strategies. It isn't needed, but it's good for documenting // purposes. var Strategy = function() {}; Strategy.prototype.execute = function() { throw new Error('Strategy#execute needs to be overridden.') }; // Like above, we want to create Greeting strategies. Let's subclass // our Strategy class to define them. Notice that the parent class // requires its children to override the execute method. var GreetingStrategy = function() {}; GreetingStrategy.prototype = Object.create(Strategy.prototype); // Here is the `execute` method, which is part of the public interface of // our Strategy-based objects. Notice how I implemented this method in term of // of other methods. This pattern is called a Template Method, and you'll see // the benefits later on. GreetingStrategy.prototype.execute = function() { return this.sayHi() + this.sayBye(); }; GreetingStrategy.prototype.sayHi = function() { return "Hello, "; }; GreetingStrategy.prototype.sayBye = function() { return "Goodbye."; }; // We can already try out our Strategy. It requires a little tweak in the // Greeter class before, though. Greeter.prototype.greet = function() { return this.strategy.execute(); }; var greeter = new Greeter(new GreetingStrategy()); greeter.greet() //=> 'Hello, Goodbye.'
En utilisant une classe, nous définissons une stratégie avec un objet méthode d'exécution. Les clients peuvent implémenter cette interface en utilisant n'importe quelle stratégie.
Remarquez également comment j'ai créé la GreetingStrategy. La partie intéressante est la surcharge de methodexecute. Il est défini sous la forme d'autres fonctions. Désormais, les sous-classes suivantes de la classe peuvent modifier des comportements spécifiques, tels que la méthodesayHiorsayBye, sans modifier l'algorithme général. Ce modèle est appelé méthode modèle et convient très bien au modèle de stratégie.
Voyons ce qui se passe.
// Since the GreetingStrategy#execute method uses methods to define its algorithm, // the Template Method pattern, we can subclass it and simply override one of those // methods to alter the behavior without changing the algorithm. var PoliteGreetingStrategy = function() {}; PoliteGreetingStrategy.prototype = Object.create(GreetingStrategy.prototype); PoliteGreetingStrategy.prototype.sayHi = function() { return "Welcome sir, "; }; var FriendlyGreetingStrategy = function() {}; FriendlyGreetingStrategy.prototype = Object.create(GreetingStrategy.prototype); FriendlyGreetingStrategy.prototype.sayHi = function() { return "Hey, "; }; var BoredGreetingStrategy = function() {}; BoredGreetingStrategy.prototype = Object.create(GreetingStrategy.prototype); BoredGreetingStrategy.prototype.sayHi = function() { return "sup, "; }; var politeGreeter = new Greeter(new PoliteGreetingStrategy()); var friendlyGreeter = new Greeter(new FriendlyGreetingStrategy()); var boredGreeter = new Greeter(new BoredGreetingStrategy()); politeGreeter.greet(); //=> 'Welcome sir, Goodbye.' friendlyGreeter.greet(); //=> 'Hey, Goodbye.' boredGreeter.greet(); //=> 'sup, Goodbye.'
GreetingStrategy crée un algorithme de classe en spécifiant les étapes de la méthode d'exécution. Dans l'extrait de code ci-dessus, nous en profitons en créant un algorithme spécialisé.
Sans sous-classement, notre Greeter présente toujours un comportement polymorphe. Il n'est pas nécessaire de basculer entre différents types de Greeter pour déclencher le bon algorithme. Tout cela est lié à chaque objet Greeter.
var greeters = [ new Greeter(new BoredGreetingStrategy()), new Greeter(new PoliteGreetingStrategy()), new Greeter(new FriendlyGreetingStrategy()), ]; greeters.forEach(function(greeter) { // Since each greeter knows its strategy, there's no need // to do any type checking. We just greet, and the object // knows how to handle it. greeter.greet(); });
Mode stratégie dans plusieurs environnements
L'un de mes exemples préférés de modèle de stratégie se trouve dans la bibliothèque Passport.js. Passport.js fournit un moyen simple de gérer l'authentification dans Node. Un large éventail de fournisseurs le soutiennent (Facebook, Twitter, Google, etc.), chacun étant mis en œuvre sous forme de politique.
La bibliothèque est disponible sous forme de package npm, comme le sont toutes ses stratégies. Les utilisateurs de la bibliothèque peuvent décider quel package npm installer pour leurs cas d'utilisation uniques. Voici un extrait de code montrant comment cela se fait :
// Taken from http://passportjs.org var passport = require('passport') // Each authentication mechanism is provided as an npm package. // These packages expose a Strategy object. , LocalStrategy = require('passport-local').Strategy , FacebookStrategy = require('passport-facebook').Strategy; // Passport can be instanciated using any Strategy. passport.use(new LocalStrategy( function(username, password, done) { User.findOne({ username: username }, function (err, user) { if (err) { return done(err); } if (!user) { return done(null, false, { message: 'Incorrect username.' }); } if (!user.validPassword(password)) { return done(null, false, { message: 'Incorrect password.' }); } return done(null, user); }); } )); // In this case, we instanciate a Facebook Strategy passport.use(new FacebookStrategy({ clientID: FACEBOOK_APP_ID, clientSecret: FACEBOOK_APP_SECRET, callbackURL: "http://www.example.com/auth/facebook/callback" }, function(accessToken, refreshToken, profile, done) { User.findOrCreate(..., function(err, user) { if (err) { return done(err); } done(null, user); }); } ));
La bibliothèque Passport.js n'est livrée qu'avec un ou deux mécanismes d'authentification simples. En dehors de cela, il n’a pas d’interface au-delà d’une classe de stratégie conforme à l’objet contextuel. Ce mécanisme permet à ses utilisateurs de mettre en œuvre facilement leurs propres mécanismes d'authentification sans nuire au projet.
Réflexion
Le modèle Strategy fournit un moyen d'augmenter la modularité et la testabilité de votre code. Cela ne signifie pas que (le modèle de stratégie) fonctionne toujours. Les mixins peuvent également être utilisés pour injecter des fonctionnalités, telles que des algorithmes, dans un objet au moment de l'exécution. Le polymorphisme plat du typage du vieux canard peut parfois être assez simple.
Cependant, l'utilisation du modèle Strategy vous permet de faire évoluer votre code à mesure que votre charge de travail augmente sans introduire une grande architecture au début. Comme nous l'avons vu avec l'exemple Passport.js, il deviendra plus facile pour les responsables d'ajouter des stratégies supplémentaires à l'avenir.