详解JavaScript的策略模式编程


Posted in Javascript onJune 24, 2015

 我喜欢策略设计模式。我尽可能多的试着去使用它。究其本质,策略模式使用委托去解耦使用它们的算法类。

这样做有几个好处。他可以防止使用大条件语句来决定哪些算法用于特定类型的对象。将关注点分离开来,因此降低了客户端的复杂度,同时还可以促进子类化的组成。它提高了模块化和可测性。每一个算法都可以单独测试。每一个客户端都可以模拟算法。任意的客户端都能使用任何算法。他们可以互调。就像乐高积木一样。

为了实现策略模式,通常有两个参与者:

该策略的对象,封装了算法。

客户端(上下文)对象,以即插即用的方式能使用任何策略。

这里介绍了我在Javascrip里,怎样使用策略模式,在混乱无序的环境中怎样使用它将库拆成小插件,以及即插即用包的。

函数作为策略

一个函数提供了一种封装算法的绝佳方式,同时可以作为一种策略来使用。只需通过一个到客户端的函数并确保你的客户端能调用该策略。

我们用一个例子来证明。假设我们想创建一个Greeter 类。它所要做的就是和人打招呼。我们希望Greeter 类能知道跟人打招呼的不同方式。为了实现这一想法,我们为打招呼创建不同的策略。
 

// 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.

在上面的例子中,Greeter 是客户端,并有三种策略。正如你所看到的,Greeter 知道怎样使用算法,但对于算法的细节却一无所知。

对于复杂的算法,一个简单的函数往往不能满足。在这种情况下,对好的方式就是按照对象来定义。
 
类作为策略

策略同样可以是类,特别是当算比上述例子中使用的人为的(策略/算法)更复杂的时候。使用类的话,允许你为每一种策略定义一个接口。

在下面的例子中,证实了这一点。
 

// 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.'

通过使用类,我们与anexecutemethod对象定义了一个策略。客户端可以使用任何策略实现该接口。

同样注意我又是怎样创建GreetingStrategy的。有趣的部分是对methodexecute的重载。它以其他函数的形式定义。现在类的后继子类可以改变特定的行为,如thesayHiorsayByemethod,并不改变常规的算法。这种模式叫做模板方法,非常适合策略模式。

让我们看个究竟。
 

// 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 通过指定theexecutemethod的步骤,创建了一个类的算法。在上面的代码片段中,我们通过创建专门的算法从而利用了这一点。

没有使用子类,我们的Greeter 依然展示出一种多态行为。没有必要在Greeter 的不同类型上进行切换来触发正确的算法。这一切都绑定到每一个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();
});

多环境下的策略模式

我最喜欢的有关策略模式的例子之一,实在 Passport.js库中。Passport.js提供了一种在Node中处理身份验证的简单方式。大范围内的供应商都支持(Facebook, Twitter, Google等等),每一个都作为一种策略实现。

该库作为一个npm包是可行的,其所有的策略也一样。库的用户可以决定为他们特有的用例安装哪一个npm包。下面是展示其如何实现的代码片段:
 

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

Passport.js库只配备了一两个简单的身份验证机制。除此之外,它没有超过一个符合上下文对象的一个策略类的接口。这种机制让他的使用者,很容易的实现他们自己的身份验证机制,而对项目不产生不利的影响。
 
反思

策略模式为你的代码提供了一种增加模块化和可测性的方式。这并不意味着(策略模式)总是有效。Mixins 同样可以被用来进行功能性注入,如在运行时的一个对象的算法。扁平的老式 duck-typing多态有时候也可以足够简单。

然而,使用策略模式允许你在一开始没有引入大型体系的情况下,随着负载型的增长,扩大你的代码的规模。正如我们在Passport.js例子中看到的一样,对于维护人员在将来增加另外的策略,将变得更加方便。

Javascript 相关文章推荐
模拟jQuery ajax服务器端与客户端通信的代码
Mar 28 Javascript
jQuery语法总结和注意事项小结
Nov 11 Javascript
jQuery遮罩层实现方法实例详解(附遮罩层插件)
Dec 08 Javascript
利用jQuery来动态为属性添加或者删除属性的简单方法
Dec 02 Javascript
Vue结合原生js实现自定义组件自动生成示例
Jan 21 Javascript
js使用highlight.js高亮你的代码
Aug 18 Javascript
JS使用贪心算法解决找零问题示例
Nov 27 Javascript
vue-router路由懒加载和权限控制详解
Dec 13 Javascript
three.js实现3D影院的原理的代码分析
Dec 18 Javascript
jQuery pagination分页示例详解
Oct 23 jQuery
JS中的一些常用的函数式编程术语
Jun 15 Javascript
详解Vue的sync修饰符
May 15 Vue.js
jquery控制页面部分刷新的方法
Jun 24 #Javascript
js实现延迟加载的方法
Jun 24 #Javascript
介绍JavaScript的一个微型模版
Jun 24 #Javascript
js实现a标签超链接提交form表单的方法
Jun 24 #Javascript
对JavaScript客户端应用编程的一些建议
Jun 24 #Javascript
javascript删除数组重复元素的方法汇总
Jun 24 #Javascript
js实现跨域的方法实例详解
Jun 24 #Javascript
You might like
AJAX for PHP简单表数据查询实例
2007/01/02 PHP
str_replace只替换一次字符串的方法
2013/04/09 PHP
PHP IDE phpstorm 常用快捷键
2015/05/18 PHP
php阿拉伯数字转中文人民币大写
2015/12/21 PHP
PHP用continue跳过本次循环中剩余代码的注意点
2017/06/27 PHP
基础的prototype.js常用函数及其用法
2007/03/10 Javascript
js获取图片长和宽度的代码
2009/11/24 Javascript
jQuery UI 实现email输入提示实例
2013/08/15 Javascript
jquery实现图片翻页效果
2013/12/23 Javascript
PHP中CURL的几个经典应用实例
2015/01/23 Javascript
jQuery 获取遍历获取table中每一个tr中的第一个td的方法
2016/10/05 Javascript
jQuery事件_动力节点Java学院整理
2017/07/05 jQuery
Vue 中使用vue2-highcharts实现曲线数据展示的方法
2018/03/05 Javascript
vue.js 使用axios实现下载功能的示例
2018/03/05 Javascript
深入理解JS异步编程-Promise
2019/06/03 Javascript
js JSON.stringify()基础详解
2019/06/19 Javascript
layui弹出框Tab选项卡的示例代码
2019/09/04 Javascript
[01:02:34]TFT vs VGJ.T Supermajor 败者组 BO3 第二场 6.5
2018/06/06 DOTA
[26:50]2018完美盛典DOTA2表演赛
2018/12/17 DOTA
[02:17]快乐加倍!DOTA2食人魔魔法师至宝+迎霜节活动上线
2019/12/22 DOTA
解决python "No module named pip" 的问题
2018/10/13 Python
python的pstuil模块使用方法总结
2019/07/26 Python
Spring实战之使用util:命名空间简化配置操作示例
2019/12/09 Python
Python object类中的特殊方法代码讲解
2020/03/06 Python
python递归调用中的坑:打印有值, 返回却None
2020/03/16 Python
深入解析HTML5中的Blob对象的使用
2015/09/08 HTML / CSS
美国在线家装零售商:Build.com
2016/09/02 全球购物
戴尔加拿大官网:Dell加拿大
2016/09/17 全球购物
汉语专业毕业生自荐信
2014/07/06 职场文书
2014年个人债务授权委托书范本
2014/09/22 职场文书
瞿秋白纪念馆观后感
2015/06/10 职场文书
安全学习心得体会范文
2016/01/18 职场文书
2017元旦晚会开幕词
2016/03/03 职场文书
MySQL 覆盖索引的优点
2021/05/19 MySQL
MongoDB日志切割的三种方式总结
2021/09/15 MongoDB
关于Redis的主从复制及哨兵问题
2022/06/16 Redis