每个开发者迟早都会发现设计模式的美丽之处。同样,开发者迟早也会发现大多数模式并不能以其纯粹的形式适用。我们经常使用变体。我们改变众所周知的定义以适应我们的用例。我知道我们(程序员)喜欢流行语。这里有一个新的——黑盒驱动开发,或简称BBDD。我在几个月前就开始应用这个概念,并且可以说结果很有希望。在完成几个项目后,我开始看到良好的实践并形成了三个原则。
什么是黑盒?
在介绍BBDD的原则之前,让我们看看黑盒的含义。根据维基百科
在科学和工程中,黑盒是指可以根据其输入、输出和传输特性进行查看的设备、系统或对象,而无需了解其内部工作原理。
在编程中,每个接受输入、执行操作并返回输出的代码段都可以被视为黑盒。在JavaScript中,我们可以通过使用函数轻松地应用这个概念。例如
var Box = function(a, b) {
var result = a + b;
return result;
}
这是BBDD单元的最简单版本。它是一个执行操作并立即返回输出的盒子。然而,我们经常需要其他东西。我们需要与盒子持续交互。这是我用来称呼“活黑盒”的另一种盒子。
var Box = function(a, b) {
var api = {
calculate: function() {
return a + b;
}
};
return api;
}
我们有一个包含盒子所有公共函数的API。它与揭示模块模式相同。这种模式最重要的特征是它带来了封装。我们对公共对象和私有对象进行了清晰的分离。
现在我们知道了什么是黑盒,让我们看看BBDD的三个原则。
原则1:模块化一切
每个逻辑片段都应该作为一个独立的模块存在。换句话说——一个黑盒。在开发周期的开始,识别这些片段有点困难。在没有编写任何代码的情况下花费太多时间来“架构”应用程序可能不会产生好的结果。有效的方法涉及编码。我们应该草拟应用程序,甚至制作其中的一部分。一旦我们有了某些东西,我们就可以开始考虑将其黑盒化。在不考虑是否正确的情况下,跳入代码并制作某些东西也更容易。关键是重构实现,直到你觉得它足够好了。
让我们以以下示例为例
$(document).ready(function() {
if(window.localStorage) {
var products = window.localStorage.getItem('products') || [], content = '';
for(var i=0; i';
}
$('.content').html(content);
} else {
$('.error').css('display', 'block');
$('.error').html('Error! Local storage is not supported.')
}
});
我们从浏览器的本地存储中获取一个名为products
的数组。如果浏览器不支持本地存储,则显示一条简单的错误消息。
代码本身很好,并且可以工作。但是,有几个职责合并到单个函数中。我们必须做的第一个优化是形成代码的良好入口点。仅将新定义的闭包发送到$(document).ready
不够灵活。如果我们想延迟初始代码的执行或以其他方式运行它该怎么办?上面的代码片段可以转换为以下内容
var App = function() {
var api = {};
api.init = function() {
if(window.localStorage) {
var products = window.localStorage.getItem('products') || [], content = '';
for(var i=0; i';
}
$('.content').html(content);
} else {
$('.error').css('display', 'block');
$('.error').html('Error! Local storage is not supported.');
}
return api;
}
return api;
}
var application = App();
$(document).ready(application.init);
现在,我们对引导过程有了更好的控制。
我们目前的数据源是浏览器的本地存储。但是,我们可能需要从数据库中获取产品,或者简单地使用模型。提取代码的这部分是有意义的
var Storage = function() {
var api = {};
api.exists = function() {
return !!window && !!window.localStorage;
};
api.get = function() {
return window.localStorage.getItem('products') || [];
}
return api;
}
我们还有另外两个操作可以形成另一个盒子——设置HTML内容和显示元素。让我们创建一个模块来处理DOM交互。
var DOM = function(selector) {
var api = {}, el;
var element = function() {
if(!el) {
el = $(selector);
if(el.length == 0) {
throw new Error('There is no element matching "' + selector + '".');
}
}
return el;
}
api.content = function(html) {
element().html(html);
return api;
}
api.show = function() {
element().css('display', 'block');
return api;
}
return api;
}
代码与第一个版本执行的操作相同。但是,我们有一个测试函数element
,它检查传递的选择器是否与DOM树中的任何内容匹配。我们还将jQuery元素黑盒化,这使得我们的代码更加灵活。想象一下,我们决定删除jQuery。DOM操作隐藏在这个模块中。值得注意的是,可以编辑它并开始使用原生JavaScript或其他库。如果我们保留旧的变体,我们可能会遍历整个代码库替换代码片段。
这是转换后的脚本。一个使用我们上面创建的模块的新版本
var App = function() {
var api = {},
storage = Storage(),
c = DOM('.content'),
e = DOM('.error');
api.init = function() {
if(storage.exists()) {
var products = storage.get(), content = '';
for(var i=0; i';
}
c.content(content);
} else {
e.content('Error! Local storage is not supported.').show();
}
return api;
}
return api;
}
请注意,我们职责分离了。我们有扮演角色的对象。使用这样的代码库更容易也更有趣。
原则2:仅公开公共方法
使黑盒有价值的是它隐藏了复杂性这一事实。程序员应该只公开需要的 方法(或属性)。用于内部流程的所有其他函数都应该是私有的。
让我们获取上面的DOM模块
var DOM = function(selector) {
var api = {}, el;
var element = function() { … }
api.content = function(html) { … }
api.show = function() { … }
return api;
}
当开发人员使用我们的类时,他感兴趣的两件事是——更改内容和显示DOM元素。他不应该考虑验证或更改CSS属性。在我们的示例中,有私有变量el
和私有函数element
。它们对外部世界隐藏。
原则3:使用组合而非继承
在JavaScript中继承类的一种流行方法是使用原型链。在下面的代码片段中,我们有类A被类C继承
function A(){};
A.prototype.someMethod = function(){};
function C(){};
C.prototype = new A();
C.prototype.constructor = C;
但是,如果我们使用揭示模块模式,则使用组合是有意义的。这是因为我们正在处理对象而不是函数(*实际上,JavaScript中的函数也是对象)。假设我们有一个实现观察者模式的盒子,并且我们想扩展它。
var Observer = function() {
var api = {}, listeners = {};
api.on = function(event, handler) { … };
api.off = function(event, handler) { … };
api.dispatch = function(event) { … };
return api;
}
var Logic = function() {
var api = Observer();
api.customMethod = function() { … };
return api;
}
我们通过为api
变量分配初始值来获取所需的功能。我们应该注意到,使用此技术的每个类都接收一个全新的观察者对象,因此无法产生冲突。
总结
黑盒驱动开发是一种架构应用程序的好方法。它提供了封装和灵活性。BBDD附带了一个简单的模块定义,有助于组织大型项目(和团队)。我看到几个开发人员在一个项目上工作,他们都独立地构建了自己的黑盒。
关于 Krasimir Tsonev
Krasimir Tsonev 是一位前端开发人员、博主和演讲者。他喜欢编写JavaScript并试验最新的CSS和HTML功能。作为“Node.js 蓝图”一书的作者,他专注于交付尖端应用程序。Krasimir 从平面设计师开始他的职业生涯,他花费数年时间编写ActionScript3代码。现在,随着移动开发的兴起,他热衷于开发针对各种设备的响应式应用程序。他居住和工作在保加利亚,并在瓦尔纳理工大学获得了计算机科学学士和硕士学位。
关于 Robert Nyman [荣誉编辑]
Mozilla Hacks 的技术布道者和编辑。发表关于 HTML5、JavaScript 和开放网络的演讲和博客文章。Robert 是 HTML5 和开放网络的坚定支持者,自 1999 年以来一直从事 Web 前端开发工作——在瑞典和纽约市。他还在 http://robertnyman.com 上定期发表博客文章,并且喜欢旅行和结识新朋友。
35 条评论