4

Dependency Injection is certainly one of the most important concepts when trying to write testable code. But while Java and C# have garbage collection, Delphi has not and normally, object disposal is managed using the ownership-principle (the one who creates the object destroys it). This is nicely supported by the try..finally construct

Obj := TObject.Create;
try
  ...
finally
  Obj.Free;
end;

Now what if one uses dependency injection:

constructor TFileLister.Create(FileSystem: TFileSystem);

Who should now be responsible for destroying the FileSystem object? Does the ownership-principle still work here?

I know that interfaces are a solution to this problem (thanks to the fact that they are reference-counted). But what if there are no interfaces (say in some legacy code)? What other approaches or best practices are there to handle memory management when using dependency injection?

3 Answers 3

7

You have to come up with an owner for the FileSystem object. This can be either the entity that creates the TFileLister instances, or you could pass ownership to the file lister, documenting that it will free the file system that was passed to the constructor.

The right approach depends on course on your particular application. For example, if other objects would also use the same file system object, it shouldn't be owned by one of these such as the file lister, but by the object that ties it all together. You could even make the file system object global if it only makes sense to have one of it.

In short, you'll have to do a little more thinking than in Java but that's not necessarily a bad thing.

Sign up to request clarification or add additional context in comments.

2 Comments

good answer, thanks! can you say why this is not necesarily bad? what is the advantage? what do you think about making the factory the owner of the objects it creates?
This means that the factory must get a signal when the object is no longer used, otherwise it will only be deallocated at shutdown. FIguring out when to do that is the same as GC, and you are round again. Without a general purpose GC mechanism, more complex problems can't be solved without hand managing or emulating certain m GC algorithms. Specially if you must do something when the last reference dies.
3

It's almost always preferable to regard the entity that create an object also to be its owner (i.e. responsible for destroying it).

To understand why I say this, consider the alternative. Suppose that object A creates object B. At some point later it passes B to object C which becomes the owner.

In the period between creating B and handing it over to C, A is responsible for destruction in case of exceptions, or perhaps the selection of a branch that bypasses C. On the other hand, once it has handed off B, A must not attempt to destroy C.

All this can be handled with sufficient care. One approach is that taken by the VCL with TComponent.Owner.

However, if you can find a way to stick to the two standard patterns of ownership then do so.

What are the two standard patterns?

  1. Create in a constructor and assign to a field; destroy in the matching destructor.
  2. Create and destroy inside a single method, with protection provided by try / finally.

I would strongly recommend that you try to shape your code so that all resource acquisition uses one of these two options.

How can you do so in your example? The option that leaps out at me is to use a factory to create your FileSystem object. This allows TFileLister to manage the lifetime of the FileSystem object, but gives you the flexibility of injecting different behaviour into TFileLister.

8 Comments

hi david, thanks for the answer. both local and constructor creation are not an option here - the question is specifically about DI. about the factory solution: how does that improve the situation? i now have to pass the factory and run into the very same question. and a singleton factory is not testable well again...
@Smasher The factory is what allows you to use one of my options 1 or 2 (what you call local and constructor creation). Indeed, the Wikipedia page to which you question links illustrates dependency injection with a factory!
@Smasher As you example stands you would use my option 1 but instead of writing FileSystem := TFileSystem.Create you would write FileSystem := CreateFileSystem where CreateFileSystem is defined to be reference to function: TFileSystemClass. That's the simplest factory implementation, but you could use a more advanced one. Really, I very very rarely find the need to use anything other than my options 1&2.
that works with a function reference, yes. but what if the factory is an object itself. then who owns the factory? thats the same poblem isnt it? besides, why should i pass a factory if what i really need is just one object. that is imho not as clean as DI in other languages...
@Smasher The factory can be global. It can be owned by the function or object that calls TFileLister.Create. It could simply be a function or a class function rather than a method (i.e. no Self). It could be an interface (managed lifetime). It could be a method of a record which was allocated on the stack (also effectively managed lifetime). Yes, there's more code without garbage collection.
|
1

I disagree with the notion that an object must be destroyed by the one that created it. Many times this is the natural choice but its certainly not the only way of managing memory. A better way to look at it is that an object's lifetime should end when it is no longer needed.

So what options do you have?

Use Interface reference counting

In many cases it is trivial to extract an interface from an existing class so don't shelve this idea just because your working with legacy code.

Use an IoC container that supports lifetime management.

There are a number of IoC containers for Delphi and more are popping up all the time. Spring for Delphi is one that I know of that supports lifetime management. Note: most of these containers target Delphi 2010 or newer so it may be difficult to find one for legacy code.

Use a garbage collector.

The Boehm GC memory manager is the only one I'm aware.

All three of these can be combined with poor man's dependency injection to get the testing benefits while making minimal changes to your legacy code. For those unfamiliar with the term you use constructor chaining to instantiate a default dependency.

constructor TMyClass.Create;
begin
  Create(TMyDependency.Create);
//Create(IoCContaineror.Resolve(TMyDependency));
end;

constructor TMyClass.Create(AMyDependency: TMyDependency)
begin
  FMyDependency := AMyDependency;
end;

Your production code continues to use the default constructor with the real object while your tests can inject a fake/mock/stub to sense what the class being exercised is playing nicely. Once your test coverage is high enough you can remove the default constructor.

1 Comment

+1 good answer. Thanks for that. I'll definitely have a closer look at these options.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.