JavaScript 面向对象设计---接口
接口在面向对象编程中是非常重要的概念/工具,早在四人帮(GOF)的Design Pattern 中提到面向对象编程的其中一规则是“面向接口编程,而不要面向实现编程”。--这可见接口在可复用的面向对象设计中的重要性吧~
对于面向对象JavaScript而言,实现接口并不简单,因为JavaScript并没有内建支持接口。幸运的是,我们可以利用JavaScript的灵活性来模拟实现接口。在模拟接口前,首先了接一下其他面向对象如何定义/处理接口:
1.Java
2 void writeBoolean(boolean value) throws IOException;
3 void writeByte(int value) throws IOException;
4 void writeChar(int value) throws IOException;
5 void writeShort(int value) throws IOException;
6 void writeInt(int value) throws IOException;
7 ...
8 }
DataOutputStream Class通过关键字implements实现了DataOutput 接口
1 1 public class DataOutputStream extends FilterOutputStream implements DataOutput {
3 2 public final void writeBoolean (boolean value) throws IOException {
4
5 3 write (value ? 1 : 0);
6
7 4 }
8
9 5 ...
10
11 6 }
在接口定义的每一个方法都必须在实现类中实现。如果某一个方法没有被实现,编译的时候会出现一些错误:
DataOutputStream should be declared abstract; it does not define writeBoolean(boolean) in DataOutputStream .
2. PHP:
interface MyInterface { public function interfaceMethod($argumentOne, $argumentTwo); } class MyClass implements MyInterface { public function interfaceMethod($argumentOne, $argumentTwo) { return $argumentOne . $arguemntTwo; } } class BadClass implements MyInterface { // No method declarations. } // BadClass causes this error at run-time: // Fatal error: Class BadClass contains 1 abstract methods and must therefore be // declared abstract (MyInterface::interfaceMethod)
3 C#:
interface MyInterface { string interfaceMethod(string argumentOne, string argumentTwo); } class MyClass : MyInterface { public string interfaceMethod(string argumentOne, string argumentTwo) { return argumentOne + argumentTwo; } } class BadClass : MyInterface { // No method declarations. } // BadClass causes this error at compile-time: // BadClass does not implement interface member MyInterface.interfaceMethod()
从上面不同的面向对象语言中可以看出,它们定义/处理接口的方式都差不多:
1.接口定义了需要实现什么方法,这些方法需要什么参数;2.而实现类则显式声明接口中定义的方法并实现之。3.如果实现类没有把接口中所有的方法声明并实现之,则报错。
当然,在JavaScript的面向对象编程中不能像上面的编程语言来定义,使用接口,因为javaScript中没有interface,implements等关键字,也没有编译的错误检查。
在JavaScript中模拟接口
我们可以用以下三种方式来在JavaScript模拟接口:
1.注释法 2.属性检测法 3 特征法
用注释的方法实现接口
/* interface Composite { function add(child); function remove(child); function getChild(index); } interface FormItem { function save(); } */ var CompositeForm = function(id, method, action) { // implements Composite, FormItem ... }; // Implement the Composite interface. CompositeForm.prototype.add = function(child) { ... }; CompositeForm.prototype.remove = function(child) { ... }; CompositeForm.prototype.getChild = function(index) { ... }; // Implement the FormItem interface. CompositeForm.prototype.save = function() { ... };
通过上面例子看到,我们用面向对象的方式定义了接口,并把它放置到注释中。这样,既不产生错误,又起了接口定义的作用。不过,这种方法还不能很好地模拟出接口,因为它没有检测机制--它不能保证,也不知道CompositeForm是否实现了Composite 接口中的所有方法。不过这种方法的好处也很明显--简单,而且不需要额外的帮助类或者方法来支持。
用属性检测来实现接口
/* interface Composite { function add(child); function remove(child); function getChild(index); } interface FormItem { function save(); } */ var CompositeForm = function(id, method, action) { //声明必须实现 'Composite','FormItem'接口
this.implementsInterfaces = ['Composite', 'FormItem']; ... }; ... function addForm(formInstance) {
if(!implements(formInstance, 'Composite', 'FormItem')) { throw new Error("Object does not implement a required interface."); } ... } // The implements function, which checks to see if an object declares that it
//接口检测 // implements the required interfaces. function implements(object) { for(var i = 1; i < arguments.length; i++) { // Looping through all arguments // after the first one. var interfaceName = arguments[i]; var interfaceFound = false; for(var j = 0; j < object.implementsInterfaces.length; j++) { if(object.implementsInterfaces[j] == interfaceName) { interfaceFound = true; break; } } if(!interfaceFound) { return false; // An interface was not found. } } return true; // All interfaces were found. }
从上面的例子看到,CompositeForm 中声明了该类必须要实现composite,formitem接口,检测没有实现,则抛出Error。
这种实现方式的优点如下:
1.记录了该类实现了哪几个接口。
2.如果该类没有实现它声明的接口,则会报错,这样,我们就可以强制性地让其他程序员必须实现这些接口。
但是,这种实现方式的缺点也很明显:
我们保证了实现类必须声明接口,但不能保证实现类一定实现了接口的所有方法。
3.特征法
什么是特征法呢?打个比方,如果我们看见一种动物它走起来像鸭子,叫声像鸭子,生活习性也像鸭子,那么我们就认定它就是鸭子。用编程的角度来讲就是:如果一个类它声明了接口,而且实现了接口的所有方法,我们就认定了它实现了这些接口。容易理解吧~看例子:
// Interfaces. var Composite = new Interface('Composite', ['add', 'remove', 'getChild']); var FormItem = new Interface('FormItem', ['save']); // CompositeForm class var CompositeForm = function(id, method, action) {
//implements Composite, FormItem’s method ... }; ... function addForm(formInstance) { Interface.ensureImplements(formInstance, Composite, FormItem); // This function will throw an error if a required method is not implemented, // halting execution of the function. // All code beneath this line will be executed only if the checks pass. ... }
/**Interface class**/
2 var Interface = function(name, methods) {
3 if(arguments.length != 2) {
4 throw new Error("Interface constructor called with " + arguments.length +
5 "arguments, but expected exactly 2.");
6 }
7 this.name = name;
8 this.methods = [];
9 for(var i = 0, len = methods.length; i < len; i++) {
10 if(typeof methods[i] !== 'string') {
11 throw new Error("Interface constructor expects method names to be "
12 + "passed in as a string.");
13 }
14 this.methods.push(methods[i]);
15 }
16 };
// Static class method. Interface.ensureImplements = function(object) { if(arguments.length < 2) { throw new Error("Function Interface.ensureImplements called with " + arguments.length + "arguments, but expected at least 2."); } for(var i = 1, len = arguments.length; i < len; i++) { var interface = arguments[i]; if(interface.constructor !== Interface) { throw new Error("Function Interface.ensureImplements expects arguments" + "two and above to be instances of Interface."); } for(var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) { var method = interface.methods[j]; if(!object[method] || typeof object[method] !== 'function') { throw new Error("Function Interface.ensureImplements: object " + "does not implement the " + interface.name + " interface. Method " + method + " was not found."); } } } };
上面的例子中,帮助类Interface约束了实现类必须声明接口,实现接口的方法。 如果读者打算用设计模式来开发复杂的系统,我想这种方式会带来很大的好处。因为,对于大型的系统而言,会有很多程序员会一起参与开发,怎样让程序员更容易交流?代码质量更高?效率更高呢?当然就是规范了,接口就定义了一系列规范,其作用不言而喻。
下面再提供一个使用接口的范例:
使用接口前的代码:
// 该类持有一个TestResult Object,并有了一个renderResults方法把测试结果输出到页面 var ResultFormatter = function(resultsObject) { if(!(resultsObject instanceOf TestResult)) { throw new Error("ResultsFormatter: constructor requires an instance " + "of TestResult as an argument."); } this.resultsObject = resultsObject; }; ResultFormatter.prototype.renderResults = function() { var dateOfTest = this.resultsObject.getDate(); var resultsArray = this.resultsObject.getResults(); var resultsContainer = document.createElement('div'); var resultsHeader = document.createElement('h3'); resultsHeader.innerHTML = 'Test Results from ' + dateOfTest.toUTCString(); resultsContainer.appendChild(resultsHeader); var resultsList = document.createElement('ul'); resultsContainer.appendChild(resultsList); for(var i = 0, len = resultsArray.length; i < len; i++) { var listItem = document.createElement('li'); listItem.innerHTML = resultsArray[i]; resultsList.appendChild(listItem); } return resultsContainer; };
使用了接口类之后的代码
// ResultSet Interface. var ResultSet = new Interface('ResultSet',['getDate', 'getResults']); // ResultFormatter class, after adding Interface checking. var ResultFormatter = function(resultsObject) { Interface.ensureImplements(resultsObject, ResultSet); this.resultsObject = resultsObject; }; ResultFormatter.prototype.renderResults = function() { ... };
从上例中应该可以看出,重构后的代码可读性提高了?更重要的是,很多设计模式也依赖于接口,如:工厂模式,命令模式,油漆工模式等…接口只是我们为JavaScript设计模式铺平了道路…Amazing will coming soon..Contact with me:timo.li.icon@gmail.com