The Plug-in Pattern

by Frans

The Plug-in Pattern is a software pattern for extending the behaviour of a class with a clean interface. Often behaviour of classes is extended by class inheritance, where the derived class overwrites some of the virtual methods of the class. A problem with this solution is that it conflicts with implementation hiding. It also leads to situations where derived class become a gathering places of unrelated behaviour extensions. Another way of extending the behaviour of classes is by means of configuration. In this solution the class is given a set of primitives with which the user of the class can configure the behaviour of the class. The problem with this solution is that the behaviour extensions are limited to the configuration abilities that the root class provides, and to the chosen implementation. In a sense the root class contain unnecessary information. Both solutions make it difficult to dynamically switch between different behaviour extensions.

The Plug-in Pattern derives its name from the fact that behaviour extension is plugged-into a core class. The pattern is realized by means of a single (partial) abstract class, which defines the interface between the plug-in and the core class. Specific plug-ins are created by sub-classing this class. The interface is bidirectional in the sense that it contains methods called by the core class that the plug-in should implement, and that it contains methods which the plug-in can call at the core class. It provides a clean interface in the sense that the abstract class only contains those methods that define the interface.

Example: syntax colouring

An example of flexible behaviour extension is that of a syntax colouring for an editor, where the chosen syntax colouring depends on the type of file used. Below some code that is taken from my reimplementation of syntax colouring in the Crystal Edit edit control. The original implementation made use of a derived class, which prevented the possibility of changing the syntax colouring dynamically in an easy way. The class AbstractSyntaxColorer defines the two-way interface of the plug-in. The class CCrystalTextView is the class that is responsible for implementing the text view.

class AbstractSyntaxColorer
{
	// CCrystalTextView needs to have access to some private members
	friend class CCrystalTextView;
public:
	AbstractSyntaxColorer();

	// Members which are access from CCrystalTextView
	// and which define default values
	COLORREF defaultTextColor;
	COLORREF defaultBkColor;
	COLORREF noTextColor;
	COLORREF selectTextColor;
	COLORREF selectBkColor;

	// Methods which should be implemented by derived syntax colorer
	virtual DWORD createParseState() { return 0; }
	virtual DWORD copyParseState(DWORD state) { return state; }
	virtual void destroyParseState(DWORD &state) { state = 0; }

	virtual void parseLine(const char *line, DWORD &state, int &lineFlags) = 0;

	// Methods which the syntax colorer can call from its implementation
	// of parseLine to perform the colouring
	void color(int begin, int end, COLORREF crText, COLORREF crBK);
	void startColor(int begin, COLORREF crText, COLORREF crBK);
	void endColor(int end);

private:
	void drawLine(LPCTSTR line, DWORD &state, int &lineFlags);
	CCrystalTextView *_view;
	bool _activeColorRange;
	int _begin;
	COLORREF _crText;
	COLORREF _crBK;
};

Implementation

AbstractSyntaxColorer::AbstractSyntaxColorer()
{
	defaultTextColor = ::GetSysColor(COLOR_WINDOWTEXT);
	defaultBkColor = ::GetSysColor(COLOR_WINDOW);
	noTextColor = defaultBkColor;
	selectTextColor = defaultBkColor;
	selectBkColor = defaultTextColor;
}


void AbstractSyntaxColorer::color(int begin, int end, COLORREF crText, COLORREF crBK)
{
	_view->DrawLineHelper(begin, end, crText, crBK);
}


void AbstractSyntaxColorer::startColor(int begin, COLORREF crText, COLORREF crBK)
{
	if (_activeColorRange)
		endColor(begin);
	_activeColorRange = true;
	_begin = begin;
	_crText = crText;
	_crBK = crBK;
}

void AbstractSyntaxColorer::endColor(int end)
{
	if (!_activeColorRange)
		return;

	color(_begin, end, _crText, _crBK);
	_activeColorRange = false;
}

void AbstractSyntaxColorer::drawLine(LPCTSTR line, DWORD &state, int &lineFlags)
{
	if (state == 0)
		state = createParseState();
	else
		state = copyParseState(state);
	_activeColorRange = false;
	parseLine(line, state, lineFlags);
	if (_activeColorRange)
		endColor(strlen(line));
}

Template class

The following template class implements an abstract syntax colorer where the parse state is represented by an arbitrary class.

template <class S>
class AbstractStateSyntaxColorer : public AbstractSyntaxColorer
{
	virtual void parseLine(const char *line, S& state, int &lineFlags) = 0;
	void parseLine(const char *line, DWORD &state, int &lineFlags)
	{	parseLine(line, *(S*)state, lineFlags);
	}
	DWORD createParseState() { return (DWORD)new S; }
	DWORD copyParseState(DWORD state) { return (DWORD)new S((S*)state); }
	void destroyParseState(DWORD &state) { delete (S*)state; }
};


home page | Software Engineering | My life as a hacker