详解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 相关文章推荐
DOM下的节点属性和操作小结
May 14 Javascript
javascript DOM编程实例(智播客学习)
Nov 23 Javascript
jQuery实现原理的模拟代码 -6 代码下载
Aug 16 Javascript
javascript中的prototype属性使用说明(函数功能扩展)
Aug 16 Javascript
用js传递value默认值的示例代码
Sep 11 Javascript
原生js仿jquery animate动画效果
Jul 13 Javascript
基于JavaScript实现点击页面任何位置返回
Aug 31 Javascript
vue制作加载更多功能的正确打开方式
Oct 12 Javascript
详解vue-cli与webpack结合如何处理静态资源
Sep 19 Javascript
node.js基于fs模块对系统文件及目录进行读写操作的方法详解
Nov 10 Javascript
vuex实现的简单购物车功能示例
Feb 13 Javascript
layer iframe 设置关闭按钮的方法
Sep 12 Javascript
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
首页四格,首页五格For6.0(GBK)(UTF-8)[12种组合][9-18][版主安装测试通过]
2007/09/24 PHP
php FPDF类库应用实现代码
2009/03/20 PHP
php输入流php://input使用浅析
2014/09/02 PHP
ThinkPHP打开验证码页面显示乱码的解决方法
2014/12/18 PHP
PHP实现websocket通信的方法示例
2018/08/28 PHP
Laravel框架处理用户的请求操作详解
2019/12/20 PHP
jQuery实现form表单reset按钮重置清空表单功能
2012/12/18 Javascript
ExtJS下书写动态生成的xml(兼容火狐)
2013/04/02 Javascript
JS自定义功能函数实现动态添加网址参数修改网址参数值
2013/08/02 Javascript
flash+jQuery实现可关闭及重复播放的压顶广告
2015/04/15 Javascript
Backbone.js的Hello World程序实例
2015/06/19 Javascript
Javascript实现的简单右键菜单类
2015/09/23 Javascript
jquery实现邮箱自动填充提示功能
2015/11/17 Javascript
浅析JavaScript声明变量
2015/12/21 Javascript
jquery Deferred 快速解决异步回调的问题
2016/04/05 Javascript
用v-html解决Vue.js渲染中html标签不被解析的问题
2016/12/14 Javascript
jquery实现轮播图效果
2017/02/13 Javascript
JavaScript下拉菜单功能实例代码
2017/03/01 Javascript
ES6新特性之字符串的扩展实例分析
2017/04/01 Javascript
AngularJS使用拦截器实现的loading功能完整实例
2017/05/17 Javascript
详解jquery选择器的原理
2017/08/01 jQuery
轻松理解vue的双向数据绑定问题
2017/10/30 Javascript
nodejs实现的连接MySQL数据库功能示例
2018/01/25 NodeJs
详解如何在你的Vue项目配置vux
2018/06/04 Javascript
async/await让异步操作同步执行的方法详解
2019/11/01 Javascript
Vue实现 点击显示再点击隐藏效果(点击页面空白区域也隐藏效果)
2020/01/16 Javascript
Python模块学习 datetime介绍
2012/08/27 Python
用Python代码来绘制彭罗斯点阵的教程
2015/04/03 Python
Python使用爬虫猜密码
2016/02/19 Python
详解python调度框架APScheduler使用
2017/03/28 Python
Python简单实现阿拉伯数字和罗马数字的互相转换功能示例
2018/04/17 Python
Python2与Python3的区别实例总结
2019/04/17 Python
Python collections模块的使用方法
2020/10/09 Python
Nip + Fab官网:英国美容品牌
2019/08/26 全球购物
自荐书封面下载
2013/11/29 职场文书
Pyqt5将多个类组合在一个界面显示的完整示例
2021/09/04 Python