forked from VSoftTechnologies/DUnitX
-
Notifications
You must be signed in to change notification settings - Fork 0
/
DUnitX.IoC.pas
332 lines (280 loc) · 11.6 KB
/
DUnitX.IoC.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
{***************************************************************************}
{ }
{ DUnitX }
{ }
{ Copyright (C) 2015 Vincent Parrett & Contributors }
{ }
{ http://www.finalbuilder.com }
{ }
{ }
{***************************************************************************}
{ }
{ Licensed under the Apache License, Version 2.0 (the "License"); }
{ you may not use this file except in compliance with the License. }
{ You may obtain a copy of the License at }
{ }
{ http://www.apache.org/licenses/LICENSE-2.0 }
{ }
{ Unless required by applicable law or agreed to in writing, software }
{ distributed under the License is distributed on an "AS IS" BASIS, }
{ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. }
{ See the License for the specific language governing permissions and }
{ limitations under the License. }
{ }
{***************************************************************************}
unit DUnitX.IoC;
{$I DUnitX.inc}
/// A Simple IoC container. This is used internally by DUnitX
/// DUnitX used the default container, if you need to use this
/// for your tests, create your own container instance.
/// NOTE: Does not do dependency Injection, if you need that then
/// use the Spring for Delphi Framework
interface
uses
Generics.Collections,
TypInfo,
Rtti,
SysUtils;
type
TActivatorDelegate<TInterface: IInterface> = reference to function: TInterface;
TActivatorDelegate = reference to function: IInterface;
TDUnitXIoC = class
private
type
TIoCRegistration = class
ActivatorDelegate : TActivatorDelegate;
Instance : IInterface;
function CreateSingletonActivator(const delegate: TActivatorDelegate): TActivatorDelegate;
procedure Initialize(const delegate: TActivatorDelegate; singleton: Boolean);
end;
private
FRaiseIfNotFound : boolean;
FContainerInfo : TDictionary<string,TIoCRegistration>;
class var FDefault : TDUnitXIoC;
protected
function GetInterfaceKey(const typeInfo: PTypeInfo; const AName: string = ''): string;
function InternalResolve(const typeInfo: PTypeInfo; const AName: string = ''): IInterface;
procedure InternalRegisterType(const typeInfo: PTypeInfo; const singleton : boolean; const delegate : TActivatorDelegate; const name : string = '');
public
constructor Create;
destructor Destroy;override;
class destructor ClassDestroy;
//Default Container - used internally by DUnitX
class function DefaultContainer : TDUnitXIoC;
procedure RegisterType<TInterface: IInterface; TImplementation: class>(const name : string = '');overload;
procedure RegisterType<TInterface: IInterface; TImplementation: class>(const singleton : boolean;const name : string = '');overload;
procedure RegisterType<TInterface: IInterface>(const delegate : TActivatorDelegate<TInterface>; const name : string = '' );overload;
procedure RegisterType<TInterface: IInterface>(const singleton : boolean;const delegate : TActivatorDelegate<TInterface>; const name : string = '');overload;
//Register an instance as a singleton. If there is more than one instance that implements the interface
//then use the name parameter
procedure RegisterSingleton<TInterface :IInterface>(const instance : TInterface; const name : string = '');
//Resolution
function Resolve<TInterface: IInterface>(const name: string = ''): TInterface;
//Returns true if we have such a service.
function HasService<T: IInterface> : boolean;
//Empty the Container.. usefull for testing only!
procedure Clear;
property RaiseIfNotFound : boolean read FRaiseIfNotFound write FRaiseIfNotFound;
end;
EIoCException = class(Exception);
EIoCRegistrationException = class(EIoCException);
EIoCResolutionException = class(EIoCException);
//Makes sure virtual constructors are called correctly. Just using a class reference will not call the overriden constructor!
//See http://stackoverflow.com/questions/791069/how-can-i-create-an-delphi-object-from-a-class-reference-and-ensure-constructor
TClassActivator = class
private
class var
FRttiCtx : TRttiContext;
class constructor Create;
public
class function CreateInstance(const AClass : TClass) : IInterface;
class function CreateActivatorDelegate(const AClass : TClass; typeInfo : PTypeInfo; raiseOnError : Boolean) : TActivatorDelegate;
end;
implementation
{ TActivator }
class constructor TClassActivator.Create;
begin
TClassActivator.FRttiCtx := TRttiContext.Create;
end;
class function TClassActivator.CreateInstance(const AClass : TClass): IInterface;
var
delegate : TActivatorDelegate;
begin
Result := nil;
delegate := CreateActivatorDelegate(AClass, TypeInfo(IInterface), False);
if Assigned(delegate) then
Result := delegate();
end;
class function TClassActivator.CreateActivatorDelegate(
const AClass : TClass; typeInfo : PTypeInfo; raiseOnError : Boolean): TActivatorDelegate;
var
rType : TRttiType;
method : TRttiMethod;
guid : TGUID;
ctor : function(InstanceOrVMT: Pointer; Alloc: ShortInt = 1): Pointer; // constructor signature
begin
Result := nil;
rType := FRttiCtx.GetType(AClass);
if rType is TRttiInstanceType then
for method in TRttiInstanceType(rType).GetMethods do
if method.IsConstructor and (Length(method.GetParameters) = 0) then
begin
guid := GetTypeData(typeInfo).Guid;
ctor := method.CodeAddress;
Result :=
function : IInterface
var
obj : TObject;
begin
obj := ctor(AClass);
if not Supports(obj, guid, Result) and raiseOnError then
EIoCResolutionException.CreateFmt('The implementation registered (%s) does not implement %s', [AClass.ClassName, typeInfo.Name]);
end;
Exit;
end;
end;
{ TDUnitXIoC }
function TDUnitXIoC.HasService<T>: boolean;
begin
Result := FContainerInfo.ContainsKey(GetInterfaceKey(TypeInfo(T)));
end;
procedure TDUnitXIoC.RegisterType<TInterface, TImplementation>(const name: string);
begin
InternalRegisterType(TypeInfo(TInterface), False,
TClassActivator.CreateActivatorDelegate(TImplementation, TypeInfo(TInterface), FRaiseIfNotFound), name);
end;
procedure TDUnitXIoC.RegisterType<TInterface, TImplementation>(const singleton: boolean; const name: string);
begin
InternalRegisterType(TypeInfo(TInterface), singleton,
TClassActivator.CreateActivatorDelegate(TImplementation, TypeInfo(TInterface), FRaiseIfNotFound), name);
end;
procedure TDUnitXIoC.InternalRegisterType(const typeInfo: PTypeInfo; const singleton : boolean; const delegate : TActivatorDelegate; const name : string = '');
var
key : string;
rego : TIoCRegistration;
begin
key := GetInterfaceKey(typeInfo, name);
if not FContainerInfo.TryGetValue(key,rego) then
begin
rego := TIoCRegistration.Create;
rego.Initialize(delegate, singleton);
FContainerInfo.Add(key, rego);
end
else
begin
//cannot replace a singleton that has already been instanciated (Instance property is only used by singletons)
if rego.Instance <> nil then
raise EIoCRegistrationException.Create(Format('An implementation for type %s with name %s is already registered with IoC',[typeInfo.Name, name]));
rego.Initialize(delegate, singleton);
end;
end;
procedure TDUnitXIoC.RegisterType<TInterface>(const delegate: TActivatorDelegate<TInterface>; const name: string);
var
internalDelegate: TActivatorDelegate;
begin
TActivatorDelegate<TInterface>(internalDelegate) := delegate;
InternalRegisterType(TypeInfo(TInterface), False, internalDelegate, name);
end;
class destructor TDUnitXIoC.ClassDestroy;
begin
FDefault.Free;
end;
procedure TDUnitXIoC.Clear;
begin
FContainerInfo.Clear;
end;
constructor TDUnitXIoC.Create;
begin
FContainerInfo := TObjectDictionary<string,TIoCRegistration>.Create([doOwnsValues]);
FRaiseIfNotFound := False;
end;
class function TDUnitXIoC.DefaultContainer: TDUnitXIoC;
begin
if FDefault = nil then
FDefault := TDUnitXIoC.Create;
Result := FDefault;
end;
destructor TDUnitXIoC.Destroy;
begin
FContainerInfo.Free;
inherited;
end;
function TDUnitXIoC.GetInterfaceKey(const typeInfo: PTypeInfo; const AName: string): string;
begin
//By default the key is the interface name unless otherwise found.
Result := string(typeInfo.Name);
if AName <> '' then
Result := Result + '_' + AName;
//All keys are stored in lower case form.
Result := LowerCase(Result);
end;
function TDUnitXIoC.InternalResolve(const typeInfo: PTypeInfo; const AName: string): IInterface;
var
key : string;
registration : TIoCRegistration;
begin
Result := nil;
key := GetInterfaceKey(typeInfo, AName);
if not FContainerInfo.TryGetValue(key, registration) then
begin
if FRaiseIfNotFound then
raise EIoCResolutionException.CreateFmt('No implementation registered for type %s', [typeInfo.Name])
//If we are not meant to raise exceptions, then handle the registration not being set.
else
Exit;
end;
Result := registration.ActivatorDelegate();
if Result = nil then
if FRaiseIfNotFound then
raise EIoCResolutionException.CreateFmt('The activator delegate failed to return an instance %s', [typeInfo.Name]);
end;
procedure TDUnitXIoC.RegisterSingleton<TInterface>(const instance: TInterface; const name: string);
begin
InternalRegisterType(TypeInfo(TInterface), True,
function: IInterface
begin
Result := instance;
end, name);
end;
procedure TDUnitXIoC.RegisterType<TInterface>(const singleton: boolean; const delegate: TActivatorDelegate<TInterface>; const name: string);
var
internalDelegate: TActivatorDelegate;
begin
TActivatorDelegate<TInterface>(internalDelegate) := delegate;
InternalRegisterType(TypeInfo(TInterface), singleton, internalDelegate, name);
end;
function TDUnitXIoC.Resolve<TInterface>(const name: string = ''): TInterface;
begin
IInterface(Result) := InternalResolve(TypeInfo(TInterface), name);
end;
{ TDUnitXIoC.TIoCRegistration }
procedure TDUnitXIoC.TIoCRegistration.Initialize(
const delegate: TActivatorDelegate; singleton: Boolean);
begin
inherited Create;
if Assigned(delegate) and singleton then
ActivatorDelegate := CreateSingletonActivator(delegate)
else
ActivatorDelegate := delegate;
end;
function TDUnitXIoC.TIoCRegistration.CreateSingletonActivator(
const delegate: TActivatorDelegate): TActivatorDelegate;
begin
Result :=
function: IInterface
begin
if not Assigned(Instance) then
begin
MonitorEnter(Self);
try
if not Assigned(Instance) then
Instance := delegate();
finally
MonitorExit(Self);
end;
end;
Result := Instance;
end;
end;
end.