Delegates! Just Handle It!

Published 20 January 08 10:54 AM | jons 

In my last post I talked about using strategy-pattern-like interfaces to encapsulate the points of extension for a class. If I take that approach to one extreme, I would end up with a single extension interface with multiple methods, each of which represented an extension point for the domain class. This is not a good thing. The very first time that I chose to build an object that implements that interface, I would instantly become responsible for implementing something for every method on that interface, even if I only wanted to extend one method.

Remember that the intent of the interface extension approach was that the generated code of the domain class would call the methods on these extension interfaces at the appropriate time. The domain class will always call the factory method to obtain an instance of an object that implemented the interface. If there were no need for an extension, the factory method would return an instance of an object that implemented the extension interface methods with dummy code that did nothing. If I did need an extension, the factory method would have to return an object that implemented logic for each method on the interface. If I had designed my architecture properly and built my templates with some degree of diligence, I should have a circumstance in which most of the domain classes should not require any extension; in that circumstance the dummy implementations of the interfaces should work just fine. But if I do want to swap out the dummy interface for one that provides some amount of extension, I have to provide the entire interface, even if only one method on the interface actually has code that does something. The larger the number of methods, the more work I have to do. The massive interface approach, thus, violates our principles of doing the least amount of work possible when doing an extension.

The way to get around this problem is to create a number of different extension interfaces, each with a smaller number of methods. That way, when I start to build an extension object, I only have to implement the interface that contains the method I want to extend. I still have to implement all of the methods of that interface but by breaking up the extension points into multiple interfaces, I typically have fewer methods that I have fill up with dummy code.

One consequence of this approach is that I will end up with more instance level variables, each of which is pointing at one of the extension interfaces that the object uses. I also have to code up calls to the factory methods for each of those interfaces. Other the other hand, the code to define each of these interface variables and the code to call the factory methods is machine-generated code. Once I get it right, I can "set it and forget it". There is an argument that the totality of the code base is bloated because of these additional variables and methods, but in my experience, if "bloated" code is truly a concern, it is very unlikely that the project is using code generation in any significant way.

This leads me to consider what would happen if I took the interface approach to its opposite extreme. That is, every interface would consist of a single method. With a little thought, it should be obvious that this approach is not much different from defining the method signatures of a set of the delegates. Each delegate would reflect the message signature of a single method interface. The instance level variables defined above, each of which held a references to an object that implemented of a particular interface, could be modified to hold a reference to a delegate. The invocations of the methods of the interface would be modified to invoke the delegate, providing the appropriate set of parameters to match the message signature.

In this approach, in addition to the above modifications, I would extend the code generation templates to generate the following:

  • An instance of a "dummy" method that matches the method signature of each delegate and does "nothing"; where "nothing" could be equivalent to returning an empty string.
  • Initialization logic that would populate each delegate variable with a pointer to a delegate instance that pointed at the corresponding internal dummy method.
  • A setter method for each of the delegate signatures to permit an external object to replace the pointer to the delegate.

During the lifetime of the domain class instance, the machine-generated code in the domain class would invoke these delegate methods at the appropriate point in the execution of the domain class logic. In the normal circumstance, where no extension is required, the invocation of the method would execute the "dummy" method within the domain class. In the circumstance where an extension is required, the invocation of the method that would execute a "real" method within an extension class.

There's just one last point to cover. In the interface approach, the machine-generated code of the domain class would invoke external factory methods to obtain an instance of an object that implemented the corresponding interface. While that approach could be used for delegates, I think that a better approach is to eliminate the interface-by-interface factory methods and replace it with a single call during the construction of each domain object to an extender class that would, using the delegate setter methods, replace whatever delegates that were needed to extend the logic of the domain class. The call to the extender class would pass in the newly-created domain-class instance; the extender method would examine the type of that instance; and tweak it as needed.

One very particular form of extension is to replace some of the methods of the domain class, particularly those involving database I/O or other long-running operations, with dummy methods to facilitate unit testing. The way that I have typically done this is to include a second factory method that includes an additional parameter that points to an object that performs the modifications based upon the needs of unit testing. Three things have to be done to make this work: first, we need to define a very simple interface, say IExtendDomainClasses, with a single method in which we pass in the domain class instance to be extended; second, we need to create a class that implements this interface and populate the single extension method of that interface to examine the type of the domain class instance to determine what extensions to apply to it; and third, we need to implement a unit test extender class that also implements this same interface and modifies the domain class instance to meet the needs of unit testing.

I think that this approach is most suitable in those circumstances where I want to have a lot of different fine-grained extension points and I expect a very low incidence of extension. This approach simplifies the effort to create extensions. One could create a single extension class that could handle all of the extensions for domain classes within the application. If the programming language of choice, say C#, supports anonymous delegates, you would not even have to write the methods explicitly.

New Comments to this post are disabled

About jons

Jon Stonecash is a technology consultant and has been designing, developing, and testing various kinds of software for such a long time that he has had the opportunity to make most of the serious software development mistakes at least once. His long term interests center about databases and the aspects of the application that handle data access and business logic. He is also interested in the tools that assist the development process, particularly code generation.