对于[JavaScript]以BDD手写依赖注入(dependencyinjection)感兴趣的读者,本文将提供您所需要的所有信息,并且为您提供关于14.AutoMapper之依赖注入(Depend
对于[JavaScript]以BDD手写依赖注入(dependency injection)感兴趣的读者,本文将提供您所需要的所有信息,并且为您提供关于14.AutoMapper 之依赖注入(Dependency Injection)、ABAP模拟Java Spring依赖注入(Dependency injection)的一个尝试、Angular2 Dependency Injection、Dependency Injection的宝贵知识。
本文目录一览:- [JavaScript]以BDD手写依赖注入(dependency injection)
- 14.AutoMapper 之依赖注入(Dependency Injection)
- ABAP模拟Java Spring依赖注入(Dependency injection)的一个尝试
- Angular2 Dependency Injection
- Dependency Injection
[JavaScript]以BDD手写依赖注入(dependency injection)
程序编写过程中,常常面临的困境是写的时候行云流水,运行的时候捶胸捣腿!
那为什么会出现这种状况?编写习惯、或者说编写流程起了重要因素,譬如:没有测试用例——这表示你在编写时压根儿没想过时刻追踪编写内容的正确性、健壮性;也没考虑过程序如何适应来自PM的花样需求。
今天不谈理论,我们就来通过BDD(行为驱动开发)的模式来完成一个类似AngularJS
里依赖注入的简单模块。通过这种方式,我们来感受一下BDD
对于开发过程的帮助。
什么是BDD
BDD(行为驱动开发)是第二代的、由外及内的、基于拉(pull)的、多方利益相关者的(stakeholder)、多种可扩展的、高自动化的敏捷方法。它描述了一个交互循环,可以具有带有良好定义的输出(即工作中交付的结果):已测试过的软件。
好吧,肯定有人会问“这他妈是人话么”。
那我换个说法,我们需要这样一种编程方式,她从需求出发,描述模块使用的各个场景,并通过测试用例监督其行为;即便一开始无法完成所有预计功能,也可以通过迭代等方式持续交付;而且在需求变更时,也能通过测试用例来确认模块的健壮性。
那么“测试用例”就是其中的关键,常见的BDD测试框架有:mocha,jasmine...。BDD测试框架的代码有极鲜明的声明式风格,代码是可“读”的!
好了,废话不敢太多,我们这就打马上路。本章所涉源码在此:naive-dependency-injection
创建项目
mkdir simple-di; cd simple-di
npm init
npm init
后,参考下图作答。我们这里使用mocha
作为测试框架,所以test command
必须为mocha
接着来,创建一个源码目录(src);一个测试用例目录(test):
mkdir src test
还有,既然在npm init
时都声明了使用mocha
作为测试命令,那不装一个mocha
似乎也说不过去嘛:
npm install --save-dev mocha
到此,各工作目录准备就绪,来试运行一下测试命令吧:
npm test
因为一个测试用例也没写,所以
0 passing
,完美,没毛病!
整装待发
准备测试用例:
touch test/ut.js
用你喜欢的工具打开test/ut.js
,并写入如下内容:
''use strict'';
var assert = require(''assert'');
var DI = require(''../src/DI'');
describe(''test dependency injection'', function() {
//TODO:这里后面我们会陆续加入各个cases
});
需求1:可以注入字面量
我们的第一个需求可以总结出如下特征:
她得是一个类(可被实例化)
她得有一个注册方法,可以接受两个参数,分别是一个名字、一个字面量
她还有一个运行方法,可以像
AngularJS
那样接受一个数组,数组的最后一项是即将被执行的函数,其余都是要被注入到执行函数里的实例名字
从需求出发,我们按照期待模块的“行为”,写出测试用例:
it(''injecting literal object'', function() {
//她是一个类(可被实例化)
var app = new DI();
//她有一个注册方法,可以接受两个参数,分别是一个名字、一个字面量
app.register(''duck'', {
fly: function() {
return ''flying'';
}
});
var msg;
//她有一个运行方法,接受一个数组,数组的最后一项是即将被执行的函数,其余都是要被注入到执行函数里的实例名字
app.run([''duck'', function(d) {
msg = d.fly();
}]);
assert.equal(msg, ''flying'', ''字面量注入失败'');
});
将以上case放入之前准备好的
test/ut.js
的TODO
位置
这时候,我们如果运行测试用例(npm test
),结果可想而知:
第一次解决问题的时候来了,根据错误描述,我们得知“Cannot find module ''../src/DI''”,解决方法,异常简单,请看
touch src/DI.js
再次运行测试用例(npm test
),得到如下结论:
这次的问题是“DI is not a function”,因为刚才我们仅仅创建了src/DI.js
文件,并没有提供任何内容,所以当然not a function
。那我们现在给src/DI.js
提供一个function
作为返回值吧:
''use strict'';
var DI = function() {};
module.exports = DI;
继续运行测试用例(npm test
),看到如下结论:
这次是“app.register is not a function”,很好修复该问题,声明一下register
方法不就好了么,顺便我们可以联想到app.run
肯定也得not a function
,也一起补了吧:
DI.prototype.register = function(name, inst) {};
DI.prototype.run = function(arr) {};
不骄不躁,我们再npm test
一次:
总算入了正题,断言assert.equal(msg, ''flying'', ''字面量注入失败'');
失败了,因为我们只是声明了要用到的各个方法,并没有写任何实现,那么先从构造函数改起吧:
var DI = function() {
//我们需要一个成员store来保存register方法注册的名字和其对应的实例
this.store = {};
};
二来修改register
方法:
DI.prototype.register = function(name, inst) {
//将名字和对应的实例存入store
this.store[name] = inst;
};
三来补充run
方法:
DI.prototype.run = function(arr) {
//1. 数组arr末尾元素的下标
var lastIndex = arr.length - 1;
//2. 取出末尾元素,作为待执行函数
var cb = arr[lastIndex];
//3. 取出除末尾元素的其它所有元素,为待注入的实例名字列表
var argsName = arr.slice(0, lastIndex);
//4. 将上述实例名字列表依次从store中获取对应的实例,组成真正待执行函数的参数列表
var args = argsName.map(name => this.store[name]);
//5. 执行第2步取出的函数,并传入第4步的到的参数
cb.apply(null, args);
};
来吧,是时候证明自己了:
需求2:可以注入class
我们的第二个需求是在前一个基础上做增量:
她得有一个注册方法,可以接受两个参数,分别是一个名字、一个字面量或者class
有了新的需求,我们也得为新“行为”,添加测试用例:
it(''injecting class'', function() {
//她依旧是一个类(可被实例化)
var app = new DI();
var woman = function() {
this.cry = function() {
return ''crying'';
};
};
//她依旧有一个注册方法,只不过接受的实例也可以是class
app.register(''woman'', woman);
var msg;
app.run([''woman'', function(w) {
msg = w.cry();
}]);
assert.equal(msg, ''crying'', ''failed injecting class'');
});
代码不改,想来也是不能工作的:
因为我们之前没适配过class
注入的情况,所以这里注入的仅仅是woman
的类声明,并非instance
,所以需要对之前的实现进行调整。这时测试用例的好处就体现出来了,修改完src/DI.js
后,只要再次npm test
,轻轻松松证明实现是否满足了新需求,又不破坏原来的功能。
我们需要调整的地方,仅仅是register
方法:
DI.prototype.register = function(name, inst) {
this.store[name] = typeof inst === ''function'' ? new inst() : inst;
};
运行测试用例(npm test
)后
再次happy
需求3:可以根据$inject属性注入实例
我们的第三个需求依旧是在前一个基础上做增量:
她有一个运行方法,可以像
AngularJS
那样接受一个待执行函数,而这个函数有一个$inject
属性,该属性为数组,指定了要注入给待执行函数的实例名字列表
根据新需求,为新“行为”添加测试用例:
it(''injecting with $inject attr'', function() {
//她依旧是一个类(可被实例化)
var app = new DI();
var man = function() {
this.fight = function() {
return ''fighting'';
};
};
app.register(''man'', man);
app.register(''cat'', {
action: function() {
return ''scratch'';
}
});
var msg1,
msg2;
var exec = function(w, c) {
msg1 = w.fight();
msg2 = c.action();
};
exec.$inject = [''man'', ''cat''];
//run方法接受一个函数,该函数的$inject属性声明了他的依赖
app.run(exec);
assert.equal(msg1, ''fighting'', ''failed injecting with $inject attr'');
assert.equal(msg2, ''scratch'', ''failed injecting with $inject attr'');
});
运行测试用例(npm test
)后,这当然是错的:
因为这次我们run
的,压根不是个数组,而是一个附有$inject
属性的函数,所以src/DI.js
的run
方法源码必须调整:
DI.prototype.run = function(arr) {
var cb,
argsName,
args,
lastIndex = arr.length - 1;
//传入数组时,一切照旧
if (Array.isArray(arr)) {
cb = arr[lastIndex];
argsName = arr.slice(0, lastIndex);
} else if (arr.$inject) {//传入函数,并附有$inject属性时,$inject就是argsName
cb = arr;
argsName = arr.$inject;
}
//剩下照旧
args = argsName.map(name => this.store[name]);
cb.apply(null, args);
};
又成功的迈进了一步,high不high?
最后的需求:根据函数反射注入实例
我们的第四个需求也是终极需求,特征如下:
她有一个运行方法,可以像
AngularJS
那样接受一个待执行函数,并能仅根据函数本身注入正确的实例
没别的,先补充测试用例:
it(''injecting with reflection'', function() {
//她依旧是一个类(可被实例化)
var app = new DI();
app.register(''woman'', function() {
this.cry = function() {
return ''crying'';
};
});
app.register(''duck'', {
fly: function() {
return ''flying'';
}
});
var msg1,
msg2;
//run方法接受一个函数,该函数仅在参数列表里以变量名表达了依赖关系
app.run(function(woman, duck) {
msg1 = woman.cry();
msg2 = duck.fly();
});
assert.equal(msg1, ''crying'', ''failed injecting woman'');
assert.equal(msg2, ''flying'', ''failed injecting duck'');
});
不用怀疑,npm test
之后,一定又错了!
回顾我们上一份实现,似乎并没有考虑run
方法接受的“是一个函数,而且这个函数没有$inject
属性”这种情况,理论上,再补一个路径选择就好了:
DI.prototype.run = function(arr) {
var cb,
argsName,
args,
lastIndex = arr.length - 1;
//传入数组时,一切照旧
if (Array.isArray(arr)) {
cb = arr[lastIndex];
argsName = arr.slice(0, lastIndex);
} else if (arr.$inject) {//传入函数,并附有$inject属性时,$inject就是argsName
cb = arr;
argsName = arr.$inject;
} else {//传入函数,又没有$inject属性时,通过toString反射函数体,利用正则表达式截取依赖列表
cb = arr;
argsName = arr
.toString()
.match(/\(\s*([a-zA-Z,\s]*)\)/)[1]
.split('','')
.map(name => name.trim());
}
args = argsName.map(name => this.store[name]);
cb.apply(null, args);
};
世界终于清净下来了!!!
总结
本章内容就是利用BDD的开发模式,帮我们一步步抽丝剥茧,在需求不断变化的情况下有条不紊的通过重构代码来实现新的内容,而又不破坏之前已实现的逻辑。
可以这么说,有了测试用例,理清BDD思路,妈妈再也不用担心你的代码重构了!
14.AutoMapper 之依赖注入(Dependency Injection)
https://www.jianshu.com/p/f66447282780
依赖注入(Dependency Injection)
AutoMapper
支持使用静态服务定位构建自定义值解析器和自定义类型转换器的功能:
Mapper.Initialize(cfg =>
{
cfg.ConstructServicesUsing(ObjectFactory.GetInstance);
cfg.CreateMap<Source, Destination>();
});
或者在基于实例的容器(包括子/嵌套容器)中使用动态服务定位:
var mapper = new Mapper(Mapper.Configuration, childContainer.GetInstance);
var dest = mapper.Map<Source, Destination>(new Source { Value = 15 });
陷阱
使用IQueryable.ProjectTo
扩展方法与使用依赖注入是互斥的。请使用IEnumerable.Select(_mapper.Map<DestinationType>).ToList()
来代替IQueryable.ProjectTo
。
ASP.NET Core
有一个NuGet包与这里描述的默认注入机制搭配使用。
Ninject
对于那些使用Ninject
的人来说,这有一个AutoMapper
使用的Ninject
模块的例子
public class AutoMapperModule : NinjectModule
{
public override void Load() { Bind<IValueResolver<SourceEntity, DestModel, bool>>().To<MyResolver>(); var mapperConfiguration = CreateConfiguration(); Bind<MapperConfiguration>().ToConstant(mapperConfiguration).InSingletonScope(); // This teaches Ninject how to create automapper instances say if for instance // MyResolver has a constructor with a parameter that needs to be injected Bind<IMapper>().ToMethod(ctx => new Mapper(mapperConfiguration, type => ctx.Kernel.Get(type))); } private MapperConfiguration CreateConfiguration() { var config = new MapperConfiguration(cfg => { // Add all profiles in current assembly cfg.AddProfiles(GetType().Assembly); }); return config; } }
简单注入器实现
工作流程如下:
- 通过
MyRegistrar.Register
注册你的类型 MapperProvider
允许你直接将IMapper
的实例注入到其他类中SomeProfile
使用PropertyThatDependsOnIocValueResolver
解析值PropertyThatDependsOnIocValueResolver
将IService
注入其中
ValueResolver
能访问IService
,是因为我们通过MapperConfigurationExpression.ConstructServicesUsing
注册容器。
public class MyRegistrar
{
public void Register(Container container) { // Injectable service container.RegisterSingleton<IService, SomeService>(); // Automapper container.RegisterSingleton(() => GetMapper(container)); } private AutoMapper.IMapper GetMapper(Container container) { var mp = container.GetInstance<MapperProvider>(); return mp.GetMapper(); } } public class MapperProvider { private readonly Container _container; public MapperProvider(Container container) { _container = container; } public IMapper GetMapper() { var mce = new MapperConfigurationExpression(); mce.ConstructServicesUsing(_container.GetInstance); var profiles = typeof(SomeProfile).Assembly.GetTypes() .Where(t => typeof(Profile).IsAssignableFrom(t)) .ToList(); mce.AddProfiles(profiles); var mc = new MapperConfiguration(mce); mc.AssertConfigurationIsValid(); IMapper m = new Mapper(mc, t => _container.GetInstance(t)); return m; } } public class SomeProfile : Profile { public SomeProfile() { var map = CreateMap<MySourceType, MyDestinationType>(); map.ForMember(d => d.PropertyThatDependsOnIoc, opt => opt.ResolveUsing<PropertyThatDependsOnIocValueResolver>()); } } public class PropertyThatDependsOnIocValueResolver : IValueResolver<MySourceType, object, int> { private readonly IService _service; public PropertyThatDependsOnIocValueResolver(IService service) { _service = service; } int IValueResolver<MySourceType, object, int>.Resolve(MySourceType source, object destination, int destMember, ResolutionContext context) { return _service.MyMethod(source); } }
码字不易,动动手指给个赞吧
ABAP模拟Java Spring依赖注入(Dependency injection)的一个尝试
Recently I will deliver a session regarding dependency inversion principle to my team.
As Java Spring is already widely used in all other Java development teams in my site, some ABAPers are not well aware of its idea and implementation under the hood. In order for ABAPers to easily understand the mechanism of Java Spring dependency inversion, I wrote a prototype in ABAP after going through related Java source code of Spring.
Before I start, I perform the search in SCN. There are already several excellent blogs written regarding dependency injection in ABAP:
(1) ABAP Dependency Injection – An implementation approach
(2) Shoot Me Up ABAP
(3) Dependency Injection for ABAP
(4) MockA in github
Thanks a lot for efforts spent by authors of them!
Compared with those blogs, the advantage of my prototype is: it follows exactly the design of Java Spring, it is not needed for users to do any other manual dependency registration except a single annotation @Inject. So it is useful for ABAPers to understand Spring dependency internal implementation.
Let me begin with a simple example. In real world I have a switch. By pressing it, the lamp connected by that switch is turned on. With switch pressed for second time, the lamp is turned off. That’s all.
Implementation without using Dependency injection
I have an interface ZIF_SWITCHABLE with two simple methods:
And a ZCL_LAMP which simply implements this interface:
CLASS ZCL_LAMP IMPLEMENTATION.
method ZIF_SWITCHABLE~OFF.
WRITE: / ''lamp off''.
endmethod.
method ZIF_SWITCHABLE~ON.
WRITE: / ''lamp on''.
endmethod.
ENDCLASS.
And a switch which internally maintains the current switch status and a reference to ZIF_SWITCHABLE:
The switch has a push method to toggle:
METHOD push.
IF isswitchon = abap_true.
mo_switchable->off( ).
isswitchon = abap_false.
ELSE.
mo_switchable->on( ).
isswitchon = abap_true.
ENDIF.
ENDMETHOD.
And a setter method is needed to inject the switchable instance:
method SET_SWITCHABLE.
mo_switchable = io_switchable.
endmethod.
These two classes and one interface are put to the following package:
Consumer code – version one without using dependency injection
Here the ZCL_SWITCH has tight dependency on ZCL_LAMP: it has to manually inject this dependency via setter method in line 11.
Let’s summarize how many manual / unnecessary operations are done by consumer:
(1) line 8: create lamp instance
(2) line 9: create switch instance
(3) line 11: connect switch with lamp
Implementation using ABAP Summer
I call my prototype as ABAP Summer just to show my admire on Java Spring
When the same requirement is implemented in Java Spring, the logic in line 11 could completely be avoided, with help of various powerful annotation like @Autowired, @Named, @Inject etc. Thanks to Java Spring container, lots of labor work has been done by it under the hood, saving lots of routine effort from application developers so that they can only concentrate on the core business logic. For example, in Java using Spring, all developers need to do is to add annotation @Inject on top of attribute switchable – in the runtime Spring will guarantee that the annotated implementation for this interface is instantiated automatically.
How can we simulate the similar logic of Spring now in ABAP Summer?
(1) Add the annotation @Inject to attribute mo_switchable, which tells ABAP summer “hey, I would like this attribute to be automatically injected with proper implementation in the runtime”.
Since I have no other way to add metadata in class attribute in ABAP – there is no first class annotation supported in ABAP – I have to use description field for simulation.
(2) And below is my consumer code, no more manual instance initialization and manual setter call. Very clean, isn’t it?
data(summer) = zcl_summer=>get_instance( ).
data(lo_switch) = cast zcl_switch( summer->get_bean( EXPORTING iv_bean_name = ''ZCL_SWITCH'' ) ).
lo_switch->push( ).
lo_switch->push( ).
Let’s make comparison. By using ABAP summer, consumer can simply get switch instance from container by passing bean technical name ( here again, I use Spring terminology “bean” to name those ABAP classes which owns an injected member attribute with @Inject ). This is exactly the way a Java developer doing daily work using Java Spring:
How does ABAP summer work?
There are lots of books written to illustrate the design of Java Spring, I read one of them listed below, and wrote this ABAP summer based on my understanding.
I draw a diagram below to explain the core method init of ABAP summer:
Last by not least, when you try this demo, after you copy the source code of ZCL_SWITCH to your system and activate it, NEVER forget to add this annotation in description field manually, as in ABAP, the attribute description is not stored in source code but in DB table.
More thought on ABAP annotation
The annotation in Java is a form of metadata which could be defined by application developer and are available in the runtime by Java reflection. It is a built-in language feature supported by JVM.
In ABAP there is no such first-class annotation supported. In CDS view, there are some grammar which have annotation-like style. You can append lots of annotation defined in SAP help to a CDS view.
However those annotation are not first-class annotation supported by ABAP language itself as well. It is just CDS view framework which parses the source code of these annotation and react accordingly. For details please see these two blogs of mine:
(1) My CDS view self study tutorial – Part 3 how is view source in Eclipse converted to ABAP view in the backend
(2) My CDS view self study tutorial – Part 4 how does annotation @OData.publish work
要获取更多Jerry的原创文章,请关注公众号"汪子熙":
本文分享 CSDN - 汪子熙。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
Angular2 Dependency Injection
前言
依赖注入是Angular的核心概念之一。通过依赖注入,我们可以将复杂、繁琐的对象管理工作交给Angular,将我们的工作重心更好的放在业务上。
依赖注入本身是后端编码的概念,熟悉Spring框架的对其应该不陌生,Angular1首次将依赖注入引入前端开发,Angular2继续将其发扬光大,同时又很好的解决了Angular1中依赖注入所遗留的问题和瓶颈。
那么什么是依赖注入呢?我觉得可以分为两个方面去解读
依赖注入是一种设计模式
面向对象编程,我们以类为单位组织我们的代码。举个简单的例子,例如某一款汽车,有引擎、轮胎、车门等配置,抽象成代码就是这样的
class Car {
constructor() {
this.engine = new Engine();
this.tires = new Tires();
this.doors = new Doors();
}
}
在构造汽车的过程中,我们安装引擎、轮胎和车门等配置,这样就可以造出一辆汽车了。但是现在我们还想造同一款车,但是想换一种引擎,怎么办?很明显,上面的Car是整个封闭的,如果想换一个引擎,我们就得重新造一款车
class OtherCar {
constructor() {
this.engine = new OtherEngine();
this.tires = new Tires();
this.doors = new Doors();
}
}
相信大家已经发现上面代码的问题了,耦合性太强,无法定制我们的引擎、轮胎和车门,要想定制,就得从头来过。如果我们的所有的引擎都符合某一个标准尺寸,然后在车里预留出这个空间,那么我们不就可以随意更换引擎了么?同理轮胎和车门,抽象成代码就是这样的
class Car {
constructor(engine, tires, doors) {
this.engine = engine;
this.tires = tires;
this.doors = doors;
}
}
通过组装的方式造车,预留配置的标准空间,同一款车我们可以随意使用各种配置
var car = new Car(
new Engine(),
new Tires(),
new Doors()
);
var car = new Car(
new MockEngine(),
new MockTires(),
new MockDoors()
);
从测试的角度来说,这样的代码也是方便测试的。上面的注入方式就是构造器注入
,通过这样的一种模式可以使我们的代码更加健壮同时也是易于测试的。
但是上面注入的实例都是我们手动去new的,当应用越来越大的时候,我们的依赖会更复杂,试着想一下某个类依赖于十几个类,而这些类之间又相互依赖,管理这些依赖关系就是件让人头疼的事情了。
Angular会帮我们管理并且维护这些依赖关系。
依赖注入是一种框架
Angular1中我们可以使用service注入服务,就像这样
angular.module(''app'', [])
.controller(''MyCtrl'', function ($scope, comService) {
comService.handle();
})
.service(''comService'', function () {
this.handle = function () {
//todo
}
});
但是Angular1的依赖注入有几个问题
所有的服务全部都是单例的
var id = 1;
angular.module(''app'', [])
.service(''comService'', function () {
this._id = id++;
this.getId = function () {
return this._id;
}
})
.controller(''ACtrl'', function ($scope, comService) {
console.log(comService.getId()); // 1
})
.controller(''BCtrl'', function ($scope, comService) {
console.log(comService.getId()); // 1
});
服务是通过名称来区分的,很容易造成冲突,后者会直接覆盖前者
angular.module(''app'', [])
.service(''comService'', function () {
this.name = ''company service 1'';
})
.service(''comService'', function () {
this.name = ''company service 2'';
})
.controller(''ACtrl'', function ($scope, comService) {
console.log(comService.name); // company service 2
});
依赖注入功能内嵌在Angular1中,无法剥离出来单独使用
Angular2中的依赖注入
组件注入服务
例如有一个日志服务logger.service.ts
export default class LoggerService {
log(str) {
console.log(`Log: ${str}`);
}
}
然后入口组件app.ts当中使用这个服务
import {Component} from ''angular2/core'';
import LoggerService from ''./logger.service'';
@Component({
selector: ''my-app'',
template: ''<h1>App Component</h1>'',
providers:[LoggerService]
})
export class AppComponent {
loggerService:LoggerService;
constructor(loggerService:LoggerService) {
this.loggerService = loggerService;
}
ngOnInit(){
this.loggerService.log(''component init'');
}
}
首先我们需要在组件的providers
配置中引入这个服务,这点很重要,在Angular2的任何组件(指令等等)当中想要使用我们自定义的服务或者其它功能必须先作出声明。
在App组件当中我们没有看到任何new
操作符,但是程序启动后我们可以看到控制台打印了Log: component init
。Angular2帮我们实例化了LoggerService并注入到了loggerService属性当中。
上面的代码还可以简写成这样
@Component({
selector: ''my-app'',
template: ''<h1>App Component</h1>'',
providers:[LoggerService]
})
export class AppComponent {
constructor(private loggerService:LoggerService) {}
ngOnInit(){
this.loggerService.log(''component init'');
}
}
loggerService:LoggerService
,后面指定的类型必不可少,这是注入的关键
在Angular2组件当中使用依赖注入可以简单的分为两步
组件当中作出声明
组件构造函数当中注入
子组件注入服务
新建一个uuid.ts的服务,可以生成一个唯一的ID
var id = 1;
export default class UuidService {
id:number;
constructor() {
this.id = id++;
}
getId() {
return this.id;
}
}
入口组件app.ts
import {Component} from ''angular2/core'';
import UuidService from ''./uuid.service'';
import ChildComponent from ''./child'';
@Component({
selector: ''my-app'',
template: ''<h1>App Component</h1><my-child></my-child>'',
providers:[UuidService],
directives:[ChildComponent]
})
export class AppComponent {
constructor(private uuidService:UuidService) {}
ngOnInit(){
console.log(this.uuidService.getId());
}
}
新建一个子组件child.ts
import {Component} from ''angular2/core'';
import UuidService from ''./uuid.service'';
@Component({
selector: ''my-child'',
template: ''<p>Child Component</p>''
})
export default class ChildComponent {
constructor(private uuidService:UuidService) {}
ngOnInit(){
console.log(this.uuidService.getId())
}
}
在子组件当中我们并没有配置providers
,为啥程序依然正常执行呢?因为子组件可以注入父组件声明的服务。打开控制台看到输出了两个1
,说明父子组件注入的是同一个实例,这并不符合uuid的功能,怎么办?
我们把子组件当中的providers
声明加上
import {Component} from ''angular2/core'';
import UuidService from ''./uuid.service'';
@Component({
selector: ''my-child'',
template: ''<p>Child Component</p>'',
providers:[UuidService]
})
export default class ChildComponent {
constructor(private uuidService:UuidService) {}
ngOnInit(){
console.log(this.uuidService.getId())
}
}
打开控制台,发现打印了1 2
,这是为什么呢?Angular2当中每个组件都有自己的依赖注入管理,依赖注入的时候会先在当前组件上寻找服务实例,如果找不到就会使用父组件上依赖注入的实例,如果还找不到,就会抛出异常。
组件是一个树状结构,我们也可以把依赖注入看成和组件平行的树状结构,每个组件都有自己的依赖管理,这样就解决了Angular1当中服务单例的的问题。
服务注入服务
有时候服务之间也会相互依赖,例如上面的例子当中LoggerService依赖另一个FormatService
format.service.ts
export default class FormatService {
format() {
return ''Log: '';
}
}
logger.service.ts
import FormatService from ''./format.service'';
export default class LoggerService {
constructor(private formatService:FormatService) {
}
log(str) {
console.log(`${this.formatService.format()}${str}`);
}
}
app.ts
import {Component} from ''angular2/core'';
import LoggerService from ''./logger.service'';
import FormatService from ''./format.service'';
@Component({
selector: ''my-app'',
template: ''<h1>App Component</h1>'',
providers: [LoggerService, FormatService]
})
export class AppComponent {
constructor(private loggerService:LoggerService) {
}
ngOnInit() {
this.loggerService.log(''component init'');
}
}
服务依赖的服务也要在
providers
中作出声明
打开控制台,发现抛出了异常,因为我们没有告知Angular2,LoggerService依赖FormatService,所以注入失败了。
通过给LoggerService添加@Injectable()
装饰器,告知Angular2本服务需要注入其它服务
logger.service.ts
import FormatService from ''./format.service'';
import {Injectable} from ''angular2/core'';
@Injectable()
export default class LoggerService {
constructor(private formatService:FormatService) {
}
log(str) {
console.log(`${this.formatService.format()}${str}`);
}
}
这样我们的程序又能正常工作了。细心的同学会发现我们的App组件也需要注入LoggerService服务,为什么不需要添加@Injectable()
装饰器?
因为组件声明已经添加了@Component()
装饰器,所以无需再次添加其它声明了。
建议我们所有的服务都添加上
@Injectable()
循环依赖注入
我们将上面的代码改造成下面这样
format.service.ts
import LoggerService from ''./logger.service'';
import {Injectable} from "angular2/core";
@Injectable()
export default class FormatService {
constructor(private loggerService:LoggerService){}
format() {
return ''Log: '';
}
}
logger.service.ts
import FormatService from ''./format.service'';
import {Injectable} from "angular2/core";
@Injectable()
export default class LoggerService {
constructor(private formatService:FormatService) {
}
log(str) {
console.log(`${this.formatService.format()}${str}`);
}
}
打开控制台会发现抛出了异常,像这种两个服务之间相互注入的情况就会产生循环依赖,我们要尽量避免这种情况的发生,保持每个服务的单一职责功能。
依赖注入核心
Angular2的依赖注入主要由三个部分构成
Injector - 暴露接口创建服务实例
Provider - 包含了当前服务的信息和依赖信息
Dependency - 服务的依赖信息
通过Injector的功能,我们可以脱离Angular2组件来使用依赖注入,例如上面的Car例子,首先引入
import {Injector, Injectable} from ''angular2/core'';
创建我们的Engine等类和Car类
class Engine{}
class Tires{}
class Doors{}
@Injectable()
class Car{
constructor(private engine:Engine, private tires:Tires, private dorrs:Doors){}
}
Car当中需要注入别的类,不要忘了添加
@Injectable()
调用Injector的resolveAndCreate
静态方法创建注入器
var injector = Injector.resolveAndCreate([Engine, Tires, Doors, Car]);
要将所有相关的类添加到参数数组中,如果实例化了参数数组中不存在的类,就会抛出异常
调用get
方法获取Car类的实例
var car = injector.get(Car);
比较下面的例子
injector.get(Tires) === injector.get(Tires); //true
car.engine === injecotr.get(Engine); //true
同一个注入器上获取的实例都是单例的
Token
我们知道Angular1当中注入的识别是通过参数的字符名称,例如
angular.module(''app'', [])
.service(''comService'', function () {
})
.controller(''ACtrl'', function (comService) {
});
controller当中使用的service名称必须和注册处保持一致,否则注入失败。Angular2获取实例则是通过Token
var injector = Injector.resolveAndCreate([Engine]);
这种方式实际上是简写的,Angular2会帮我们封装成下面的形式
var injecotr = Injector.resolveAndCreate([provide(Engine,{useClass:Engine})]);
provide
是Angular2的核心方法之一,返回值是一个Provider实例。第一个参数就是Token,这里我们直接使用了类Engine作为Token,useClass表示通过实例化类的方式注入。
实际上Token可以换成别的类型,例如
var injector = Injector.resolveAndCreate([provide(''engine'', {useClass: Engine})]);
var engine = injector.get(''engine'');
console.log(engine instanceof Engine); //true
当然了使用字符串这种方式容易被覆盖
useClass
实例化类的方式注入,注入器会帮我们new实例,如果传递一个非类,typescript编译都通不过
useValue
直接注入这个值
var injector = Injector.resolveAndCreate([
provide(Engine, {useValue: ''engine''})
]);
console.log(injector.get(Engine) === ''engine''); //true
useFactory
注入工厂方法的返回值
var injector = Injector.resolveAndCreate([provide(Engine, {
useFactory: function () {
return ''engine''
}
})]);
console.log(injector.get(Engine) === ''engine'');
factory方法当中可以依赖别的服务
var injector = Injector.resolveAndCreate([EngineA, EngineB, provide(Engine, {
useFactory: function (engineA, engineB) {
if (true) {
return engineA;
} else {
return engineB;
}
},
deps: [EngineA, EngineB]
})]);
console.log(injector.get(Engine) instanceof EngineA); //true
useExisting
使用已存在的实例注入,这个容易跟useClass弄混,注意下面的输出
var injector = Injector.resolveAndCreate([
EngineA,
provide(EngineB, {useClass: EngineA})
]);
console.log(injector.get(EngineA) === injector.get(EngineB)); //false
var injector = Injector.resolveAndCreate([
EngineA,
provide(EngineB, {useExisting: EngineA})
]);
console.log(injector.get(EngineA) === injector.get(EngineB)); //true
multi
如果我们重复注册同一个Token,后面的会覆盖前面的,例如
var injector = Injector.resolveAndCreate([
provide(''COM_ID'', {useValue: 1}),
provide(''COM_ID'', {useValue: 2})
]);
console.log(injector.get(''COM_ID'')); // 2
使用multi配置可以使相同的Token共存,注入的是一个数组
var injector = Injector.resolveAndCreate([
provide(''COM_ID'', {
useValue: 1,
multi: true
}),
provide(''COM_ID'', {
useValue: 2,
multi: true
})
]);
console.log(injector.get(''COM_ID'')); // [1,2]
相同的Token,不能出现混合的情况,例如下面的写法就会报错
var injector = Injector.resolveAndCreate([
provide(''COM_ID'', {useValue: 1, multi: true}),
provide(''COM_ID'', {useValue: 2})
]);
子注入器
通过resolveAndCreateChild
可以创建子注入器
var injector = Injector.resolveAndCreate([Engine, Tires, Doors, Car]);
var childInjector = injector.resolveAndCreateChild([Engine, Car]);
var grantInjector = childInjector.resolveAndCreateChild([Car]);
grantInjector.get(Car) === childInjector.get(Car); //false
grantInjector.get(Car) === injector.get(Car); //false
grantInjector.get(Engine) === childInjector.get(Engine); //true
childInjector.get(Engine) === injector.get(Engine); //false
grantInjector.get(Tires) === childInjector.get(Tires); //true
childInjector.get(Tires) === injector.get(Tires); //true
每个注入器都会有自己的依赖注入管理,它会先从本身查找服务,如果找不到就会往父级注入器查找
小结
自此Angular2解决了Angular1遗留的问题
我们可以单独使用依赖注入功能
Token防止重名覆盖
树状的注入器各自管理自己的实例
原文
Dependency Injection
IoC 容器和 Dependency Injection 模式
Java 社群近来掀起了一阵轻量级容器的热潮,这些容器能够帮助开发者将来自不同项目的组件组装成为一个内聚的应用程序。
在它们的背后有着同一个模式,这个模式决定了这些容器进行组件装配的方式。
人们用一个大而化之的名字来称呼这个模式:“控制反转”(Inversion of Control,IoC)
在本文中,我将深入探索这个模式的工作原理,给它一个更能描述其特点的名字 —— “依赖注入”(Dependency Injection),并将其与 “服务定位器”(Service Locator )模式作一个比较。不过,这两者之间的差异并不太重要,更重要的是:应该将组件的配置与使用分离开 —— 两个模式的目标都是这个。
在企业级 Java 的世界里存在一个有趣的现象:有很多人投入很多精力来研究主流 J2EE 技术的替代品 —— 自然,这大多发生在 open source 社群。
在很大程度上,这可以看作是开发者对主流 J2EE 技术的笨重和复杂作出的回应,但其中的确有很多极富创意的想法,的确提供了一些可供选择的方案。
J2EE 开发者常遇到的一个问题就是如何组装不同的程序元素:如果 web 控制器体系结构和数据库接口是由不同的团队所开发的,彼此几乎一无所知,你应该如何让它们配合工作?
很多框架尝试过解决这个问题,有几个框架索性朝这个方向发展,提供了更通用的 “组装各层组件” 的方案。这样的框架通常被称为 “轻量级容器”,PicoContainer 和 Spring 都在此列中。
在这些容器背后,一些有趣的设计原则发挥着作用。这些原则已经超越了特定容器的范畴,甚至已经超越了 Java 平台的范畴。
在本文中,我就要初步揭示这些原则。我使用的范例是 Java 代码,但正如我的大多数文章一样,这些原则也同样适用于别的 OO 环境,特别是.NET。
组件和服务
“装配程序元素”,这样的话题立即将我拖进了一个棘手的术语问题:如何区分 “服务”(service)和 “组件”(component )?
你可以毫不费力地找出关于这两个词定义的长篇大论,各种彼此矛盾的定义会让你感受到我所处的窘境。有鉴于此,对于这两个遭到了严重滥用的词汇,我将首先说明它们在本文中的用法。
所谓 “组件” 是指这样一个软件单元:它将被作者无法控制的其他应用程序使用,但后者不能对组件进行修改。也就是说,使用一个组件的应用程序不能修改组件的源代码,但可以通过作者预留的某种途径对其进行扩展,以改变组件的行为。
服务和组件有某种相似之处:
它们都将被外部的应用程序使用。
在我看来,两者之间最大的差异在于:
组件是在本地使用的(例如 JAR 文件、程序集、DLL、或者源码导入);
而服务是要通过 —— 同步或异步的 —— 远程接口来远程使用的(例如 web service、消息系统、RPC,或者 socket )。
在本文中,我将主要使用 “服务” 这个词,但文中的大多数逻辑也同样适用于本地组件。实际上,为了方便地访问远程服务,你往往需要某种本地组件框架。不过,“组件或者服务” 这样一个词组实在太麻烦了,而且 “服务” 这个词当下也很流行,所以本文将用 “服务” 指代这两者。
一个简单的例子
为了更好地说明问题,我要引入一个例子。和我以前用的所有例子一样,这是一个超级简单的例子:它非常小,小得有点不够真实,但足以帮助你看清其中的道理,而不至于陷入真实例子的泥潭中无法自拔。
在这个例子中,我编写了一个组件,用于提供一份电影清单,清单上列出的影片都是由一位特定的导演执导的。实现这个伟大的功能只需要一个方法:
class MovieLister...
public Movie[] moviesDirectedBy(String arg) {
List allMovies = finder.findAll();
for (Iterator it = allMovies.iterator(); it.hasNext();) {
Movie movie = (Movie) it.next();
if (!movie.getDirector().equals(arg)) it.remove();
}
return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
}
你可以看到,这个功能的实现极其简单:moviesDirectedBy 方法首先请求 finder (影片搜寻者)对象(我们稍后会谈到这个对象)返回后者所知道的所有影片,然后遍历 finder 对象返回的清单,并返回其中由特定的某个导演执导的影片。非常简单,不过不必担心,这只是整个例子的脚手架罢了。
我们真正想要考察的是 finder 对象,或者说,如何将 MovieLister 对象与特定的 finder 对象连接起来。
为什么我们对这个问题特别感兴趣?
因为我希望上面这个漂亮的 moviesDirectedBy 方法完全不依赖于影片的实际存储方式。
所以,这个方法只能引用一个 finder 对象,而 finder 对象则必须知道如何对 findAll 方法作出回应。
为了帮助读者更清楚地理解,我给 finder 定义了一个接口:
public interface MovieFinder {
List findAll();
}
现在,两个对象之间没有什么耦合关系。
但是,当我要实际寻找影片时,就必须涉及到 MovieFinder 的某个具体子类。
在这里,我把 “涉及具体子类” 的代码放在 MovieLister 类的构造子中。
class MovieLister...
private MovieFinder finder;
public MovieLister() {
finder = new ColonDelimitedMovieFinder("movies1.txt");
}
这个实现类的名字就说明:我将要从一个逗号分隔的文本文件中获得影片列表。
你不必操心具体的实现细节,只要设想这样一个实现类就可以了。
如果这个类只由我自己使用,一切都没问题。
但是,如果我的朋友叹服于这个精彩的功能,也想使用我的程序,那又会怎么样呢?如果他们也把影片清单保存在一个逗号分隔的文本文件中,并且也把这个文件命名为 “movie1.txt”,那么一切还是没问题。如果他们只是给这个文件改改名,我也可以从一个配置文件获得文件名,这也很容易。
但是,如果他们用完全不同的方式 —— 例如 SQL 数据库、XML 文件、web service,或者另一种格式的文本文件 —— 来存储影片清单呢?
在这种情况下,我们需要用另一个类来获取数据。由于已经定义了 MovieFinder 接口,我可以不用修改 moviesDirectedBy 方法。但是,我仍然需要通过某种途径获得合适的 MovieFinder 实现类的实例。
“在 MovieLister 类中直接创建 MovieFinder 实例” 时的依赖关系
图 1 展现了这种情况下的依赖关系:MovieLister 类既依赖于 MovieFinder 接口,也依赖于具体的实现类。我们当然希望 MovieLister 类只依赖于接口,但我们要如何获得一个 MovieFinder 子类的实例呢?
在 Patterns of Enterprise Application Architecture 一书中,我们把这种情况称为 “插件” (plugin):
MovieFinder 的实现类不是在编译期连入程序之中的,因为我并不知道我的朋友会使用哪个实现类。
我们希望 MovieLister 类能够与 MovieFinder 的任何实现类配合工作,并且允许在运行期插入具体的实现类,插入动作完全脱离我(原作者)的控制。
这里的问题就是:如何设计这个连接过程,使 MovieLister 类在不知道实现类细节的前提下与其实例协同工作。
将这个例子推而广之,在一个真实的系统中,我们可能有数十个服务和组件。在任何时候,我们总可以对使用组件的情形加以抽象,通过接口与具体的组件交流(如果组件并没有设计一个接口,也可以通过适配器与之交流)。
但是,如果我们希望以不同的方式部署这个系统,就需要用插件机制来处理服务之间的交互过程,这样我们才可能在不同的部署方案中使用不同的实现。
所以,现在的核心问题就是:如何将这些插件组合成一个应用程序?这正是新生的轻量级容器所面临的主要问题,而它们解决这个问题的手段无一例外地是控制反转(Inversion of Control)模式。
控制反转
几位轻量级容器的作者曾骄傲地对我说:这些容器非常有用,因为它们实现了 “控制反转”。
这样的说辞让我深感迷惑:控制反转是框架所共有的特征,如果仅仅因为使用了控制反转就认为这些轻量级容器与众不同,就好象在说 “我的轿车是与众不同的,因为它有四个轮子”。
问题的关键在于:它们反转了哪方面的控制?
我第一次接触到的控制反转针对的是用户界面的主控权。早期的用户界面是完全由应用程序来控制的,你预先设计一系列命令,例如 “输入姓名”、“输入地址” 等,应用程序逐条输出提示信息,并取回用户的响应。而在图形用户界面环境下, UI 框架将负责执行一个主循环,你的应用程序只需为屏幕的各个区域提供事件处理函数即可。
在这里,程序的主控权发生了反转:从应用程序移到了框架。
对于这些新生的容器,它们反转的是 “如何定位插件的具体实现”。
在前面那个简单的例子中, MovieLister 类负责定位 MovieFinder 的具体实现 —— 它直接实例化后者的一个子类。这样一来,MovieFinder 也就不成其为一个插件了,因为它并不是在运行期插入应用程序中的。
而这些轻量级容器则使用了更为灵活的办法,只要插件遵循一定的规则,一个独立的组装模块就能够将插件的具体实现 “注射” 到应用程序中。
因此,我想我们需要给这个模式起一个更能说明其特点的名字 ——“控制反转” 这个名字太泛了,常常让人有些迷惑。与多位 IoC 爱好者讨论之后,我们决定将这个模式叫做 “依赖注入” (Dependency Injection)。
下面,我将开始介绍 Dependency Injection 模式的几种不同形式。
不过,在此之前,我要首先指出:要消除应用程序对插件实现的依赖,依赖注入并不是唯一的选择,你也可以用 Service Locator 模式获得同样的效果。介绍完 Dependency Injection 模式之后,我也会谈到 Service Locator 模式。
依赖注入的几种形式
Dependency Injection 模式的基本思想是:用一个单独的对象(装配器)来获得 MovieFinder 的一个合适的实现,并将其实例赋给 MovieLister 类的一个字段。
这样一来,我们就得到了图 2 所示的依赖图:
依赖注入的形式主要有三种,我分别将它们叫做构造子注入(Constructor Injection)、设值方法注入(Setter Injection)和接口注入(Interface Injection)。
如果读过最近关于 IoC 的一些讨论材料,你不难看出:这三种注入形式分别就是 type 1 IoC (接口注入)、type 2 IoC (设值方法注入)和 type 3 IoC (构造子注入)。
我发现数字编号往往比较难记,所以我使用了这里的命名方式。
使用 PicoContainer 进行构造子注入
首先,我要向读者展示如何用一个名为 PicoContainer 的轻量级容器完成依赖注入。
PicoContainer 通过构造子来判断 “如何将 MovieFinder 实例注入 MovieLister 类”。
因此, MovieLister 类必须声明一个构造子,并在其中包含所有需要注入的元素:
class MovieLister...
public MovieLister(MovieFinder finder) {
this.finder = finder;
}
MovieFinder 实例本身也将由 PicoContainer 来管理,因此文本文件的名字也可以由容器注入:
class ColonMovieFinder...
public ColonMovieFinder(String filename) {
this.filename = filename;
}
随后,需要告诉 PicoContainer:各个接口分别与哪个实现类关联、将哪个字符串注入 MovieFinder 组件。
private MutablePicoContainer configureContainer() {
MutablePicoContainer pico = new DefaultPicoContainer();
Parameter[] finderParams = {new ConstantParameter("movies1.txt")};
pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams);
pico.registerComponentImplementation(MovieLister.class);
return pico;
}
这段配置代码通常位于另一个类。
对于我们这个例子,使用我的 MovieLister 类的朋友需要在自己的设置类中编写合适的配置代码。当然,还可以将这些配置信息放在一个单独的配置文件中,这也是一种常见的做法。你可以编写一个类来读取配置文件,然后对容器进行合适的设置。
尽管 PicoContainer 本身并不包含这项功能,但另一个与它关系紧密的项目 NanoContainer 提供了一些包装,允许开发者使用 XML 配置文件保存配置信息。NanoContainer 能够解析 XML 文件,并对底下的 PicoContainer 进行配置。这个项目的哲学观念就是:将配置文件的格式与底下的配置机制分离开。
使用这个容器,你写出的代码大概会是这样:
public void testWithPico() {
MutablePicoContainer pico = configureContainer();
MovieLister lister = (MovieLister)
pico.getComponentInstance(MovieLister.class);
Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
assertEquals("Once Upon a Time in the West", movies[0].getTitle());
}
尽管在这里我使用了构造子注入,实际上 PicoContainer 也支持设值方法注入,不过该项目的开发者更推荐使用构造子注入。
使用 Spring 进行设值方法注入
Spring 框架是一个用途广泛的企业级 Java 开发框架,其中包括了针对事务、持久化框架、web 应用开发和 JDBC 等常用功能的抽象。
和 PicoContainer 一样,它也同时支持构造子注入和设值方法注入,但该项目的开发者更推荐使用设值方法注入 —— 恰好适合这个例子。
为了让 MovieLister 类接受注入,我需要为它定义一个设值方法,该方法接受类型为 MovieFinder 的参数:
class MovieLister...
private MovieFinder finder;
public void setFinder(MovieFinder finder) {
this.finder = finder;
}
类似地,在 MovieFinder 的实现类中,我也定义了一个设值方法,接受类型为 String 的参数:
class ColonMovieFinder...
public void setFilename(String filename) {
this.filename = filename;
}
第三步是设定配置文件。Spring 支持多种配置方式,你可以通过 XML 文件进行配置,也可以直接在代码中配置。不过,XML 文件是比较理想的配置方式。
<beans>
<bean id="MovieLister" class="spring.MovieLister">
<property name="finder">
<ref local="MovieFinder"/>
</property>
</bean>
<bean id="MovieFinder" class="spring.ColonMovieFinder">
<property name="filename">
<value>movies1.txt</value>
</property>
</bean>
</beans>
于是,测试代码大概就像下面这样:
public void testWithSpring() throws Exception {
ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml");
MovieLister lister = (MovieLister) ctx.getBean("MovieLister");
Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
assertEquals("Once Upon a Time in the West", movies[0].getTitle());
}
接口注入
除了前面两种注入技术,还可以在接口中定义需要注入的信息,并通过接口完成注入。
Avalon 框架就使用了类似的技术。
在这里,我首先用简单的范例代码说明它的用法,后面还会有更深入的讨论。
首先,我需要定义一个接口,组件的注入将通过这个接口进行。
在本例中,这个接口的用途是将 一个 MovieFinder 实例注入继承了该接口的对象。
public interface InjectFinder {
void injectFinder(MovieFinder finder);
}
这个接口应该由提供 MovieFinder 接口的人一并提供。任何想要使用 MovieFinder 实例的类 (例如 MovieLister 类)都必须实现这个接口。
public interface InjectFilename {
void injectFilename (String filename);
}
class ColonMovieFinder implements MovieFinder, InjectFilename......
public void injectFilename(String filename) {
this.filename = filename;
}
现在,还需要用一些配置代码将所有的组件实现装配起来。简单起见,我直接在代码中完成配置, 并将配置好的 MovieLister 对象保存在名为 lister 的字段中:
class IfaceTester...
private MovieLister lister;
private void configureLister() {
ColonMovieFinder finder = new ColonMovieFinder();
finder.injectFilename("movies1.txt");
lister = new MovieLister();
lister.injectFinder(finder);
}
测试代码则可以直接使用这个字段:
class IfaceTester...
public void testIface() {
configureLister();
Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
assertEquals("Once Upon a Time in the West", movies[0].getTitle());
}
关于[JavaScript]以BDD手写依赖注入(dependency injection)的介绍现已完结,谢谢您的耐心阅读,如果想了解更多关于14.AutoMapper 之依赖注入(Dependency Injection)、ABAP模拟Java Spring依赖注入(Dependency injection)的一个尝试、Angular2 Dependency Injection、Dependency Injection的相关知识,请在本站寻找。
本文标签: