在nodejs中面向对象:Bearcat

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

依赖倒置原则,它转换了依赖,高层模块不依赖于低层模块的实现,而低层模块依赖于高层模块定义的接口。

  • 低层接口依赖高层实现


    在nodejs中面向对象:Bearcat_第1张图片
    低层依赖高层

很明显当再有低层添加接口的时候,需要去重新修改高层的实现。

  • 高层接口依赖低层实现


    在nodejs中面向对象:Bearcat_第2张图片
    高层依赖低层

这样的设计,在添加低层模块实现的时候就让实现去适配接口,而不需要再修改高层接口的代码,所以明显是一种更加优秀的设计原则。那么下面就围绕着如何设计这种设计原则来展开。

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 来指明 propertiesname, 使用 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
    

构造 & 析构

beangetBean 调用请求时, 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
};

你可能感兴趣的:(在nodejs中面向对象:Bearcat)