Wednesday, 11 August 2010

Design Patterns in memory managed languages

A lot of the work I am currently doing revolves around a legacy Delphi codebase. Delphi is an objected oriented language based on the Pascal syntax - and it requires the programmer to manage memory. As such, it has been an interesting challenge to attempt to apply modern design patterns to such a codebase. Languages such as Java and Ruby don't require the programmer to free up objects after they are no longer required, and as such many of the patterns we use in these languages need to be modified in order to apply them in a language such as Delphi.

For example, the following is a common pattern in constructor overloading:

class Foo {
field Bar bar;

constructor Foo() {
this(new DefaultBar());
}

constructor Foo(Bar bar) {
this.bar = bar;
}
}

Constructor overloading in this manner allows the user to inject custom behaviour if desired, or make use of a default implementation. However, in a memory managed language, what should the destructor do? If bar was initialised using the first constructor, then Foo, is responsible for the clean up of bar. However, if bar was injected, then traditionally, the calling method is responsible for the clean up of bar. One option is getting Foo to remember whether bar was initialised or injected. However this is messy and obscures business logic occurring in Foo. Another alternative always make Foo responsible for the clean up of bar. This fits nicely with another pattern than needs to be adapted for a memory managed language - the Factory pattern.

class FooFactory {
Foo createFoo() {
Bar bar = new MarsBar();
return new Foo(bar);
}
}

In this case, FooFactory cannot be responsible for the destruction of Foo. Instead, that responsibility becomes that of the method which called FooFactory. Also, Foo becomes responsible for the destruction of bar as well.

Therefore, to properly make use of dependency injection in memory managed languages, certain concepts must be challenged. The method that creates an object is no longer its clear owner. When applying dependency injection in languages such as this, it is more important than ever to use sensible domain objects and abstractions. This makes it clear who owns any given object, and therefore who is responsible for its destruction.

2 comments:

Bill Schofield said...

Thanks for the intriguing post!

I've spent most of the last 6 years working through the exact issues you're describing. Here's the best I've been able to come up with so far:

* For classes with a small number of instances use reference counted pointers (especially if the injected objects are shared amongst multiple 'owners'). This has performance and memory implications.

* Use factories/providers to create and destroy objects. This means that you need to hold onto a reference to the factory, but allows the factory the opportunity to use efficient memory allocation strategies.

* Use naming convention to manage whether the injected object is owned by the creator or the injectee. This is not ideal, but does help some.


These all seem like reasonably healthy compromises between code flexibility and performance. I needed both when working on a relatively complex Wii game.

philip.g.potter said...

I would think of your issue with constructor overloading in terms of making ownership part of the class contract. A class must clearly define whether it or somebody else is responsible for clearing up the things it refers to.

In C++, you have the RAII idiom and the unique_ptr and shared_ptr classes to implement resource ownership. If your instance, and nothing else, is responsible for releasing a resource, use unique_ptr. If you want several instances to share ownership, use shared_ptr; with this, the last instance out of the room will switch the lights off.

Note that this is more general than memory management: it can be applied to network connections, files, sound/graphics devices, and all kinds of other resources.

Finally, I would like to ask what you mean by owner in this sentence: "The method that creates an object is no longer its clear owner." Was it ever the clear owner? What does an "owner" mean in a language with a GC?

I think of the "owner" of a resource as the thing responsible for releasing the resource when it's done. Under a GC, there is no need to release memory, so this definition of "owner" breaks down (or alternatively the GC is the owner of all allocated memory)