Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

decorator Method in ng #18

Open
fnjoe opened this issue Jul 23, 2016 · 2 comments
Open

decorator Method in ng #18

fnjoe opened this issue Jul 23, 2016 · 2 comments

Comments

@fnjoe
Copy link

fnjoe commented Jul 23, 2016

前提 依赖注入与$injector

  1. AngularJS的世界里,依赖注入的实现使我们面向对象编码过程中,能够更清晰的管理类之间的耦合关系,将构造所需实例的逻辑使用统一的方法实现,在ng中,这个方法就是$injector。
  2. 当我们将依赖的实现交给$injector后,自身内部不再出现关于依赖的实现逻辑,但是,$injector帮我们实现的依赖有时并不能完全满足当前对象的逻辑,这时,我们需要在依赖的实现逻辑中加入我们自身的逻辑。
  3. 那么,问题来了,该部分逻辑应该放在哪里?首先,这部分逻辑不应该出现在自身内部,因为按照依赖注入的设计,当我们声明依赖时,只需关注自身如何使用依赖,并不关心依赖的实现过程;其次,不应该出现依赖自身的实现上,自定义的逻辑应该只适用于自身的依赖,不能改变该依赖本身的实现方式。

思考,能否在$injector的逻辑中,存在修改已注册依赖的入口,让我们针对具体对象能够配置对应的依赖,并且没有破坏原有依赖自身的实现逻辑?

依赖注入关注各个对象之间的关系,ng中将可以被依赖注入的对象,称为服务,以下将对依赖的修改统称为对服务的修改。

什么是decorator

首先来看ng官方的定义:

The $provide service has a number of methods for registering components with the $injector. Many of these functions are also exposed on angular.Module.

ng对decorator方法的划分也是在一个$provide的服务上,这样我们可以通过注入$provide来调用。下面来看具体的使用方式。

decorator(name, decorator);
Register(注册) a decorator function with the $injector. A decorator function intercepts(拦截) the creation of a service, allowing it to override(覆盖) or modify(修改) the behavior of the service. The return value of the decorator function may be the original service, or a new service that replaces (or wraps and delegates(代表) to the original service.

简单来说:

  1. decorator的本质是在$injector上注册了一个方法。

  2. 这个方法可以拦截一个服务的创建,并且可以覆盖或修改这个服务的行为。

  3. 该方法应该有一个返回值,这个值可以是原来的服务(即你没有做任何修改),也可以是一个替换或者修改并代表了原服务的一个全新的服务。

    很顺利,ng给我们提供了一个入口来切入服务创建的过程,并且能够写入自身的逻辑。
    就是本文的主角:decorator 方法

怎样使用decorator

结合我们的问题与ng给出的定义,很清楚的可以得到实现过程,首先来看,decorator方法在代码中使用:

decorator(name, decorator);

  1. name正是我们需要去添加自定义逻辑的已经注册的服务的名称。
  2. decorator是一个方法,这个方法的返回值正是我们需要的新的服务的实现方式。
  3. name很好理解,我们重点来关注第二个参数的实现方式,这里需要一个函数,在这个函数中我们希望能够获得原有的服务,这样我们可以在其基础上添加逻辑,并且返回新的服务来覆盖或修改原有服务。

This function will be invoked(运行) when the service needs to be provided(需要被提供) and should return the decorated service instance(修改过的服务实例). The function is called using the injector.invoke method and is therefore fully injectable(可注入的).
Local injection arguments:
$delegate - The original service instance, which can be replaced, monkey patched, configured, decorated or delegated to.

要拿到原有服务,我们需要为这个方法注入$delegate,同样的,我们可以注入更多的服务,来丰富自定义逻辑。

剩下的就很简单了,下面是一个简单的代码示例。

angular.module('app').config(config); //  在模块启动时更改原有服务逻辑

    config.$inject = ['$provide'];  //  注入$provide以便调用decorator方法

    function config($provide) {
                // 要修改的目标OriginService
        $provide.decorator('OriginService', OriginServiceDecorator); 

                // 注入$delegate来获得原有的服务实例,注入其他服务来实现逻辑
        OriginServiceDecorator.$inject = ['$delegate', 'other'];

        function OriginServiceDecorator($delegate, other) {
            var firstMethod= $delegate.firstMethod;

            function newFirstMethod() {
                // new service function
                other.someFunc();
                return firstMethod.apply($delegate, arguments);
            }

            $delegate.firstMethod= newFirstMethod;
            var secondMethod = $delegate.secondMethod ;

            function newSecondMethod () {
                other.someFunc();
                return secondMethod.apply($delegate, arguments);
            }

            $delegate.secondMethod = newSecondMethod ;
            // 通过以上的修改,原有的OriginService已经被我们新增加了自己的逻辑,
                        // 并且没有改变原有OriginService实现逻辑。仅在app模块以及其依赖模块上有效。
                       return $delegate;
        }
    }
@hjzheng
Copy link
Member

hjzheng commented Jul 24, 2016

关于decorator模式原生JS的写法
主要利用了Function对象的apply方法,对原有方法进行了劫持,从而给方法添加新的功能

var decorateFun = function(fun, before, after) {
    var Decorator = function() {
        var args = arguments,result,i;
        if (before && typeof before == 'function') {
            // 前置方法可对参数进行修改
            args = before.apply(this, args) || args;
        }
        result = fun.apply(this, args);
        if (after && typeof after == 'function') {
            // 后置方法可对结果进行修改
            result = after.apply(this,[result, args]) || result;
        }
        return result;
    }
    // 由于函数也是对象, 因此要将其属性都复制过来
    for (i in fun) {
        if (fun.hasOwnProperty(i)) {
            Decorator[i] = fun[i];
        }
    }
    Decorator.prototype = fun.prototype;
    // 保留原始函数对象, 以备复原
    Decorator.originalFunction = fun;
    return Decorator;
};

var computer = {
    add: function(x, y) { return x + y; } 
}

computer.add = decorateFun(computer.add,
    function() { 
           console.log('before: 修改参数');
           return [arguments[0]*2, arguments[1]*2]; 
    },
    function() { 
           console.log('after: 修改结果'); 
           return arguments[0]*2; 
     }
);

computer.add(1, 2);

@hjzheng
Copy link
Member

hjzheng commented Aug 25, 2016

AngularJS 的 $provide.decorator 可以装饰 provider 吗?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants