JS中的面向对象
最最最开始,我们先来说说JS中的面向对象。
原型链
参考文章:图解Javascript原型链 Javascript继承机制的设计思想
prototype & _proto_
所有实例对象需要共享的属性和方法,都放在这个 prototype
对象里面;那些不需要共享的属性和方法,就放在构造函数里面。
在JS中并不是所有对象都拥有 prototype
属性,只有函数类型的变量拥有该属性。也就是只有通过 function
,或者是与 function
对应的构造方法 new Function()
声明的变量。
而所有的JS对象是存在一个内置的 [[Prototype]]
属性,指向它“父类”的 prototype
。在Node当中就提供了一个 __proto__
代替了这个属性指向父类的 prototype
。
通过以下这种方式,就能获得一个对象的 root
原型。
var Obj = function(){};
var o = new Obj();
o.__proto__ === Obj.prototype; //=> true
o.__proto__.constructor === Obj; //=> true
Obj.__proto__ === Function.prototype; //=> true
Obj.__proto__.constructor === Function; //=> true
Function.__proto__ === Function.prototype; //=> true
Object.__proto__ === Object.prototype; //=> false
Object.__proto__ === Function.prototype; //=> true
Function.__proto__.constructor === Function;//=> true
Function.__proto__.__proto__; //=> {}
Function.__proto__.__proto__ === o.__proto__.__proto__; //=> true
o.__proto__.__proto__.__proto__ === null; //=> true
new
关键词的作用就是完成上图所示实例与父类原型之间关系的串接,并创建一个新的对象;
instanceof
关键词的作用也可以从上图中看出,实际上就是判断 __proto__
(以及 __proto__.__proto__
…)所指向是否父类的原型。
继承
阮一峰老师的博客给我们提供了多种的继承思路,主要是在构造函数的原型上做文章和拷贝父元素的属性。
而在node中,是为我们提供了一些继承方法的,我们这边简要的分析一下。
util.inherits
if (ctor === undefined || ctor === null)
throw new TypeError('The constructor to "inherits" must not be ' +
'null or undefined');
if (superCtor === undefined || superCtor === null)
throw new TypeError('The super constructor to "inherits" must not ' +
'be null or undefined');
if (superCtor.prototype === undefined)
throw new TypeError('The super constructor to "inherits" must ' +
'have a prototype');
ctor.super_ = superCtor;
Object.setPrototypeOf(ctor.prototype, superCtor.prototype);
前面全都是一些合法性检测,只有最后调用了setPrototypeOf
方法,将superCtor
的原型复制给了ctor
,这里我查看不了setPrototypeOf
方法,但是我能肯定复制以后,肯定将ctor.prototype
的构造方法指向了自己。所以这种继承方法,是不能继承构造函数里的属性的。
DIP设计原则
Bearcat是什么?Bearcat是一个IoC容器。看来我们学习Bearcat之前需要系统的学习一下什么是IoC容器了。来,先上一篇博文 深入理解DIP、IoC、DI以及IoC容器
- 依赖倒置原则(DIP):一种软件架构设计的原则(抽象概念)。
- 控制反转(IoC):一种反转流、依赖和接口的方式(DIP的具体实现方式)。
- 依赖注入(DI):IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)。
- IoC容器:依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架)。
DIP
依赖倒置原则,它转换了依赖,高层模块不依赖于低层模块的实现,而低层模块依赖于高层模块定义的接口。
-
低层接口依赖高层实现
很明显当再有低层添加接口的时候,需要去重新修改高层的实现。
-
高层接口依赖低层实现
这样的设计,在添加低层模块实现的时候就让实现去适配接口,而不需要再修改高层接口的代码,所以明显是一种更加优秀的设计原则。那么下面就围绕着如何设计这种设计原则来展开。
IoC
那么IoC控制反转就是DIP的一种实现,也就是我们常说的一种设计模式。控制反转(IoC),它为相互依赖的组件提供抽象,将依赖(低层模块)对象的获得交给第三方(系统)来控制,即依赖对象不在被依赖模块的类中直接通过new来获取。
简单来说类B依赖类A,原来的思想是在B中我们就去 new A
,然后进行使用。但是现在我们为它们解耦,不在B中 new A
,在其他地方 new A
然后传入给B,让B来进行使用。(其实后面大篇幅讨论的就是怎么传入给B)
那么我们为什么要这样呢?就是因为类A是可能随时会进行修改的(这里泛指各种修改,有可能名字都改了,叫C了)。那么修改以后,如果我们是在B中直接 new A
使用的话,还需要去修改类B。那么我们正是在规避这种行为。
DI
那么DI(依赖注入)就给了我们一种将A实例传入B的一种思路。当然这个大思路下是有多种方案的。
-
构造函数注入
顾名思义,构造函数注入,那么就是在B的构造函数中,将A实例传入B。关键就放在了B的构造函数如何定义了,如果定义成为某个具体的类,那么我们是不是就犯了高层依赖于低层的错误了。
public B(A a) { _a = a;//传递依赖 }
所以为了规避这个错误,我们应该让高层依赖于抽象类,让低层统一继承这个抽象类。
// 抽象类定义接口 public interface C { void Add(); } // A继承抽象类实现接口方法 public class A:C { // 实现Add方法 } // B的构造函数 public B(C c) { _c = c;//传递依赖 }
-
属性注入
属性注入就是通过修改B类中的属性,将A传入B。实现方式与依赖注入类似,都是中间依赖于一个抽象类。
IoC容器
IoC容器实际上是一个DI框架,它能简化我们的工作量。它包含以下几个功能:
- 动态创建、注入依赖对象。
- 管理对象生命周期。
- 映射依赖关系。
我觉得我们可以把IoC容器想成是一个更高级的工厂。
AOP
Bearcat同样是对AOP进行了支持,那么什么是AOP呢?我们再来详细说说AOP。
其实呢,这是一个我们一直在使用的思想。在Node的各种框架当中有一个东西叫中间件,而在Java中则变成了拦截器、过滤器。广义上来说,我觉得他们都可以说是AOP。那我们同样先放一篇学习资料:什么是面向切面编程AOP?
这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。
这样看来,AOP其实只是OOP的补充而已。
以上是在OOP中对于AOP的总结,那我们来看看如何更加广义的来看待AOP。对于切面编程我们需要考虑的是两个问题。我们啥时候(切点)干啥(增强/通知)。我们在多个业务流程当中如果有同样的业务,那么我们就可以将这个业务组织成一个切片,切入到我的这些业务流当中,而我只需要关注它应该啥时候切入就可以。
真正的bearcat
所有基础知识准备好了以后,我们来学习一下真正的bearcat。
IoC容器
依赖注入
-
基于构造函数的依赖注入
{ "name": "simple_inject_args", "beans": [{ "id": "car", "func": "car", "args": [{ "name": "engine", "ref": "engine" }] }, { "id": "engine", "func": "engine" }] }
使用 args
属性来表明这是个基于构造函数的DI。在 args
属性里, 使用 name
属性来指定参数所对应的名字(不影响注入过程, 方便看明白注入关系), ref
属性来指定当前容器中需要被注入的 bean
的名字(唯一id)。
除了可以通过构造函数注入另一个bean对象之外, 还可以在构造函数中注入 value
(值) 和 var
(变量, getBean 调用时可以传入具体参数)。
-
基于对象属性的依赖注入
{ "name": "simple_inject_args", "beans": [{ "id": "car", "func": "car", "props": [{ "name": "engine", "ref": "engine" }] }, { "id": "engine", "func": "engine" }] }
使用 props
属性来表面是基于 Properties
的 DI. 同样的, 在 props
属性里, 使用 name
来指明 properties
的 name
, 使用 ref
来指明在当前容器中需要注入的bean的名字.
懒加载
在默认情况下,ApplicationContext
在启动的时候会积极的创建和配置所有的单例 beans
。当然你可以使用参数来制止 beans
的预加载,使该 beans
在第一次被使用的时候加载。
{
"name": "simple_lazy_init",
"beans": [{
"id": "car",
"func": "car",
"lazy": true
}]
}
Scope
-
单例
在默认情况下,bean
是符合单例模式的,每次对名叫id的bean
的请求都返回同一个bean
实例。{ "name": "simple", "beans": [{ "id": "car", "func": "car", "scope": "singleton" }] }
main.js
var car1 = bearcat.getBean('car'); var car2 = bearcat.getBean('car'); // car2 is exactly the same instance as car1
-
多例
当然也可以通过修改配置文件来将其改变为多例,每次对名叫id的bean
的请求,容器都会创建一个新的bean
实例。{ "name": "simple", "beans": [{ "id": "car", "func": "car", "scope": "prototype" }] }
main.js
var car1 = bearcat.getBean('car'); var car2 = bearcat.getBean('car'); // car2 is not the same instance as car1
构造 & 析构
当 bean
被 getBean
调用请求时, init
方法会被调用来做一些初始化的事情。但是我现在还没有找到析构函数是在什么时候被调用?因为Node与C是不同的,垃圾回收部分是交到V8手里去处理的,后面再来补这个坑。
var Car = function() {
this.num = 0;
}
// 构造函数
Car.prototype.init = function() {
console.log('init car...');
this.num = 1;
return 'init car';
}
// 析构函数
Car.prototype.destroy = function() {
console.log('destroy car...');
return 'destroy car';
}
Car.prototype.run = function() {
console.log('run car...');
return 'car ' + this.num;
}
module.exports = Car;
content.js
{
"name": "simple_destroy_method",
"beans": [{
"id": "car",
"func": "car",
"init": "init",
"destroy": "destroy"
}]
}
这里需要注意的是异步 init
函数,可以将 async
标为 true
来得到一个异步构造函数,那么多个异步构造函数使用时,我们可以通过设置 order
值,来为多个构造函数来排序调用。
{
"name": "simple_async_init",
"beans": [{
"id": "car",
"func": "car",
"init": "init",
"order": 2
}, {
"id": "wheel",
"func": "wheel",
"async": true,
"init": "init",
"order": 1
}]
}
继承
一个子 bean
定义可以继承在父 bean
定义。子 bean
定义可以覆盖一些值,添加另外一些值。使用 bean
定义继承可以节省很多事情,这其实是模板的一种方式。
bus.js
var Bus = function(engine, wheel, num) {
this.engine = engine;
this.wheel = wheel;
this.num = num;
}
Bus.prototype.run = function() {
return 'bus ' + this.num;
}
module.exports = {
func: Bus,
id: "bus",
parent: "car",
args: [{
name: "engine",
ref: "engine"
}, {
name: "wheel",
ref: "wheel"
}]
};
car.js
var n = 1;
var Car = function(engine, wheel, num) {
this.engine = engine;
this.wheel = wheel;
this.num = num;
n++;
};
Car.prototype.run = function() {
this.engine.start();
this.wheel.run();
console.log(this.num);
}
module.exports = {
func: Car,
id: "car",
args: [{
name: "engine",
ref: "engine"
}, {
name: "num",
value: 100
}, {
name: "wheel",
ref: "wheel"
}],
order: 1
};