JavaScript DI / IoC等同于静态类型语言的标准DI模式

.NET和Java都有大量的DI / IoC容器可供它们使用,每个容器都有许多模式,我发现这些模式在处理它们的各个方面非常有用。 我现在正想在JavaScript中做同等的事情。 由于JavaScript是一种动态语言,我不希望DI / IoC容器直接等同于静态类型语言中容器提供的所有function,因此欢迎使用这些模式的替代方案。 我还希望JavaScript中提供的DI / IoC容器的function会有所不同,因此对不同容器的引用非常受欢迎。

以下模式是Autofac 3支持的模式 ,我认为这些模式适用于动态语言。 有关这些模式和关系的一般信息,请参阅http://autofac.readthedocs.org/en/latest/resolve/relationships.html和http://nblumhardt.com/2010/01/the-relationship-zoo/ 。 以下概念中的大多数(如果不是全部)也可以使用其他语言和DI / IoC容器,例如Google Guice和Spring 。

与JavaScript中下面描述的概念和模式最接近的等价物是什么?

一般概念

概念1:向IoC容器注册

在IoC容器可以创建类型的实例之前,它需要知道类型。 这是通过注册完成的。 注册通常以声明方式完成:

class A {} var builder = new ContainerBuilder(); builder.RegisterType(); 

上面使IoC容器知道类型A.它通过reflection发现A的依赖性。 注册也可以通过充当工厂的function进行。 这些函数通常是lambdas,可以内联编写:

 class B {} class A { A(string name, B b) { } } builder.RegisterType(); builder.Register(c => // c is a reference to the created container new A("-name-", c.Resolve())); 

当您的类型需要使用非服务的依赖项(例如上面示例中的名称)进行参数化时,能够提供工厂函数尤其有用。

由于C#支持接口和抽象类,因此它通常不是重要的具体数据类型,而是它实现的抽象类型。 在这些情况下,您将该类型注册为应该可用的接口或抽象类:

 interface IPlugin {} class P : IPlugin builder.RegisterType

().As();

通过上述注册,任何请求P尝试都将失败,但是对IPlugin的请求将成功。

概念2:容器创建和组合根

完成所有注册后,需要创建容器:

 public class Program { public static void Main(string[] args) { var builder = new ContainerBuilder(); /* perform registrations on builder */ var container = builder.Build(); /* do something useful with container */ } } 

容器在程序生命周期的早期创建,并成为组合根 – 组成应用程序所有部分的代码中的位置,确保创建所有必需的依赖项。 然后使用该容器解析应用程序中的主要组件:

 public static void Main(string[] args) { var builder = new ContainerBuilder(); /* perform registrations on builder */ var container = builder.Build(); var application = container.Resolve(); application.Launch(); } 

概念3:终身和实例管理

鉴于:

 class A {} 

如果我们想要为每个依赖项创建一个新的A实例,则可以将其注册为builder.RegisterType()而无需进一步指定任何内容。

如果我们希望每次需要将其注册为“SingleInstance”时返回相同的A实例:

 builder.RegisterType().SingleInstance(); 

有时我们希望在特定范围内共享实例,但对于不同的范围,我们需要不同的实例。 例如,我们可能希望在用于处理特定HTTP请求的所有DAO中共享单个数据库连接。 这通常通过为每个HTTP请求创建新范围,然后确保使用新范围来解决依赖关系来完成。 在Autofac中,可以手动控制,如下所示:

 builder.RegisterType().InstancePerLifetimeScope(); var scope = container.BeginLifetimeScope(); // within the request's scope var root = scope.Resolve(); root.Process(); 

一般模式

模式1:A需要B的实例

 class B {} // registered as: builder.RegisterType() class A { // registered as: builder.RegisterType() A(B b) {} } var a = container.Resolve(); 

IoC容器使用reflection来发现对B的依赖并注入它。

模式2:A在将来的某个时刻需要B

 class B {} class A { A(Lazy lazyB) { // when ready for an instance of B: try { var b = lazyB.Value; } catch (DependencyResolutionException) { // log: unable to create an instance of B } } } 

在这种模式中,依赖性的实例化需要由于某种原因而被延迟。 在这种情况下,我们假设B是由第三方创建的插件,其构造可能会失败。 为了安全地使用它,必须保护对象结构。

模式3:需要创建B的实例

 class B {} class A { A(Func factory) { try { // frequently called multiple times var b = factory.Invoke(); } catch (DependencyResolutionException) { // log: Unable to create } } } 

当需要创建非值对象的多个实例时,通常使用此模式。 这也允许延迟实例的创建,但通常这样做的原因与模式2中的不同(A在将来的某个时刻需要B)。

模式4:A将类型X和Y的参数提供给B.

 class X {} class Y {} class B { B(X x, Y y) { } } 

当需要控制或配置注入的依赖项时,通常使用此模式。 例如,考虑一个需要提供数据库连接字符串的DAO:

 class DAO { DAO(string connectionString) {} } class A { A(Func daoFactory) { var dao = daoFactory.Invoke("DATA SOURCE=..."); var datum = dao.Get(); } } 

模式5:A需要各种B

 interface IPlugin {} class X: IPlugin {} // builder.RegisterType().As() class Y: IPlugin {} // builder.RegisterType().As() class Z: IPlugin {} // builder.RegisterType().As() class A { A(IEnumerable plugins) { foreach (var plugin in plugins) { // Add all plugins to menu } } } 

在该模式中,针对给定类型进行多次注册。 然后,消费者可以请求该类型的所有实例并相应地使用它们。

模式6:A需要知道B,或者A需要知道关于B的X.

 class B {} // builder.RegisterType().WithMetadata("IsActive", true); // A needs to know about B class A { A(Meta metaB) { if ((bool)metaB.Metadata["IsActive"]) { // do something intelligent... } } } // OR... class B {} // builder.RegisterType().WithMetadata(...); class X { bool IsActive { get; } } // A needs to know X about B class A { A(Meta metaB) { if (metaB.IsActive) { // do something intelligent... } } } 

让我们再次说我们有一个使用插件的系统。 可以根据用户的意愿启用或禁用插件或重新排序插件。 通过将元数据与每个插件相关联,系统可以忽略非活动插件,或者按照用户期望的顺序放置插件。

模式7:上述模式的组成

 interface IPlugin: class Plugin1 : IPlugin {} class Plugin2 : IPlugin {} class Plugin3 : IPlugin {} class PluginUser { PluginUser(IEnumerable<Lazy> lazyPlugins) { var plugins = lazyPlugins .Where(CreatePlugin) .Where(x => x != null); // do something with the plugins } IPlugin CreatePlugin(Lazy lazyPlugin) { try { return lazyPlugin.Value; } catch (Exception ex) { // log: failed to create plugin return null; } } } 

在此代码示例中,我们请求包含在Lazy对象中的所有插件的列表,以便可以在将来的某个时间点创建或解析它们。 这允许他们的实例化被保护或过滤。

模式8:适配器

此示例取自: https : //code.google.com/p/autofac/wiki/AdaptersAndDecorators

 interface ICommand {} class SaveCommand: ICommand {} class OpenCommand: ICommand {} var builder = new ContainerBuilder(); // Register the services to be adapted builder.RegisterType() .As() .WithMetadata("Name", "Save File"); builder.RegisterType() .As() .WithMetadata("Name", "Open File"); // Then register the adapter. In this case, the ICommand // registrations are using some metadata, so we're // adapting Meta instead of plain ICommand. builder.RegisterAdapter<Meta, ToolbarButton>( cmd => new ToolbarButton(cmd.Value, (string)cmd.Metadata["Name"])); var container = builder.Build(); // The resolved set of buttons will have two buttons // in it - one button adapted for each of the registered // ICommand instances. var buttons = container.Resolve<IEnumerable>(); 

以上内容允许注册的所有命令自动适应ToolbarButton ,使其易于添加到GUI。

模式9:装饰者

 interface ICommand { string Name { get; } bool Execute(); } class SaveCommand : ICommand {} class OpenCommand : ICommand {} class LoggingCommandDecorator: ICommand { private readonly ICommand _cmd; LoggingCommandDecorator(ICommand cmd) { _cmd = cmd; } bool Execute() { System.Console.WriteLine("Executing {0}", _cmd.Name); var result = _cmd.Execute(); System.Console.WriteLine( "Cmd {0} returned with {1}", _cmd.Name, result); return result; } } // and the corresponding registrations builder.RegisterType().Named("command"); builder.RegisterType().Named("command"); builder.RegisterDecorator((c,inner) => new LoggingCommandDecorator(inner), fromKey: "command"); // all ICommand's returned will now be decorated with the // LoggingCommandDecorator. We could, almost equivalently, use // AOP to accomplish the same thing. 

摘要

首先,虽然我试图使这些例子合理地代表所描述的模式,但这些是说明性的玩具示例,由于空间限制可能不是理想的。 对我来说更重要的是概念,模式和最近的JavaScript等价物。 如果JavaScript中的大多数IoC / DI容器不支持上面的某些模式,因为有更简单的方法可以做到这一点,足够公平。

与JavaScript中下面描述的概念和模式最接近的等价物是什么?

您提到的一些function是通过使用AMD实现的。 例如,看看RequireJS: http: //requirejs.org/docs/whyamd.html

另一个值得关注的实现是Angular JS DI: https : //docs.angularjs.org/guide/di

您可以轻松地注入模块(封装和元数据中包含的function单元),从而实现抽象。 它允许切换实现,在测试单元中运行模拟等等。

希望能帮助到你