I was working on performance tuning an application the other day. The code in this post is a rough approximation of the problem I had to solve. The application defines a plug-in interface that the plug-in developers can implement. The plug-in gets initialized with a dictionary and and the application talks through the rest of the interface.
public interface IPlugin
{
void Initialize(Dictionary<string, Information> dataLibrary);
string TalkToMe(string message);
}
The initialization from the application is simple:
public void SetUpPlugin(IPlugin plugin)
{
Dictionary<string, Information> data = PopulateDictionary();
plugin.Initialize(data);
}
When this interface was originally written, the dictionary was populated using XML files. The dictionary only included about two hundred items, but over time, this dictionary became more complex. It grew out of the XML files, and into a database. It grew into tens of thousands of items. It is not hard to tell that there is a performance bottleneck here as the application populates this many items from the database. It turns out that the plug-ins don't tend to need a lot of the data in the dictionary, though I cannot predict what it will need to lookup. To populate the entire dictionary takes a TON of time, and it is completely unnecessary.
Unfortunately, I am bound to this interface, which is already published to our plug-in developers. My first inclination was to write a custom Dictionary<> class. It would be really nice if .NET would let me do something like this:
public class DataLibrary : Dictionary<string, Information>
{
private IApplicationLogic _logic;
public DataLibrary(IApplicationLogic logic)
{
_logic = logic;
}
public override Information this[string key]
{
get
{
return _logic.LookupInformation(key);
}
}
public override int Count
{
get
{
return _logic.InformationCount;
}
}
// etc
}
This would solve my needs completely if only methods were not sealed by default in C#. Unfortunately, they are, and .NET didn't make the Dictionary members virtual. In fact, it turns out, I am completely coded into a corner since I can't change the interface. If only the IPlugin interface were thought through a bit further, I wouldn't be in this predicament.
All that really needed to happen was for IPlugin.Initialize to take an IDictionary<string, Information> instead of a Dictionary<string, Information> (notice the 'I' in front of the Dictionary, indicating that it is an interface as opposed to a concrete class.) If this were only the case, I would be able to implement the interface using a database lookup and move on with my life.
Now, I really don't have any solution other than asking for an interface change to all of the plug-in writers for my application. This not only looks bad, but it IS bad. I suppose might be able to come up with something crazy using Dynamic Proxies, but that would get rather ugly rather quickly.
The more you think about it, the choice of Dictionary<string, Information> was bad for other reasons as well. The Dictionary class is a read/write class. We do not want our plug-in authors to modify the dictionary -- we only want them to look up data. We don't need them to iterate over the data, and we don't need to give them a vectored view of the keys or the values.
The message I am trying to get through here is simple: When you are creating an interface for public use, you need to think hard about what that interface looks like. Avoid concrete data passing when interfaces are available (IDictionary instead of Dictionary). These interfaces don't get to change much. They aren't as flexible as the internal code. Please, think about your interfaces.
14 comments:
Since Dictionary`2 is an IDictionary`2, can't you just change the plugin interface out from under your consumers?
Is the issue that you don't want them to recompile? Just make 'em recompile. Its not that big a deal :)
You can't always assume that the plug-ins can be recompiled. By changing the interface, you are invalidating previously delivered plug-ins. Backwards compatibility to existing plug-ins gets broken.
In addition, I don't know who has developed plug-ins for my software. I somehow need to notify the plug-in developers that they need to re-compile and re-distribute the plug-ins.
Good point.
That's the same reason why MSDN suggests to use ICollection or IList instead of the List
BTW, some refactoring tools do detect the opportunity to use some interface instead of the concrete class.
An excellent example of why .NET could do with Duck Typing.
Yes! Good point. I was actually thinking about that last night.
As an academic exercise, I was thinking it would be fun to come up with a "Duck Typing" library for .NET using reflection. I haven't worked it all out in my head, but I think it can be done with some hacky tricks... Dynamic Proxies, possibly?
My recommendation for bypassing the sealed class is use reflector and disassemble the class and then modify away. Or even check if Microsoft published the source code for the class.
I also recommend avoiding any type of heavy driven class from using reflection it is just monumentally slow.
Brian Can't you just create a new interface ? and declare the other one obsolete ? I have no clue what you're options are really. Well thinking about it probly not because you would have to change the code that uses your interface as well :-/
Perfect example of a failure by strong coupling. You remind me of JK.
P.S., Duck typing is NOT the answer.
"Perfect example of a failure by strong coupling."
Right on. That's an eloquent way of putting it. The answer is not doing this in the first place.
Duck Typing could be a way out, if it existed.
Who is JK?
Really good post and nice that you included a concrete example.
Couldn't you put a new in front and just hide the underlying indexer?
Unfortunately, using "new" in front of it only works if you have a reference to the derived type. The plugins only have a reference to the base type and the "new" override never gets called.
It is one of the caviats of using the "new" keyword. It is not fully polymorphic.
What I do not understand is that this cannot really be the only interface between your application and the plugins?
This interface is just for initializing your plugin. How do you pass interface references for calling your application functionality.. and also an interface for messages passing/method calling.
No, this is not the entire interface. This is just an example to illustrate my point. The rest of the interface is irrelevant to the article, so I simply replaced it with "string TalkToMe(string)" for brevity.
Post a Comment