Back to Black,谈谈JavaScript中的黑盒驱动开发模式
发布在每天学点javascript2014年9月3日view:5057
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

enter image description here

Back to Black,谈谈JavaScript中的黑盒驱动开发模式

众所周知,JavaScript有两大开发模式,测试驱动开发(TDD)和行为驱动开发(BDD)。但是,今天要介绍的是一种全新的开发模式 – 黑盒驱动开发模式(Black Box Driven Development),也叫做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<products.length; i++) {
        content += products[i].name + '<br />';
    }
    $('.content').html(content);
} else {
    $('.error').css('display', 'block');
    $('.error').html('Error! Local storage is not supported.')
}
});

我们可以从浏览器的localStorage中得到一个叫做products的数组。如果一个浏览器不支持localStorage,用户将看到一条错误信息。

上面的这段代码没有什么问题,也能够正常工作。然而,它将几个不同的功能合并到了一个函数中。对一个函数进行优化的第一个步骤是在代码中划分出一个入口。仅仅使用$(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<products.length; i++) {
            content += products[i].name + '<br />';
        }
        $('.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);

现在,我们可以更好的控制代码的启动了。

目前为止,数据的来源是浏览器的localStorage。然而,我们可能需要从一个数据库中获取代码或者使用一个实体模型。因此我们可以将这部分代码抽象出来。

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<products.length; i++) {
            content += products[i].name + '<br />';
        }
        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变量。我们应该注意到每一个类都可以使用这个方法来获得一个新的观察者对象,而无需担心会产生什么冲突。

总结

黑盒驱动开发模式是一种很不错的应用架构模式。它提供了代码的封装和更好的灵活性。如果你已经意识到了黑盒开发的好处,就开始在你的项目中尝试一下吧!


本文参考自Black Box Driven Development in JavaScript,原文地址https://hacks.mozilla.org/2014/08/black-box-driven-development-in-javascript/

评论
发表评论
3年前

不错,学习了

WRITTEN BY
张小俊128
Intern in Baidu mobile search department。认真工作,努力钻研,期待未来更多可能。
TA的新浪微博
PUBLISHED IN
每天学点javascript

javascript进阶级教程,循序渐进掌握javascript

我的收藏