装饰者模式

1. 作用:

  • 可用来透明地把对象包装在具有同样接口的另一对象之中,这样可以给一个方法添加一些行为,然后将方法调用传递给原始对象。
  • 可用于为对象增加功能,用来代替大量子类。
  • 装饰者对其组件进行了透明包装,二者可以互换使用,因为他们 实现了同样的接口

2. 例子:自行车

function extend(subClass, superClass) {
var F = function() {}
F.prototype = superClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass subClass.superclass = superClass.prototype
if(superClass.prototype.constructor !== superClass) {
superClass.prototype.constructor = superClass
}
} // var Bicycle = new Interface('Bicycle', ['assemble', 'wash', 'ride', 'repair', 'getPrice']); /**
* [AcmeComfortCruiser 自行车类]
*/
var AcmeComfortCruiser = function(){};
AcmeComfortCruiser.prototype = {
assemble: function() {},
wash: function() {},
ride: function() {},
repair: function() {},
getPrice: function() {
return 399.00
}
} /**
* [BicycleDecorator 装饰类的抽象类]
*/
var BicycleDecorator = function(bicycle) {
// Interface.ensureImplements(bicycle, Bicycle);
this.bicycle = bicycle;
}
BicycleDecorator.prototype = {
assemble: function() {
return this.bicycle.assemble();
},
wash: function() {
return this.bicycle.wash();
},
ride: function() {
return this.bicycle.ride();
},
repair: function() {
return this.bicycle.repair();
},
getPrice: function() {
return this.bicycle.getPrice();
}
} /**
* [HeadlightDecorator 装饰者类]
*/
var HeadlightDecorator = function(bicycle) {
HeadlightDecorator.superclass.constructor.call(this, bicycle)
}
extend(HeadlightDecorator, BicycleDecorator)
HeadlightDecorator.prototype.assemble = function() {
return this.bicycle.assemble() + ' Attach headlight to handlebars.';
}
HeadlightDecorator.prototype.getPrice = function() {
return this.bicycle.getPrice() + 15.00;
} /**
* [TaillightDecorator 装饰者类]
*/
var TaillightDecorator = function(bicycle) {
TaillightDecorator.superclass.constructor.call(this, bicycle)
}
extend(TaillightDecorator, BicycleDecorator);
TaillightDecorator.prototype.assemble = function() {
return this.bicycle.assemble() + ' Attach taillight to the seat post.';
}
TaillightDecorator.prototype.getPrice = function() {
return this.bicycle.getPrice() + 9.00;
} // usage
var myBicycle = new AcmeComfortCruiser();
console.log(myBicycle.getPrice()) myBicycle = new HeadlightDecorator(myBicycle);
console.log(myBicycle.getPrice()) myBicycle = new TaillightDecorator(myBicycle);
console.log(myBicycle.getPrice())

3. 装饰者模式与组合模式的比较

  • 相同点:

    • 装饰者对象和组合对象都是用来包装别的对象(那些对象组合模式中称为子对象,装饰者模式中称为组件)
    • 实现了同样的接口,并且会把任何调用传递给这些对象
  • 差异点:
    • 组织模式:用于组织对象;装饰者模式:用于在不修改现有对象及不从其派生子类的前提下为其增添职责。
    • 装饰模式的子对象只有一个

4. 装饰者修改其组件的方式

4.1 在方法之后添加行为

先调用组件方法,并在其放回后实施一些附加行为

HeadlightDecorator.prototype.getPrice = function() {
return this.bicycle.getPrice() + 15.00;
}

4.2 在方法之前添加行为

var FrameColorDecorator = function(bicycle, frameColor) {
FrameColorDecorator.superclass.constructor.call(this, bicycle); // 添加了用以实现其提供的附加特性的属性
this.frameColor = frameColor;
}
extend(FrameColorDecorator, BicycleDecorator);
FrameColorDecorator.prototype.assemble = function() {
// 方法添加的步骤出现在其方法之前
return 'Print the frame ' + this.frameColor + ' and allow it to dry. ' + this.bicycle.assemble()
}
FrameColorDecorator.prototype.getPrice = function() {
return this.bicycle.getPrice() + 30.00;
} // usage
var myBicycle = new AcmeComfortCruiser();
console.log(myBicycle.assemble()) myBicycle = new FrameColorDecorator(myBicycle, 'red')
console.log(myBicycle.assemble())

4.3 使用替代方法

引入替换组件方法的装饰者后,必须设法确保按正确的顺序应用装饰者(如:使用工厂方法,ps:参考第 5 大点)

/**
* [LifetimeWarrantyDecorator 装饰者类]
*/
var LifetimeWarrantyDecorator = function(bicycle) {
LifetimeWarrantyDecorator.superclass.constructor.call(this, bicycle);
}
extend(LifetimeWarrantyDecorator, BicycleDecorator); // 把原来的 repair 方法替换为一个新方法,而组件的方法则不会再被调用
LifetimeWarrantyDecorator.prototype.repair = function() {
return 'This bicycle is covered by a lifetime warranty.'
}
LifetimeWarrantyDecorator.prototype.getPrice = function() {
return this.bicycle.getPrice() + 199.00
} /**
* [TimedWarrantyDecorator 装饰者类]
*/
var TimedWarrantyDecorator = function(bicycle, coverageLengthInYears) {
TimedWarrantyDecorator.superclass.constructor.call(this, bicycle);
this.coverageLength = coverageLengthInYears;
this.expDate = new Date();
var coverageLengthInMs = this.coverageLength * 365 * 24 * 60 * 60 * 1000;
this.expDate.setTime(this.expDate.getTime() + coverageLengthInMs)
}
extend(TimedWarrantyDecorator, BicycleDecorator); // 根据某种条件决定是否替代组件方法,在条件满足是替代,否则使用组件的方法
TimedWarrantyDecorator.prototype.repair = function() {
var repairInstructions;
var currentDate = new Date();
if(currentDate < this.expDate) {
repairInstructions = 'This bicycle is currently covered by a warrenty.'
}else {
repairInstructions = this.bicycle.repair();
}
return repairInstructions;
}
TimedWarrantyDecorator.prototype.getPrice = function() {
return this.bicycle.getPrice() + (40.00 * this.coverageLength)
} // usage
var myBicycle = new AcmeComfortCruiser();
console.log(myBicycle.getPrice()) // 替代
myBicycle = new LifetimeWarrantyDecorator(myBicycle)
console.log(myBicycle.repair()) // 判断是否替代
myBicycle = new TimedWarrantyDecorator(myBicycle, 1)
console.log(myBicycle.getPrice())

4.4 添加新方法

想稳妥地实现这一点并不容易,想要使用这些新方法,外围代码必须知道有这些新方法。由于这些新方法并不是在接口中定义的,而是动态添加的,因此有必要进行类型检查,以验明用于包装组件对象的最外层装饰者与用新方法装饰的组件对象相同

var BellDecorator = function(bicycle) {
BellDecorator.superclass.constructor.call(this, bicycle);
}
extend(BellDecorator, BicycleDecorator);
BellDecorator.prototype.assemble = function() {
return this.bicycle.assemble() + 'Attach bell to handlebars.'
}
BellDecorator.prototype.getPrice = function() {
return this.bicycle.getPrice() + 6.00;
}
// 这里添加了一个新方法
BellDecorator.prototype.ringBell = function() {
return 'Bell rung'
} // usage
var myBicycle = new AcmeComfortCruiser(); myBicycle = new BellDecorator(myBicycle)
myBicycle = new HeadlightDecorator(myBicycle); console.log(myBicycle.ringBell()) // 这样子会报错,因为 BellDecorator 添加的 ringBell 方法(及其他方法)会在 HeadlightDecorator 类通过 extend() 继承 new F() 时被抹除(也不是被抹除,只是不能在通过当前对象的原型链找到,其实这个方法在新对象的 bicycle 属性里面还是能通过其原型链找到)。 // BellDecorator 必须放在最后应用,否则这个新方法将无法访问。这是因为其他装饰者只能传递他们知道的方法,也即那些定义在接口中的方法。P170

关于上面说到的方法被抹除问题的解决方案

function extend(subClass, superClass) {
var F = function() {}
F.prototype = superClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass subClass.superclass = superClass.prototype
if(superClass.prototype.constructor !== superClass) {
superClass.prototype.constructor = superClass
}
}
var Interface = function(name, methods) {
if(arguments.length !== 2) {
throw new Error("Interface constructor called with "+ arguments.length + "arguments, but expected exactly 2")
}
this.name = name;
this.methods = [];
for(var i=0, len=methods.length; i<len; i++) {
if(typeof methods[i] !== 'string') {
throw new Error("Interface constructor expects method names to be passed in as a string")
}
this.methods.push(methods[i]);
}
} var Bicycle = new Interface('Bicycle', ['assemble', 'wash', 'ride', 'repair', 'getPrice']); var BicycleDecorator = function(bicycle) {
this.bicycle = bicycle;
this.interface = Bicycle;
outerloop: // 使用标记,可以在程序的任何地方使用这个名字来引用他
for(var key in this.bicycle) {
if(typeof this.bicycle[key] !== 'function') {
continue outerloop;
}
for(var i=0, len=this.interface.methods.length; i<len; i++) {
if(key === this.interface.methods[i]) {
continue outerloop
}
}
var that = this;
(function(methodName) {
that[methodName] = function() {
return that.bicycle[methodName]();
}
})(key);
}
}
BicycleDecorator.prototype = {
assemble: function() {
return this.bicycle.assemble();
},
wash: function() {
return this.bicycle.wash();
},
ride: function() {
return this.bicycle.ride();
},
repair: function() {
return this.bicycle.repair();
},
getPrice: function() {
return this.bicycle.getPrice();
}
} /**
* [AcmeComfortCruiser 自行车类]
*/
var AcmeComfortCruiser = function(){};
AcmeComfortCruiser.prototype = {
assemble: function() {
return 'assemble:'
},
wash: function() {},
ride: function() {},
repair: function() {},
getPrice: function() {
return 399.00
}
} /**
* [HeadlightDecorator 装饰者类]
*/
var HeadlightDecorator = function(bicycle) {
HeadlightDecorator.superclass.constructor.call(this, bicycle)
}
extend(HeadlightDecorator, BicycleDecorator)
HeadlightDecorator.prototype.assemble = function() {
return this.bicycle.assemble() + ' Attach headlight to handlebars.';
}
HeadlightDecorator.prototype.getPrice = function() {
return this.bicycle.getPrice() + 15.00;
} /**
* [BellDecorator 装饰者类]
*/
var BellDecorator = function(bicycle) {
BellDecorator.superclass.constructor.call(this, bicycle);
}
extend(BellDecorator, BicycleDecorator);
BellDecorator.prototype.assemble = function() {
return this.bicycle.assemble() + 'Attach bell to handlebars.'
}
BellDecorator.prototype.getPrice = function() {
return this.bicycle.getPrice() + 6.00;
}
BellDecorator.prototype.ringBell = function() {
return 'Bell rung'
} var myBicycle = new AcmeComfortCruiser();
console.log(myBicycle.getPrice()) myBicycle = new BellDecorator(myBicycle) myBicycle = new HeadlightDecorator(myBicycle);
console.log(myBicycle.getPrice()) console.log(myBicycle.ringBell())

5. 工厂的角色

可以使用工厂模式配合装饰者模式,这样就可以事先规定好实例化时的装饰者应用顺序,从而避免上面说到的新添加的方法在经过别的装饰类包装后访问不到添加的方法的问题


function extend(subClass, superClass) {
var F = function() {}
F.prototype = superClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass subClass.superclass = superClass.prototype
if(superClass.prototype.constructor !== superClass) {
superClass.prototype.constructor = superClass
}
} var Interface = function(name, methods) {
if(arguments.length !== 2) {
throw new Error("Interface constructor called with "+ arguments.length + "arguments, but expected exactly 2")
}
this.name = name;
this.methods = [];
for(var i=0, len=methods.length; i<len; i++) {
if(typeof methods[i] !== 'string') {
throw new Error("Interface constructor expects method names to be passed in as a string")
}
this.methods.push(methods[i]);
}
}
Interface.ensureImplements = function(object) {
if(arguments.length < 2) {
throw new Error("Function Interface.ensureImplements call 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")
}
}
}
} var Bicycle = new Interface('Bicycle', ['assemble', 'wash', 'ride', 'repair', 'getPrice']); // model 1
var AcmeComfortCruiser = function(){};
AcmeComfortCruiser.prototype = {
assemble: function() {},
wash: function() {},
ride: function() {},
repair: function() {},
getPrice: function() {
return 399.00
}
} // model 2
var AcmeSpeedster = function() {}
extend(AcmeSpeedster, AcmeComfortCruiser) /**
* [BicycleDecorator 装饰类的抽象类]
*/
var BicycleDecorator = function(bicycle) {
// Interface.ensureImplements(bicycle, Bicycle);
this.bicycle = bicycle;
}
BicycleDecorator.prototype = {
assemble: function() {
return this.bicycle.assemble();
},
wash: function() {
return this.bicycle.wash();
},
ride: function() {
return this.bicycle.ride();
},
repair: function() {
return this.bicycle.repair();
},
getPrice: function() {
return this.bicycle.getPrice();
}
} /**
* [HeadlightDecorator 装饰者类]
*/
var HeadlightDecorator = function(bicycle) {
HeadlightDecorator.superclass.constructor.call(this, bicycle)
}
extend(HeadlightDecorator, BicycleDecorator)
HeadlightDecorator.prototype.assemble = function() {
return this.bicycle.assemble() + ' Attach headlight to handlebars.';
}
HeadlightDecorator.prototype.getPrice = function() {
return this.bicycle.getPrice() + 15.00;
} /**
* [BellDecorator 装饰者类]
*/
var BellDecorator = function(bicycle) {
BellDecorator.superclass.constructor.call(this, bicycle);
}
extend(BellDecorator, BicycleDecorator);
BellDecorator.prototype.assemble = function() {
return this.bicycle.assemble() + 'Attach bell to handlebars.'
}
BellDecorator.prototype.getPrice = function() {
return this.bicycle.getPrice() + 6.00;
}
BellDecorator.prototype.ringBell = function() {
return 'Bell rung'
} // BicycleShop class 是一个抽象类,需要在继承后实现里面的方法
var BicycleShop = function() {};
BicycleShop.prototype = {
sellBicycle: function(model) {
var bicycle = this.createBicycle(model)
bicycle.assemble()
return bicycle;
},
// 工厂方法
createBicycle: function(model) {
throw new Error('Unsupported operation on an abstract class.')
}
} var AcmeBicycleShop = function() {};
extend(AcmeBicycleShop, BicycleShop);
AcmeBicycleShop.prototype.createBicycle = function(model, options) {
var bicycle = new AcmeBicycleShop.models[model](); // 有必要时可以在这里对装饰者组件先后应用进行排序,下面使用的是直接遍历按顺序应用 for(var i=0, len= options.length; i<len; i++) {
var decorator = AcmeBicycleShop.options[options[i].name]
if(typeof decorator !== 'function') {
throw new Error('Decorator ' + options[i].name + 'not found');
}
var argument = options[i].arg;
bicycle = new decorator(bicycle, argument)
} Interface.ensureImplements(bicycle, Bicycle);
return bicycle }
AcmeBicycleShop.models = {
'The Speedster' : AcmeSpeedster,
'The Comfort Cruiser' : AcmeComfortCruiser
}
AcmeBicycleShop.options = {
'headlight' : HeadlightDecorator,
'bell': BellDecorator
} var alecsCruisers = new AcmeBicycleShop();
var myBicycle = alecsCruisers.createBicycle('The Speedster', [
{name: 'headlight'},
{name: 'bell'}
]) myBicycle.ringBell()

6. 函数装饰者

用于包装独立的函数和方法的装饰者

6.1 包装函数

// 将传入函数的执行结果转化为大写形式
function upperCaseDecorator(func) {
return function() {
return func.apply(this, arguments).toUpperCase()
}
} function getDate() {
return (new Date()).toString()
} getDateCaps = upperCaseDecorator(getDate) // usage
getDateCaps()

6.2 包装方法

function upperCaseDecorator(func) {
return function() {
return func.apply(this, arguments).toUpperCase()
}
} function extend(subClass, superClass) {
var F = function() {}
F.prototype = superClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass subClass.superclass = superClass.prototype
if(superClass.prototype.constructor !== superClass) {
superClass.prototype.constructor = superClass
}
} /**
* [AcmeComfortCruiser 自行车类]
*/
var AcmeComfortCruiser = function(){};
AcmeComfortCruiser.prototype = {
assemble: function() {},
wash: function() {},
ride: function() {},
repair: function() {},
getPrice: function() {
return 399.00
}
} /**
* [BicycleDecorator 装饰类的抽象类]
*/
var BicycleDecorator = function(bicycle) {
this.bicycle = bicycle;
}
BicycleDecorator.prototype = {
assemble: function() {
return this.bicycle.assemble();
},
wash: function() {
return this.bicycle.wash();
},
ride: function() {
return this.bicycle.ride();
},
repair: function() {
return this.bicycle.repair();
},
getPrice: function() {
return this.bicycle.getPrice();
}
} /**
* [BellDecorator 装饰者类]
*/
var BellDecorator = function(bicycle) {
BellDecorator.superclass.constructor.call(this, bicycle);
}
extend(BellDecorator, BicycleDecorator); BellDecorator.prototype.ringBell = function() {
return 'Bell rung'
} // 使用函数装饰者装饰方法
BellDecorator.prototype.ringBellLoudly = upperCaseDecorator(BellDecorator.prototype.ringBell) var myBicycle = new AcmeComfortCruiser();
myBicycle = new BellDecorator(myBicycle)
myBicycle.ringBell()
myBicycle.ringBellLoudly()

7. 装饰者模式的适用场合

  • 为类添加特效或职能,而从该类派生子类又不实际时(不实际可能是子类的数量大)
  • 需要为对象添加特效而又不想改变使用该对象的代码的话,也可使用装饰者模式。因为装饰者模式可以动态而透明的修改对象,所以它们很适合于修改现有系统这一任务。

8. 示例:方法性能分析器

/**
* [ListBuilder]
* @param {[type]} parent [description]
*/
var ListBuilder = function(parent) {
this.parentEl = document.getElementById(parent);
}
ListBuilder.prototype = {
buildList: function(listLength) {
var list = document.createElement('ol');
this.parentEl.appendChild(list); for(var i=0; i< listLength; i++) {
var item = document.createElement('li');
list.appendChild(item)
}
}
} /**
* [MethodProfiler class]
* @param {[type]} component [description]
*/
var MethodProfiler = function(component) {
this.component = component;
this.timers = {}; for(var key in this.component) {
if(typeof this.component[key] !== 'function') {
continue;
}
var that = this;
// 使用匿名函数的作用是保留正确的 methodName 变量值
(function(methodName) {
that[methodName] = function() {
that.startTimer(methodName);
var returnValue = that.component[methodName].apply(that.component, arguments);
that.displayTime(methodName, that.getElapsedTime(methodName));
return returnValue;
}
})(key)
}
}
MethodProfiler.prototype = {
startTimer: function(methodName) {
this.timers[methodName] = (new Date()).getTime();
},
getElapsedTime: function(methodName) {
return (new Date()).getTime() - this.timers[methodName];
},
displayTime: function(methodName, time) {
console.log(methodName + ': ' + time + ' ms')
}
} // usage
var list = new ListBuilder('feed-readers')
var listp = new MethodProfiler(list)
listp.buildList(500)

9. 装饰者的利与弊

  • 利:

    • 方便,灵活,透明
    • 不用重新定义对象就能对其进去扩充
  • 弊:
    • 在遇到用装饰者包装起来的对象时,那些依赖于类型检查的代码会出问题。
    • 使用装饰者模式往往会增加架构的复杂度。(添加了一些小对象;实现动态接口的装饰者涉及的语法细节也令人生畏)

注意

转载、引用,但请标明作者和原文地址

JavaScript设计模式(8)-装饰者模式的更多相关文章

  1. 再起航,我的学习笔记之JavaScript设计模式13(装饰者模式)

    装饰者模式 装饰者模式(Decorator): 在不改变原对象的基础上,通过对其进行过包装拓展(添加属性高或者方法)使原有对象可以满足用户的更复杂需求. 如果现在我们有个需求,需要做一个提交表单,当我 ...

  2. Javascript设计模式之装饰者模式详解篇

    一.前言: 装饰者模式(Decorator Pattern):在不改变原类和继承的情况下动态扩展对象功能,通过包装一个对象来实现一个新的具有原对象相同接口的新的对象. 装饰者模式的特点: 1. 在不改 ...

  3. JavaScript设计模式-17.装饰者模式(下)

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  4. JavaScript设计模式-16.装饰者模式(上)

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  5. JavaScript设计模式(装饰者模式)

    一.模拟传统面向对象语言的装饰者模式: 假设我们在编写一个飞机大战的游戏,随着经验值的增加,我们操作的飞机对象可以升级成更厉害的飞机,一开始这些飞机只能发射普通的子弹,升到第二级时可以发射导弹,升到第 ...

  6. javascript设计模式之装饰者模式

    /* * 装饰者模式提供比继承更有弹性的替代方案 * 在不改变原构造函数的情况下,添加新的属性或功能 */ //需要装饰的类(函数) function Macbook() { this.cost = ...

  7. 学习javascript设计模式之装饰者模式

    1.装饰者模式定义:给对象动态添加职责的方式称为装饰者(decorator)模式. js如何实现装饰者模式 通过保存原函数引用方式改写某函数 window.onload = function(){al ...

  8. Java 设计模式泛谈&装饰者模式和单例模式

    设计模式(Design Pattern) 1.是一套被反复使用.多人知晓的,经过分类编目 的 代码设计经验总结.使用设计模式是为了可重用代码,让代码更容易维护以及扩展. 2.简单的讲:所谓模式就是得到 ...

  9. C#设计模式(9)——装饰者模式(Decorator Pattern)

    一.引言 在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).A ...

随机推荐

  1. BZOJ 2199: [Usaco2011 Jan]奶牛议会 [2-SAT 判断解]

    http://www.lydsy.com/JudgeOnline/problem.php?id=2199 题意:裸的2-SAT,但是问每个变量在所有解中是只能为真还是只能为假还是既可以为真又可以为假 ...

  2. BZOJ 2844: albus就是要第一个出场 [高斯消元XOR 线性基]

    2844: albus就是要第一个出场 题意:给定一个n个数的集合S和一个数x,求x在S的$2^n$个子集从小到大的异或和序列中最早出现的位置 一开始看错题了...人家要求的是x第一次出现位置不是第x ...

  3. IntelliJ IDEA使用心得之基础篇

    今天和大家分享一个非常好用的Java开发工具-IntelliJ IDEA. 下载地址:https://www.jetbrains.com/idea/ 目录: 1)IntelliJ IDEA使用心得之基 ...

  4. Linux C 一个简单的线程池程序设计

    最近在学习linux下的编程,刚开始接触感觉有点复杂,今天把线程里比较重要的线程池程序重新理解梳理一下. 实现功能:创建一个线程池,该线程池包含若干个线程,以及一个任务队列,当有新的任务出现时,如果任 ...

  5. Newtonsoft.Json 操作 JSON 字符串

    Newtonsoft.Json介绍 在做开发的时候,很多数据交换都是以json格式传输的.而使用Json的时候,我们很多时候会涉及到几个序列化对象的使用:DataContractJsonSeriali ...

  6. 微信小程序项目踩过的几个坑

    一.前言 近期,开始了一段辛酸的还未开始就已经结束的"创业"(参见我的第二次创业,以梦为马,莫负韶华).大体上是开发了一款微信小程序,关于创业这件事情就不细说了,本文主要介绍一下开 ...

  7. Angular4.0用命令行创建组件服务出错

    之前使用cnpm创建的angular4.0项目,由于cnpm下载的node_modules资源经常会有部分缺失,所以在用命令行创建模板.服务的时候会报错: Error: ELOOP: too many ...

  8. 【SSH框架】系列之 Spring 整合 Hibernate 框架

    1.SSH 三大框架整合原理 Spring 与 Struts2 的整合就是将 Action 对象交给 Spring 容器来负责创建. Spring 与 Hibernate 的整合就是将 Session ...

  9. Linux下Nodejs安装

    网上有很多node安装方法,最好用的应该是在windows下载nodejs linux版本,安装包移植到ubuntu中, 下载下来的是个双重压缩的文件.tar.xz,先解压xz,再解压tar xz - ...

  10. Java中equal和==区别及String创建过程

    Java中equal和==区别 1.起因 在一段Java代码中,使用了两种实现方式. //第一种命令行输入 int main (String[] args) { if(args[0] == " ...