Imagine that a developer would like to extend our text editor with a tool that converts, at the press of a button, all of the text in the editor to pig latin. PigLatinTool needs a way to get and set the text in the editor. So at the very least, we need to create a context interface with the capability to get and set the text in the editor. What if we just expose the TextEditorComponent as a whole to the tool, by defining an interface like so:
public interface ITextEditorToolContext : IToolContext
{
TextEditorComponent TextEditor { get; }
}
This fulfills our requirement: the tool accesses the TextEditorComponent from the interface, which gives it access to the TextEditorComponent.Text property. However, we’ve also given the tool access to every method and property on the TextEditorComponent, which might allow the tool to do things we don’t want it to do.
Exposing the entire component through the interface is not a good idea. We should only expose properties that we explicitly want tools to have access to. Let’s re-define the interface as:
public interface ITextEditorToolContext : IToolContext
{
string Text { get; set; }
}
That looks better. We have exposed a Text property which provides get/set access to the text in the editor without exposing the entire component. Are there other properties that should be exposed? We might as well expose the word count, since we have it available, and some tool may wish to make use of it. A more sophisticated TextEditorComponent would probably have some notion of a current selection, in which case we would want to expose this to allow the tool to operate on the selected text. However, that is beyond the scope of this discussion. The final revision of the interface will be defined as:
public interface ITextEditorToolContext : IToolContext
{
string Text { get; set; }
int WordCount { get; }
}
Now we must create an implementation of this interface. The recommended way to do this is to create an inner class that resides inside of the TextEditorComponent class:
public class TextEditorComponent : ApplicationComponent
{
private class TextEditorToolContext : ToolContext, ITextEditorToolContext
{
}
}
Note that TextEditorToolContext extends the abstract class ToolContext. This protects it against any possible future changes to the IToolContext interface. We must now give TextEditorToolContext a reference back to the owning component, and implement ITextEditorToolContext. The complete definition is as follows:
private class TextEditorToolContext : ToolContext, ITextEditorToolContext
{
private TextEditorComponent _owner;
public TextEditorToolContext(TextEditorComponent owner)
{
_owner = owner;
}
public string Text
{
get { return _owner.Text; }
set { _owner.Text = value; }
}
public int WordCount
{
get { return _owner.WordCount; }
}
}
Now we can return to the TextEditorComponent constructor where we created an instance of ToolSet, and fill in the second parameter to the ToolSet constructor.
public TextEditorComponent()
{
_toolSet = new ToolSet(
new TextEditorToolExtensionPoint(),
new TextEditorToolContext(this));
}
And with that, TextEditorComponent creates an instance of every tool that extends TextEditorToolExtensionPoint and initializes those tools with an ITextEditorToolContext that refers back to the TextEditorComponent itself.