【问题标题】:Problems with circular dependency and OOP in AngularJSAngularJS 中的循环依赖和 OOP 问题
【发布时间】:2013-10-21 01:44:59
【问题描述】:

AngularJS + OOP 使用起来有点性感

您好,我已经成功使用 AngularJs 的 OOP 有一段时间了(首先从 angularjs with oop inheritance in action 开始),提供的方法允许您将您的类定义为 Angular 服务,以后您可以像这样扩展或继承它:

Application.factory('AbstractObject', [function () {
    var AbstractObject = Class.extend({
        virtualMethod: function() {
           alert("Hello world");
        },
        abstractMethod: function() { // You may omit abstract definitions, but they make your interface more readable
           throw new Error("Pure abstract call");
        }
    });

    return AbstractObject; // You return class definition instead of it's instance
}]);

Application.factory('DerivedObject', ['AbstractObject', function (AbstractObject) {
    var DerivedObject = AbstractObject.extend({
        virtualMethod: function() { // Shows two alerts: `Hey!` and `Hello world`
            alert("Hey!");

            this._super();
        },
        abstractMethod: function() {
            alert("Now I'm not abstract");
        }
    });

    return DerivedObject;
}]);

Plunker:http://plnkr.co/edit/rAtVGAsNYggBhNADMeoT

使用所描述的方法使您能够定义完美地集成到 Angular 基础架构中的类。你可以从两个世界——OOP 和 AngularJs 中获得各种漂亮的特性。依赖注入对您的类来说是免费的,它使您的类变得简单,允许将大量样板控制器代码放入一些以后可以重用的基类中。

然而

AngularJs 基础设施阻止了之前描述的方法,使其无法 100% 展开。当您尝试定义递归类定义(即递归聚合)时会出现问题,假设您有两个类定义,如 BlogTag

Application.factory('Blog', ['Tag', function (Tag) {
    var Blog = Class.extend({
        tags: function() {
            return this.tags;
        }
    });

    return Blog;
}]);

Application.factory('Tag', ['Blog', function (Blog) {
    var Tag = Class.extend({
        Blogs: function() {
           return this.blogs;
        }
    });

    return Tag;
}]);

它不起作用,因为BlogTag 都在自我引用自己,从而导致循环依赖。

附言

最后一件事,我找到了一个丑陋的解决方案,可以在我的特定情况下解决我的问题,但通常不起作用,正如我所说,它并不漂亮:

Application.factory('BlogNamespace', [function () {
    var Blog = Class.extend({
        tags: function() {
            return this.tags;
        }
    });

    var Tag = Class.extend({
        Blogs: function() {
           return this.blogs;
        }
    });

    return {
        Tag: Tag,
        Blog: Blog
    };
}]);

问题

上述修复不起作用,因为命名空间也可能是循环依赖的主题。这意味着它不是描述问题的解决方案,而是现在更深层次的问题。

关于如何在一般情况下解决所描述的问题有什么建议吗?

【问题讨论】:

    标签: javascript oop angularjs


    【解决方案1】:

    循环依赖总是关注点混合的标志,这是一件非常糟糕的事情。 AngularJS 的作者之一 Miško Hevery 解释了一个很好的解决方案on his awesome blog。简而言之,您可能在某处隐藏了第三个服务,这是其他两个真正需要的代码中唯一的部分。

    【讨论】:

    • Blackhole,您提供的帖子非常有趣,我对TagBlog 的案例证明Misko 是正确的,因为这些是一种将彼此关联为N 的数据库实体: M 和在关系基础世界中,这种情况也可以通过表示第三个关系表来解决,该表同时引用 TagBlog,例如 BlogTags
    • 到目前为止一切顺利...飞行很稳定,所描述的方法确实有助于识别有问题的实施点。
    • 我认为你的方法是正确的,在代码中使用循环 deps 是一件坏事,所以答案交给你
    • @Lu4 你是否通过添加BlogTag 注入的BlogTags 工厂解决了这个问题?如果可以的话,我真的不想在我的代码库中添加“连接表”类......如果我唯一的其他选择是使用 $injetor.get 的循环依赖项,正如你所指出的那样,我可能会坚持下去。跨度>
    • 是的,我有一个服务/模型,它需要来自其他模型的计数(实际上是 n 个深层结构的层次结构图)来生成“小部件”来显示该数据。包含数据的所有业务逻辑都是分开的,但是要生成绑定到这些模型的新小部件实例,我需要先查找元数据,然后再生成它们......对于这个用例,我会觉得 $injector.get 是安全的。跨度>
    【解决方案2】:

    我正在回答我自己的问题,只是因为我找到了解决我最初发布的问题的技术方法。但在此之前,我强烈建议您使用 Blackhole 的建议,因为它可以解决通常由不良架构引起的更广泛的问题。请先使用他的方法,如果您知道自己在做什么,请返回当前的方法。

    所以这里是:

    您可以使用$injector 服务并在运行时注入所需的定义,这从技术角度来看是合法的,但再次根据this 帖子(很难想象它是在 2008 年编写的),这是像黑魔法一样,这样做,它会反击你:

    Application.factory('Blog', ['$injector', function ($injector) {
        var Tag = $injector.get('Tag'); // Here is your tag
    
        ...    
    }]);
    
    Application.factory('Tag', ['Blog', function (Blog) {
        ...
    }]);
    

    编辑

    事实证明,目前的方法是服务定位器模式的一个例子,即 IoC 反模式。

    【讨论】:

    • 这个答案拯救了我的一天! +1
    • 加入黑暗面 :)
    • @RodrigoQuesada 您的建议是一位真正年轻而充满激情的开发人员的建议 :) 您接受教条和范式真的很酷,这意味着您可以选择自己的方式。但是对我来说,这看起来很有趣,因为我已经太老了,无法遵循它。请不要把我的话当作侮辱,而是把它们当作经验的声音,你未来的声音。我有很多理由担心你的提议。其中之一是我知道它们是由许多无辜开发人员的鲜血编写的。而且我不需要每个人都同意这个事实,我只需要聪明人
    • 这行不通,$injector 实际上是报告循环依赖关系以jsfiddle.net/iamwhitebox/1z2kua70开始的原因
    • @whitebox 它不适用于单元测试,但如果您在模块首次初始化后使用它(例如,在模块中的函数中使用它)它可以正常工作。我认为 Lu4 在写答案时简化了案例,以便简短。
    【解决方案3】:

    最后的手段:不鼓励

    在我的情况下,以角度解决此类循环依赖问题的最佳方法是通过$rootScope-broadcasts 触发函数调用。然后,其他服务可以收听此广播并对所需的函数调用做出反应。它可能不是最优雅的解决方案,但在某些情况下,无论如何服务之间的交互主要是单向的,它可能是一个合理的替代方案。 (请注意,这也允许仅通过回调将返回值传递回广播函数)


    一个伪例子是:

    angular.module('myApp').factory('service1', ["$rootScope",
      function($rootScope) {
        function func1() {
          // do something
        }
        $rootScope.$broadcast("callFunc2"); // calls func2 from service 1
    
        return {
          func1: func1
        }
      }
    ]);
    angular.module('myApp').factory('service2', ["service1", "$rootScope",
      function(service1, $rootScope) {
        function func2() {
          // do something
        }
        service1.func1();  // calls func1 from service 2
        $rootScope.on("callFunc2", func2);
      }
    ]);
    

    【讨论】:

    • @Lu4 有什么建设性的批评吗?
    • 当我发布我自己的问题的答案时,我认为我已经达到了“黑暗”的极限(我指的是 cmets 中的“黑暗面”笑话),但现在我清楚地看到了我的与你的相比,答案像 100 瓦电灯泡一样闪耀)))
    • 说真的,这种代码(即$broadcast)从未被修改为在生产中广泛使用(我的意思是你提出的方式)这就是为什么它前面有这个$美元符号. $broadcast 是 Angular 引入的最大设计缺陷之一。由于缺乏对 Angular 第一版的理解/文档/示例,它在胚胎社区中获得了普及。在大多数情况下,几乎每个涉及$broadcast 的用例都可以重写为直接函数调用。使用$broadcast 表明您的应用架构存在问题。
    • $broadcast 唯一擅长的就是让你的代码变得不透明和不可预测。查看某人的代码,您永远不知道需要订阅哪个事件。您永远无法确定您订阅了必要的订阅者。当事件发生时,你永远不知道是谁发起的。它总是接近魔法,黑色,黑魔法。不要使用它,它会让你的项目最终变得糟糕,非常糟糕。
    • @Lu4 : 哦,经验教训 ^^ 我想现在我们有一个很好的反例来说明如何不解决这个问题。我会检查我的架构,看看我是否可以避免完全是问题。所以是的:欢迎来到黑暗面。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-19
    • 2013-01-08
    相关资源
    最近更新 更多