Saturday, April 28, 2007

Horizontal extensions

MonoDevelop has always allowed creating new types of projects by subclassing the Project class. A Project subclass can for example override the OnBuild and OnExecute methods to provide custom behavior for building and executing. In this way add-ins can provide support for new languages. That's what I call vertical extensions (I always envision inheritance as a vertical hierarchy)

At some point I saw the need of allowing "horizontal extensions". For example, the gtk# designer needs to plug into the build process to generate the GUI code just before the project is compiled. Using a project subclass and override OnBuild is not possible in this case. We don't want to create a new project type, just add some behavior to all existing project types (well, maybe only those which can use the designer, but it is still an undetermined number of project types).

The first quick and dirty approach to allow this kind of extensibility was to create a new extension point where add-ins could register implementations of an interface like this:
public interface IBuildStep
{
ICompilerResult Build (IProgressMonitor monitor, Project project);
}
When building a project, the project would get a list of all IBuildStep implementations and would call Build() on each of them. In fact, the default implementation of the project build was just another IBuildStep implementation, so add-ins would be able to add build steps before or after the default project build operation.

This design has the advantage of being very simple, but it has some important limitations. For example, an extension cannot have control over the sequence of execution. If an add-in wants to do something before and after the project build, it has to add two steps in the correct position of the pipeline, and it won't be easy to share data between them.

Those limitations were not a problem for the gtk# designer add-in, but they became evident as I tried to extend the build pipeline for other purposes. So, I decided to rethink the design and do something more generic.

The first idea was to use the "message sink" model on which Remoting is based. Using this model the interface would look like this:
public interface IBuildExtension
{
ICompilerResult Build (IProgressMonitor monitor, Project project);
IBuildExtension Next { get; set; }
}
The idea here is that the project service chains all extensions by setting the Next property of every extension to the next extension in the chain. Then it calls the Build method on the first extension, and the extension is supposed to do whatever custom operations it needs to do and then call Build on the next extension in the chain. In this model, an extension has more control on the build process because it can do whatever operation before, after, or in replacement of the build operation (it can decide to not call Next.Build at all, for example). This model is not as direct to implement as the previous model, because the extension has to handle the Next property, but still it's quite easy.

One problem I find in this model is that it can't easily scale. The Build operation is not the only operation that makes sense to extend in a project. Other operations like Clean, Execute, Load and Save might also be extensible. One option is to add all those methods to a more generic IProjectExtension interface. The problem with this option is that if an add-in only wants to extend one operation it has to add dummy implementations for all methods. Another option is to create an IxxxxExtension interface for each operation. This solution is maybe better but it isn't ideal because it requires dealing with many interfaces and having one extension chain per operation. Every time you want to extend some operation you would have to find the interface that defines it, implement it in a class, find the correct extension point for that interface and register it.

Also, I'm becoming lately a bit tired of using interfaces. Interfaces are very commonly used in extensible APIs: you define an interface and the extender implements it. Using interfaces has advantages, but also important limitations: you are forced to implement all methods (even if you are not interested in all of them), they can't provide a default implementation, and you can't add new methods without breaking the compatibility with old versions. I've seen many APIs which define interfaces without a clear idea of what benefit does using those interfaces have. In many cases interfaces are not really needed, and using a simple class is just enough. I think the build extension example is one of those cases.

I finally designed the build extension more or less like this:

public class ProcessServiceExtension
{
internal ProcessServiceExtension next;

public virtual ICompilerResult Build (IProgressMonitor monitor, Project project)
{
return next.Build (monitor, project);
}
}
So, to provide an extension to the build method you don't implement an interface, but a subclass of ProcessServiceExtension, and then you override the Build method. For example:
public class MyExtension: ProcessServiceExtension
{
public override ICompilerResult Build (IProgressMonitor monitor, Project project)
{
// Do some custom processing
bool res = base.Build (project);
// Some post-processing
return res;
}
}
This class is similar to the previous IBuildExtension interface, but using a class has several advantages:
  • You don't have to implement the Next property. The base class does it. In fact, it is hidden to the extender. What I like about this model is that it allows implementing horizontal extensions just like if they were regular vertical extensions.
  • I can add new overridable methods to ProjectServiceExtension without breaking old extensions, and without having to define new interfaces or extension points.
  • ProjectServiceExtension can define overridable methods for all project operations, and extenders can override only the methods they are interested in.
  • ProjectServiceExtension can provide specialized overloads of the operations. For example, it could define generic Build(item) method, which would call a Build(project) method, or a Build(solution) method depending on the item type. Extenders interested in projects can just override Build(project), while extenders interested in more generic extensions which apply to all types of items can override Build(item).
I'm getting addicted to this extension model. MonoDevelop is using it in several places, and so far I haven't missed the "benefits" of interfaces at all.

8 comments:

eddiestone said...

cool...
there are some news about the next version of monodevelop ?

thanks for the great job

Anonymous said...

Hey that's pretty cool, man.

It sounds a little bit like you could've used the IAdaptable concept used all over the place in Eclipse. That helps them maintain the nice syntactic look'n'feel of interfaces but allows them to arbitrarily extend objects "horizontally" at runtime such that adding a new interface or whatever doesn't break existing API. It's a pretty cool concept, you ought to check it out if you haven't seen it already.

Thanks for blogging this stuff, though we love it!

Anonymous said...

Actually interfaces have a use only in the single-inheritance model, when you know that the implementor won't be able to derive from your base class or when it is useful to implement more interfaces in the same class (and you can't derive from two base classes, obviously).

So I'd say that your approach shows exactly what interfaces were created for, even if you're not using them. ;)

meebey said...

Wouldn't an Project.Building and Project.Builded event solve this issue too? The gtk# desginer would register at the Building event, and it can do the stuff it needs to do before the actual build is started.

Lluis said...

> ...
> So I'd say that your approach shows
> exactly what interfaces were created
> for, even if you're not using them. ;)

I disagree on that. My conclusion is that classes are more useful than interfaces in this specific extensibility problem.

Using an interface here would allow extenders to use their own base classes and maybe implement several extension interfaces in the same class. But if you think a bit about the semantics of this extension problem you'll realize that this is a very uncommon case, and it is not worth loosing all flexibility that using a base class provides just to support those rare cases.

Thinking in terms of "that's what interfaces are designed for" is dangerous. Interfaces have some advantages over classes, but also many constraints, so you may end unnecessarily constraining your design.

Andrea said...

>....
>But if you think a bit about the semantics
>.....

For this reason usually I define an IFoo interface and then I provide a FooBase class to inherit from with a default implementation.

As a "closed source" programmer and user :( I learn to not understimate the single inheritance limit of C#

trevorat said...

You forgot the part in your GTK visual designer where you specify the number of vertical or horizontal boxes / spacers in the properties context; fail.

Kevin said...

牙醫,植牙,矯正牙周病,紋身,刺青,創業,批發,皮膚科,痘痘,中醫,飛梭雷射,毛孔粗大,醫學美容,seo,關鍵字行銷,關鍵字自然排序,網路行銷,關鍵字自然排序,關鍵字行銷seo,關鍵字廣告,部落格行銷,網路行銷,seo,關鍵字行銷,關鍵字廣告,關鍵字,自然排序,部落格行銷,網路行銷,網路爆紅,牛舌餅婚紗台中婚紗