AngularJS 中循环依赖和 OOP 的问题

IT技术 javascript oop angularjs
2021-01-24 00:53:33

AngularJS + OOP 是一种使用起来很性感的特性

嗨,我已经成功地将 OOP 与 AngularJs 一起使用了一段时间(首先从angularjs开始, 在操作中使用 oop 继承),提供的方法允许您将类定义为 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
    };
}]);

问题

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

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

3个回答

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

我认为你的方法是正确的,在代码中使用循环 deps 是一件坏事,所以答案就交给你了
2021-03-17 00:53:33
@ LU4你加入一个解决这个BlogTags工厂,BlogTag获得与注射?如果可以的话,我真的宁愿不将“连接表”类添加到我的代码库中……如果我唯一的其他选择是使用$injetor.get您指出的循环依赖项,我可能会坚持下去。
2021-03-29 00:53:33
黑洞,它就是你提供了非常有趣的帖子,我的情况与TagBlog证明MISKO是正确的,因为这有几分是涉及一个到另一个如N数据库实体:M和关系基础的世界这种情况也被解决表示保存参照上述两个第三关系表TagBlog诸如BlogTags
2021-04-01 00:53:33
是的,我有一个服务/模型,它需要来自其他模型的计数(实际上是 n 个深度结构的层次结构图)来生成“小部件”来显示该数据。包含的数据具有独立的所有业务逻辑,但是要生成绑定到这些模型的新小部件实例,我需要在生成它们之前首先查找元数据......我会觉得在这个用例中使用 $injector.get 是安全的。
2021-04-01 00:53:33
到目前为止一切顺利......飞行稳定,描述的方法确实有助于识别有问题的实施点。
2021-04-11 00:53:33

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

所以这里是:

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

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

    ...    
}]);

Application.factory('Tag', ['Blog', function (Blog) {
    ...
}]);

编辑

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

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

最后的手段:不鼓励

在我的情况下,在 angular 中解决这样的循环依赖问题的最佳方法是通过$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);
  }
]);
唯一$broadcast做得好的就是让你的代码不透明和不可预测。查看某人的代码,您永远不知道需要订阅哪个事件。您永远无法确定是否订阅了必要的订阅者。当事件发生时,你永远不知道是谁发起的。它总是接近魔法,黑色,黑魔法。不要使用它,它会让你的项目变得很糟糕,真的很糟糕。
2021-03-15 00:53:33
@Lu4:哦,吸取教训^^ 我想现在我们有一个很好的反例来说明如何不解决这个问题..我会检查我的架构,看看我是否可以完全避免这个问题。所以是的:欢迎来到黑暗的一面。
2021-03-29 00:53:33
在发布我自己的问题的答案时,我以为我已经达到了“黑暗”的极限(我指的是评论中的“黑暗面”笑话),但现在我清楚地看到我的答案像 100 瓦电灯泡一样闪耀给你的)))
2021-03-31 00:53:33
说真的,这种代码(即$broadcast)永远不会在生产中广泛使用(我的意思是你提出的方式),这就是为什么它$前面有这个美元符号。$broadcast是有史以来引入 angular 的最大设计缺陷之一。它之所以在胚胎社区中流行,只是因为对 angular 的第一个版本缺乏理解/文档/示例。在大多数情况下,几乎所有涉及的用例$broadcast都可以重写为直接函数调用。使用$broadcast是您的应用程序架构中存在问题的标志。
2021-03-31 00:53:33
@Lu4 有什么建设性的批评吗?
2021-04-04 00:53:33