L'interface est l'un des outils les plus utiles de la boîte à outils du programmeur JavaScript orienté objet . L'un des principes de la conception orientée objet réutilisable proposé dans les Design Patterns est « la programmation pour les interfaces plutôt que la programmation d'implémentation », ce que nous appelons la programmation orientée interface. L'importance de ce concept est évidente. Mais le problème est que dans le monde de JavaScript, il n'existe pas de méthodes intégrées pour créer ou implémenter des interfaces, et il n'existe aucun ensemble de méthodes permettant de déterminer si un objet implémente la même chose qu'un autre objet, ce qui rend son utilisation difficile. objets de manière interchangeable. Heureusement, JavaScript possède une excellente flexibilité, ce qui facilite la simulation des interfaces orientées objet traditionnelles et l'ajout de ces fonctionnalités. Une interface fournit un moyen de décrire les méthodes qu'un objet doit avoir. Bien qu'elle puisse indiquer la signification de ces méthodes, elle ne contient pas d'implémentations spécifiques. Avec cet outil, les objets peuvent être regroupés selon les propriétés qu'ils fournissent. Par exemple, si A et B et l'interface I, même si l'objet A et l'objet B sont très différents, tant qu'ils implémentent tous les deux l'interface I, alors A et B peuvent être utilisés de manière interchangeable dans la méthode A.I(B), comme BI(A). Vous pouvez également utiliser des interfaces pour développer des points communs entre différentes classes. Si une fonction qui nécessite à l'origine une classe spécifique comme paramètre est remplacée par une fonction qui nécessite une interface spécifique comme paramètre, alors tous les objets qui implémentent l'interface peuvent lui être transmis en tant que paramètres. De cette manière, tous les objets qui ne le sont pas. liés les uns aux autres peuvent lui être transmis en tant que paramètres. Les objets peuvent également être traités de la même manière.
L'interface établie est auto-descriptive et peut favoriser la réutilisabilité du code. L'interface peut fournir une sorte d'informations pour indiquer à la classe externe quelles méthodes doivent être utilisées. mis en œuvre. Cela aide également à stabiliser la méthode de communication entre les différentes classes et réduit les problèmes qui surviennent lors du processus d'héritage de deux objets. Ceci est également utile pour le débogage. Dans un langage faiblement typé comme JavaScript, les incohérences de type sont difficiles à suivre. Lors de l'utilisation de l'interface, si un problème survient, un message d'erreur plus clair s'affichera. Bien entendu, les interfaces ne sont pas totalement exemptes de défauts. Une utilisation intensive des interfaces affaiblira dans une certaine mesure leur flexibilité en tant que langage faiblement typé. D'un autre côté, JavaScript n'a pas de support intégré pour les interfaces, mais simule uniquement les objets traditionnels. interfaces orientées. Cela rend JavaScript, qui est intrinsèquement flexible, plus difficile à contrôler. De plus, quelle que soit la manière dont vous implémentez une interface, cela aura un impact sur les performances, en partie à cause de la surcharge des appels de méthodes supplémentaires. Le plus gros problème avec l'utilisation des interfaces est que, contrairement à d'autres langages fortement typés, JavaScript ne pourra pas être compilé s'il ne respecte pas les conventions d'interface. Sa flexibilité peut efficacement éviter les problèmes ci-dessus. S'il se trouve dans un environnement de développement collaboratif, son interface. est très susceptible d'être endommagé sans provoquer d'erreurs, ce qui est incontrôlable.
Dans les langages orientés objet, les interfaces sont utilisées de manière généralement similaire. Les informations contenues dans l'interface décrivent les méthodes que la classe doit implémenter et les signatures de ces méthodes. Les définitions de classe doivent indiquer explicitement qu'elles implémentent ces interfaces, sinon elles ne seront pas compilées. Évidemment, nous ne pouvons pas faire la même chose en JavaScript, car il n'y a pas d'interface et de mots-clés d'implémentation, et il n'y a pas de vérification au moment de l'exécution si l'interface suit le contrat, mais nous pouvons imiter la plupart de ses fonctionnalités via des méthodes auxiliaires et des vérifications explicites.
Il existe trois façons principales d'imiter des interfaces en JavaScript : via les commentaires, la vérification des attributs et le débat de type canard. Si les trois méthodes ci-dessus sont efficacement combinées, cela se produit. produira un effet de type interface.
Les commentaires sont un moyen relativement intuitif de mettre des mots-clés liés à l'interface (tels que interface, implémentation, etc.) avec du code JavaScript dans les commentaires pour simuler des interfaces. C'est la méthode la plus simple, mais elle a le pire effet. . Le code est le suivant :
//以注释的形式模仿描述接口 /* interface Composite{ function add(child); function remove(child); function getName(index); } interface FormItem{ function save(); } */ //以注释的形式模仿使用接口关键字 var CompositeForm =function(id , method,action) { //implements Composite , FormItem // do something } //模拟实现具体的接口方法 此处实现Composite接口 CompositeForm.prototype.Add=function(){ // do something } CompositeForm.prototype.remove=function(){ // do something } CompositeForm.prototype.getName=function(){ // do something } //模拟实现具体的接口方法 此处实现FormItem接口 Composite.prototype.save=function(){ // do something }
Cette méthode n'est en fait pas très bonne, car ce type d'imitation reste uniquement dans le cadre des spécifications du document. Reste à savoir si les développeurs respecteront strictement cet accord. Le respect de l'interface repose entièrement sur la conscience du développeur. De plus, cette méthode ne vérifie pas si une fonction implémente réellement « l’interface » sur laquelle nous nous sommes mis d’accord. Néanmoins, cette approche présente également des avantages : elle est facile à mettre en œuvre sans nécessiter de classes ou de fonctions supplémentaires, et elle peut améliorer la réutilisabilité du code car les interfaces implémentées par les classes sont annotées. Cette approche n'affecte pas l'espace occupé par le fichier ni la vitesse d'exécution, car le code commenté peut être facilement éliminé lors du déploiement. Mais comme aucun message d’erreur n’est fourni, cela n’aide pas beaucoup pour les tests et le débogage. La méthode suivante vérifiera si l'interface est implémentée. Le code est le suivant :
//以注释的形式模仿使用接口关键字 var CompositeForm =function(id , method,action) { //implements Composite , FormItem // do something this.implementsinterfaces=['Composite','FormItem']; //显式地把接口放在implementsinterfaces中 } //检查接口是否实现 function implements(Object){ for(var i=0 ;i< arguments.length;i++){ 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; }else{ return true; } } } function AddForm(formInstance){ if(!implements(formInstance,'Composite','FormItem')){ throw new Error('Object does not implements required interface!'); } }
上述代码是在方式一的基础上进行完善,在这个例子中,CompositeForm宣称自己实现了Composite和FormItem这两个接口,其做法是把这两个接口的名称加入一个implementsinterfaces的数组。显式地声明自己支持什么接口。任何一个要求其参数属性为特定类型的函数都可以对这个属性进行检查,并在所需要的接口未在声明之中时抛出错误。这种方式相对于上一种方式,多了一个强制性的类型检查。但是这种方法的缺点在于它并未保证类真正地实现了自称实现的接口,只是知道它声明自己实现了这些接口。其实类是否声明自己支持哪些接口并不重要,只要它具有这些接口中的方法就行。鸭式辩型(像鸭子一样走路并且嘎嘎叫的就是鸭子)正是基于这样的认识,它把对象实现的方法集作为判断它是不是某个类的实例的唯一标准。这种技术在检查一个类是否实现了某个接口时也可以大显身手。这种方法的背后观点很简单:如果对象具有与接口定义的方法同名的所有方法,那么就可以认为它实现了这个接口。可以使用一个辅助函数来确保对象具有所有必需的方法,代码如下:
//interface var Composite =new Interface('Composite',['add','remove','getName']); var FormItem=new Interface('FormItem',['save']); //class var Composite=function(id,method,action){ } //Common Method function AddForm(formInstance){ ensureImplements(formInstance,Composite,FormItem); //如果该函数没有实现指定的接口,这个函数将会报错 }
与另外两种方式不同,这种方式无需注释,其余的各个方面都是可以强制实施的。EnsureImplements函数需要至少两个参数。第一个参数是想要检查的对象,其余的参数是被检查对象的接口。该函数检查器第一个参数代表的对象是否实现了那些接口所声明的方法,如果漏掉了任何一个,就会抛错,其中会包含被遗漏的方法的有效信息。这种方式不具备自我描述性,需要一个辅助类和辅助函数来帮助实现接口检查,而且它只关心方法名称,并不检查参数的名称、数目或类型。
在下面的代码中,对Interface类的所有方法的参数都进行了严格的控制,如果参数没有验证通过,那么就会抛出异常。加入这种检查的目的就是,如果在执行过程中没有抛出异常,那么就可以肯定接口得到了正确的声明和实现。
var Interface = function(name ,methods){ if(arguments.length!=2){ throw new Error('2 arguments required!'); } this.name=name; this.methods=[]; for(var i=0;len=methods.length;i<len;i++){ if(typeof(methods[i]!=='String')){ throw new Error('method name must be String!'); } this.methods.push(methods[i]); } } Interface.ensureImplements=function(object){ if(arguments.length<2){ throw new Error('2 arguments required at least!'); } for(var i=0;len=arguments.length;i<len;i++){ var interface=arguments[i]; if(interface.constructor!==Interface){ throw new Error('instance must be Interface!'); } for(var j=0;methodLength=interface.methods.length;j<methodLength;j++){ var method=interface.methods[j]; if(!object[method]||typeof(object[method])=='function')){ throw new Error('object does not implements method!'); } } } }
其实多数情况下,接口并不是经常被使用的,严格的类型检查并不总是明智的。但是在设计复杂的系统的时候,接口的作用就体现出来了,这看似降低了灵活性,却同时也降低了耦合性,提高了代码的重用性。这在大型系统中是比较有优势的。在下面的例子中,声明了一个displayRoute方法,要求其参数具有三个特定的方法,通过Interface对象和ensureImplements方法来保证这三个方法的实现,否则将会抛出错误。
//声明一个接口,描述该接口包含的方法 var DynamicMap=new Interface{'DynamicMap',['centerOnPoint','zoom','draw']}; //声明一个displayRoute方法 function displayRoute(mapInstance){ //检验该方法的map //检验该方法的mapInsstance是否实现了DynamicMap接口,如果未实现则会抛出 Interface.ensureImplements(mapInstance,DynamicMap); //如果实现了则正常执行 mapInstance.centerOnPoint(12,22); mapInstance.zoom(5); mapInstance.draw(); }
下面的例子会将一些数据以网页的形式展现出来,这个类的构造器以一个TestResult的实例作为参数。该类会对TestResult对象所包含的数据进行格式化(Format)后输出,代码如下:
var ResultFormatter=function(resultObject){ //对resultObject进行检查,保证是TestResult的实例 if(!(resultObject instanceof TestResult)){ throw new Error('arguments error!'); } this.resultObject=resultObject; } ResultFormatter.prototype.renderResult=function(){ var dateOfTest=this.resultObject.getData(); var resultArray=this.resultObject.getResults(); var resultContainer=document.createElement('p'); var resultHeader=document.createElement('h3'); resultHeader.innerHTML='Test Result from '+dateOfTest.toUTCString(); resultContainer.appendChild(resultHeader); var resultList=document.createElement('ul'); resultContainer.appendChild(resultList); for(var i=0;len=resultArray.length;i<len;i++){ var listItem=document.createElement('li'); listItem.innerHTML=resultArray[i]; resultList.appendChild('listItem'); } return resultContainer; }
该类的构造器会对参数进行检查,以确保其的确为TestResult的类的实例。如果参数达不到要求,构造器将会抛出一个错误。有了这样的保证,在编写renderResult方法的时候,就可以认定有getData和getResult两个方法。但是,构造函数中,只对参数的类型进行了检查,实际上这并不能保证所需要的方法都得到了实现。TestResult类会被修改,致使其失去这两个方法,但是构造器中的检查依旧会通过,只是renderResult方法不再有效。
此外,构造器中的这个检查施加了一些不必要的限制。它不允许使用其他的类的实例作为参数,否则会直接抛错,但是问题来了,如果有另一个类也包含并实现了getData和getResult方法,它本来可以被ResultFormatter使用,却因为这个限制而无用武之地。
解决问题的办法就是删除构造器中的校验,并使用接口代替。我们采用这个方案对代码进行优化:
//接口的声明 var resultSet =new Interface('ResultSet',['getData','getResult']); //修改后的方案 var ResultFormatter =function(resultObject){ Interface.ensureImplements(resultObject,resultSet); this.resultObject=resultObject; }
上述代码中,renderResult方法保持不变,而构造器却采用的ensureImplements方法,而不是typeof运算符。现在的这个构造器可以接受任何符合接口的类的实例了。
<1>工厂模式:对象工厂所创建的具体对象会因具体情况而不同。使用接口可以确保所创建的这些对象可以互换使用,也就是说对象工厂可以保证其生产出来的对象都实现了必需的方法;
<2>组合模式:如果不使用接口就不可能使用这个模式,其中心思想是可以将对象群体与其组成对象同等对待。这是通过接口来做到的。如果不进行鸭式辩型或类型检查,那么组合模式就会失去大部分意义;
<3>Modèle de décorateur : Les décorateurs fonctionnent en fournissant de manière transparente un emballage pour un autre objet. Ceci est réalisé en implémentant une interface exactement la même que celle de l’autre objet. Pour le monde extérieur, il n'y a aucune différence entre un décorateur et l'objet qu'il enveloppe, utilisez donc une interface pour vous assurer que le décorateur créé implémente les méthodes nécessaires ;
<4> Mode commande : Tous les objets de commande dans le code implémentent le même lot de méthodes (telles que run, ecxute, do, etc.). En utilisant des interfaces, les classes créées sans exécuter ces objets de commande n'ont pas besoin de savoir ce que sont ces objets, tant que car sachez simplement qu’ils implémentent tous correctement l’interface. Cela vous permet de créer des API avec un haut degré de modularité et un faible couplage.
Ce qui précède est une brève discussion du code d'interface en JavaScript. Pour plus de contenu connexe, veuillez faire attention au site Web PHP chinois (www.php.cn) !