Menu

[r2018]: / trunk / Src / USingleton.pas  Maximize  Restore  History

Download this file

289 lines (254 with data), 8.3 kB

  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
{
* USingleton.pas
*
* Provides a base class for singleton objects along with a manager object that
* records instances of each type of singleton.
*
* Based on by code by Yoav Abrahami see <URL:http://bit.ly/cAH0HO>, updated to
* take advantage of modern Delphi features: generics, class vars, class
* constructor and destructor etc. Further updated to use class types instead of
* class names as dictionary keys following suggestions made in comments on my
* blog post at <URL:http://bit.ly/d8n9Hq>.
*
* $Rev$
* $Date$
*
* ***** BEGIN LICENSE BLOCK *****
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
* the specific language governing rights and limitations under the License.
*
* The Original Code is USingleton.pas
*
* The Initial Developer of the Original Code is Peter Johnson
* (http://www.delphidabbler.com/).
*
* Portions created by the Initial Developer are Copyright (C) 2010-2012 Peter
* Johnson. All Rights Reserved.
*
* Contributor(s)
* NONE
*
* ***** END LICENSE BLOCK *****
}
unit USingleton;
interface
uses
// Project
UBaseObjects;
type
{
TSingleton:
Base class for singleton objects. Only one singleton object of each
descendant class can be created. Once a subclassed TSingleton object is
created further attempts to create an instance of that type return the
already created object. All attempts to destroy singletons will fail,
although instances are freed correctly when the program closes.
*** NOTE: TSingleton instances should not be created directly. The class
should be subclassed.
}
TSingleton = class(TConditionalFreeObject)
strict private
procedure Dispose;
{Frees the singleton instance.
}
strict protected
function CanDestroy: Boolean; override;
{Determines if the object can be destroyed. This is the case only when the
singleton manager is destroying.
@return True if object can be destroyed.
}
procedure Initialize; virtual;
{Initialises object. Descendants should override this method instead of
constructor. Does nothing in this base class.
}
public
class function NewInstance: TObject; override;
{Creates a new instance of singleton if it doesn't exist. If singleton
already exists returns existing instance.
@return Singleton instance.
}
end;
{$IFDEF TESTING}
procedure FreeAllSingletons;
{Frees all singleton instances. Used when testing only.
}
{$ENDIF}
implementation
uses
// Delphi
SysUtils, Generics.Collections;
type
{
TSingletonManager:
Class that records instantiated TSingleton descendant objects. Maintains a
map of class names to instances that TSingleton uses to decide whether to
create a new singleton instance. Ensures all singletons are freed when the
program closes.
}
TSingletonManager = class(TNoConstructObject)
strict private
class var fDestroying: Boolean;
{Flag that indicates if manager is destroying singletons}
class var fMap: TDictionary<TClass,TSingleton>;
{Map of class names to singleton instances}
{$IFNDEF TESTING}strict{$ENDIF}
protected
class procedure FreeAll;
{Frees all registered singletons.
}
class procedure CreateMap;
{Create Map object if doesn't exist.
}
public
class constructor Create;
{Class constructor. Sets up required class vars.
}
class destructor Destroy;
{Class destructor. Frees all singletons.
}
class procedure RegisterSingleton(const S: TSingleton);
{Registers a new singleton object providing it is not already registered.
@param S [in] Singleton to register.
}
class function SingletonExists(const Cls: TClass): Boolean;
{Checks if a singleton of a certain class already exists.
@param Name of singleton class.
@return True if an instance of this class already exists, False if not.
}
class function Lookup(const Cls: TClass): TSingleton;
{Looks up a singleton class name in the map.
@param ClsName [in] Name of requested singleton class.
@return Required singleton instance.
@except EListError raised if there is no singleton instance with the
requested class name.
}
class property Destroying: Boolean read fDestroying write fDestroying;
{Indicates if the this class is destroying singletons. Singleton instances
use this property to allow themselves to be destroyed}
end;
{$IFDEF TESTING}
procedure FreeAllSingletons;
{Frees all singleton instances. Used when testing only.
}
begin
// Can't call class constructor directly so we use following methods.
// These methods are normally *strict* protected, but relaxed for testing.
TSingletonManager.FreeAll;
TSingletonManager.CreateMap;
end;
{$ENDIF}
{ TSingleton }
function TSingleton.CanDestroy: Boolean;
{Determines if the object can be destroyed. This is the case only when the
singleton manager is destroying.
@return True if object can be destroyed.
}
begin
Result := TSingletonManager.Destroying;
end;
procedure TSingleton.Dispose;
{Frees the singleton instance.
}
begin
inherited FreeInstance;
end;
procedure TSingleton.Initialize;
{Initialises object. Descendants should override this method instead of
constructor. Does nothing in this base class.
}
begin
// Override to initialise code that would normally be placed in constructor
end;
class function TSingleton.NewInstance: TObject;
{Creates a new instance of singleton if it doesn't exist. If singleton already
exists returns existing instance.
@return Singleton instance.
}
var
S: TSingleton; // reference to a new singleton
begin
if not TSingletonManager.SingletonExists(Self) then
begin
S := TSingleton(inherited NewInstance);
try
S.Initialize;
TSingletonManager.RegisterSingleton(S);
except
S.Dispose;
raise;
end;
end;
Result := TSingletonManager.Lookup(Self);
end;
{ TSingletonManager }
class constructor TSingletonManager.Create;
{Class constructor. Sets up required class vars.
}
begin
CreateMap;
end;
class procedure TSingletonManager.CreateMap;
{Create Map object if doesn't exist.
}
begin
if not Assigned(fMap) then
fMap := TDictionary<TClass,TSingleton>.Create;
end;
class destructor TSingletonManager.Destroy;
{Class destructor. Frees all singletons.
}
begin
FreeAll;
end;
class procedure TSingletonManager.FreeAll;
{Frees all registered singletons.
}
var
Singleton: TSingleton; // each singleton in map
begin
// indicate to singletons they can destroy
Destroying := True;
// free the singletons in the map, then the map itself
for Singleton in fMap.Values do
Singleton.Free;
FreeAndNil(fMap);
Destroying := False;
// setting fMap nil and Destroying False make it safe to re-create map when
// testing
end;
class function TSingletonManager.Lookup(const Cls: TClass): TSingleton;
{Looks up a singleton class name in the map.
@param ClsName [in] Name of requested singleton class.
@return Required singleton instance.
@except EListError raised if there is no singleton instance with the
requested class name.
}
begin
Result := fMap[Cls];
end;
class procedure TSingletonManager.RegisterSingleton(const S: TSingleton);
{Registers a new singleton object providing it is not already registered.
@param S [in] Singleton to register.
}
begin
if not SingletonExists(S.ClassType) then
fMap.Add(S.ClassType, S);
end;
class function TSingletonManager.SingletonExists(
const Cls: TClass): Boolean;
{Checks if a singleton of a certain class already exists.
@param Name of singleton class.
@return True if an instance of this class already exists, False if not.
}
begin
Result := fMap.ContainsKey(Cls);
end;
end.
Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.