The Support Pattern

January 31, 2009

Today I thought I might write about a little pattern that I like to call the support pattern. It’s probably got some other name, or exists as a composite of one of the original GoF patterns but I always refer to it as the support pattern. The reason I call it the support pattern is because of the Java PropertyChangeSupport class. If you’re not familiar with it, the PropertyChangeSupport class provides a simple means for POJOs to fire an event when a property value changes. Clients can then subscribe to these change events and decide what to do with them. This is particularly useful in MVC type scenarios where changes in the Model need to be propagated to the View. Really when it comes down to it the support pattern is best employed in situations where you want to augment an existing object with some commonly used behavior that doesn’t necessarily fall within what you would consider to be the object’s primary purpose (remember the single responsibility OO design principle).

Anyway last night I found myself needing to add tagging behavior to my Transaction and BudgetItem objects in my personal finance app Freedom. Being that I had to add this behavior in more than one place it was an immediate candidate for some level of encapsulation and/or abstraction. Say hello to the support pattern!

The ITaggable interface

First I created an interface that defines what a taggable object can do.

ITaggable.java

/**
 * Interface for tagging objects.
 *
 * @since 30/01/2009 7:36:24 AM
 * @revision $Revision$ $Date$
 * @author Dave Kuhn
 *
 */
public interface ITaggable {

	/**
	 * Each taggable object that has at least one tag set has a primary tag. The
	 * primary tag is usually the first tag set via {@link #tag(String)} or
	 * {@link #tag(String[])}. Otherwise the primary tag can be set explicitly
	 * via {@link #setPrimaryTag(String)}.
	 *
	 * @return the primary tag
	 */
	String getPrimaryTag();

	/**
	 * Returns all the tags associated with this object.
	 *
	 * @return all tags for this
	 */
	String[] getTags();

	/**
	 * Returns true if this object has the specified tag.
	 *
	 * @param tag the tag to check
	 * @return true if object has tag, otherwise false
	 */
	boolean hasTag(String tag);

	/**
	 * Removes the specified tag from this object. This has no effect if the tag
	 * does not exist on the object.
	 *
	 * @param tag the tag to remove
	 */
	void removeTag(String tag);

	/**
	 * Removes the specified tags from the object. This has no effect if the any
	 * or all of the tags in the array do not exist for this object.
	 *
	 * @param tags the tags to remove
	 */
	void removeTags(String[] tags);

	/**
	 * Sets the primary tag for this object. If the tag does not exist, it is
	 * added to the greater list of tags for this object. If it already exists
	 * then the matching tag is simply set to be the primary tag.
	 *

	 * Where a primary tag has already been set (e.g. via the first call to
	 * {@link #tag(String)}) then setting the primary tag simply relegates the
	 * incumbent tag to being a regular tag, it does not remove it.
	 *
	 * @param tag the tag to set as the primary tag
	 */
	void setPrimaryTag(String tag);

	/**
	 * Tags this object with the specified tag.
	 *
	 * @param tag the tag to apply to this
	 */
	void tag(String tag);

	/**
	 * Tags this object with the specified tags.
	 *
	 * @param tags an array of tags to apply to this
	 */
	void tag(String[] tags);

}

The TagSupport class

The next step was to create a support class that implemented the ITaggable interface. This encapsulates all the necessary knowledge of how to tag an object.

TagSupport.java

/**
 * Utility class that provides tagging support for objects implementing the
 * {@link ITaggable} interface.
 *
 * @since 30/01/2009 6:52:28 PM
 * @version $Revision$ $Date$
 * @author Dave Kuhn
 */
public class TagSupport implements ITaggable {

	private String primaryTag;
	private List tags;

	/**
	 * Constructs a TagSupportProvider initialised with an empty tag list.
	 */
	public TagSupport() {
		tags = new ArrayList();
	}

	/**
	 * Constructs a TagSupportProvider initialised with the tags provided.
	 *
	 * @param tags tags to add
	 */
	public TagSupport(String[] tags) {
		this();
		tag(tags);
	}

	@Override
	public String getPrimaryTag() {
		return primaryTag;
	}

	@Override
	public String[] getTags() {
		return tags.toArray(new String[tags.size()]);
	}

	@Override
	public boolean hasTag(String tag) {
		return tags.contains(tag);
	}

	@Override
	public void removeTag(String tag) {
		// Ignore nulls, empty strings and tags which aren't in list.
		if (tag == null || tag.isEmpty() || !hasTag(tag))
			return;

		tags.remove(tag);

		/* First tag in list becomes primary tag in the event that tag being
		 * removed is the primary tag. If the item being removed is the last
		 * in the list (i.e. there are no tags left) the primary tag is set to
		 * null. */
		if (primaryTag.equals(tag) && tags.size() > 0)
			primaryTag = tags.get(0);
		else
			primaryTag = null;
	}

	@Override
	public void removeTags(String[] tags) {
		for (String tag : tags)
			removeTag(tag);
	}

	@Override
	public void setPrimaryTag(String tag) {
		if (!hasTag(tag))
			tag(tag);

		primaryTag = tag;
	}

	@Override
	public void tag(String tag) {
		// Ignore nulls, empty strings and duplicates.
		if (tag == null || tag.isEmpty() || hasTag(tag))
			return;

		// First tag becomes primary tag by default.
		if (tags.size() == 0)
			setPrimaryTag(tag);

		tags.add(tag);
	}

	@Override
	public void tag(String[] tags) {
		for (String tag : tags)
			tag(tag);
	}

}

Putting it to work

Lastly I added a TagSupport instance to my Transaction and BudgetItem classes, implemented the ITaggable interface, and then delegated to my member TagSupport instance like so:

Transaction.java

/**
 * A stateful transaction implementation.
 *
 * @since 30/01/2009 9:54:00 PM
 * @version $Revision$ $Date$
 * @author Dave Kuhn
 */
public class Transaction implements ITaggable {
	...

	private ITaggable tagSupport = new TagSupport();

	...

	@Override
	public void tag(String tag) {
		tagSupport.tag(tag);
	}

}

That’s all there is to it!

Follow

Get every new post delivered to your Inbox.