The Adorner Pattern in C++

Posted in design-patterns by Christopher R. Wirz on Sun Dec 02 2018



The "adorner pattern" has been missing from history. Try googling it? You don't get very many results. One of the top results is a Chinese translation, but the definition is mostly right:

Problem

How do you organize your code so that it can easily add basic or some rarely used features, rather than writing directly without extra code inside your class?

Solution

The adorner pattern provides a flexible scheme for changing subclasses. The Adorner pattern allows you to modify objects dynamically without causing the number of subclasses to explode, adding attributes.

Inheritance is available in most modern languages, but if you put too many properties and methods on a class, you end up with "God classes". This is an anti-pattern in which one class maintains a lot of data - most of which it normally doesn't need. I won't go into why taking up more memory than necessary is bad for performance.

The next option is to subclass a type - and only use that type sparingly. That's actually a great idea - if nothing (that also may electively use certain data) is already inheriting from the base class. Of course, we know that is rarely the case in object oriented languages. So how do you avoid bloating your base class when it only needs certain properties once in a while: the Adorner pattern.

The Adorner pattern provides added capabilities in a facade class that holds reference to the original object for purposes of implicit casting. Once an Adorner is needed, it lives for the duration of the object that is adorned. So, instead of paying for sizeof(BaseClass), you're only paying for 8 bytes worth of pointer - and just when you need it. Believe me when I say the savings amortizes very quickly.

So why is the "Adorner Pattern" missing from the zeitgeist? Is everyone okay with alternatives like the Decorator Pattern, Facade Pattern, or anti-patterns like God Classes? Did the debate on this very matter almost break up the "Gang of Four" and possibly change the course of software engineering history (it's hard to break a tie with 4 people)?

Note: "Design Patterns: Elements of Reusable Object-Oriented Software (1994)" is a software engineering book describing software design patterns with authors Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides - foreword by Grady Booch. The book explores the capabilities and pitfalls of object-oriented programming and identify 23 classic software design patterns.

Maybe we should take a look and see exactly what is going on. Sticking with the language of the choice for the "Gang of Four", we build the basic example in C++. First, we need a property:


#include <stdio.h>
#include <functional>
#include <map> /* for map */

/**
* This class lets you specify access patterns using C++11 lambdas.
*/
template <typename T>
class property {
private:
    std::function<void(const T&)> setFunction;
    std::function<T()> getFunction;
public:
    /**
    * Constructor taking the access methods
    *
    * @param getf A function to set the value
    * @param setf A function to get the value
    */
    property(std::function<T()> getf, std::function<void(const T&)> setf) {
        getFunction = getf;
        setFunction = setf;
    }
    /**
    * Gets the data
    */
    operator T () {
        return getFunction();
    }
    /**
    * Sets the data
    */
    T operator = (const T &i) {
        setFunction(i);
        return i;
    }
};

With the easy part out of the way, it's time to define the adornment


/**
 * Contains data elements we wish to adorn to a class
 */
struct Adornment {
	int Attribute = 8;
	char* Type = "Unknown";
};

Yes, it's pretty basic, less than 32 bytes, but it illustrates something you won't check all the time: type.


/**
* A class to adorn other classes
*/
class Adorner {
private:
	void* adorned = NULL;
	Adornment* adornment = NULL;
	static std::map<void*, Adornment*> adorners;
    
	/**
	* Creates a new instance of an adorner form a pointer
	*
	* The adornment will be re-used if the pointer is recognized
	*
	* @param pointer A pointer to the adorned instance
	*/
	Adorner(void* pointer) {
		if (Adorner::adorners.find(pointer) == Adorner::adorners.end()) {
			Adorner::adorners[pointer] = new Adornment();
			printf("Adorner created on 0x%p\n", pointer);
		}
		else {
			printf("Adorner re-used on 0x%p\n", pointer);
		}
		adornment = Adorner::adorners[pointer];
		adorned = pointer;
	};
public:
	/**
	* Erases an Adornment for an instance
	*
	* This method searches through the adornments and erases
	* the one specific to the pointer
	*
	* @param pointer A pointer to the adorned instance
	* @return true if erased, false if unable to erase
	*/
	static bool Erase(void* pointer) {
		if (Adorner::adorners.find(pointer) != Adorner::adorners.end()) {
			Adornment* a = Adorner::adorners[pointer];
			delete a;
			a = NULL;
			Adorner::adorners.erase(pointer);
			printf("Adorner deleted on 0x%p\n", pointer);
			return true;
		}
		return false;
	}
	/**
	* Creates a new instance of an adorner form a pointer
	*
	* Calls the class constructor for a general pointer
	*
	* @param pointer A pointer to the adorned instance
	*/
	template<typename T>
	Adorner(T* ptr) : Adorner((void*)(ptr)) {
		printf("Implicit pointer cast using 0x%p\n", ptr);
		this->adornment->Type = "Unknown";
	}
	/**
	* Allows for implicit cast to the original type
	*/
	template<typename T>
	operator T*() { return  (T*)adorned; }
	/**
	* Property to get the "Attribute" adornment
	*/
	property<int> Attribute = property<int>([this]() { return this->adornment->Attribute; }, [this](int val) {this->adornment->Attribute = val; });
	/**
	* Property to get the "Type" adornment
	*/
	property<char*> Type = property<char*>([this]() { return this->adornment->Type; }, [this](char* type) {this->adornment->Type = type; });
};
/**
* Static members are defined out of the class scope.
*/
std::map<void*, Adornment*> Adorner::adorners;
    
/**
* Define a new delete method
*/
void operator delete(void * p)
{
	if (!Adorner::Erase(p)) {
		printf("No adorner found on 0x%p\n", p);
	}
	free(p);
	p = NULL;
}

There are several tricks in here to implement the Adorner pattern. First, we created an Adorner class that can be passed into methods as if it was the class it was adorning (through implicit casting). The second thing we did was create a static dictionary of wrapped objects and Adorners. Third, we the delete operator was overridden such that the Adorner will not live beyond the object it is adorning.

Great, now let's make an example class. We can also add some specifics such that we let the compiler help us out with adornment: in this case, type detection. We can do this through constructor template specialization.


/**
* An example class
*/
class NormalClass {
private:
	int val = 0;
public:
	/**
	* Property to get the Value
	*/
	property<int> Value = property<int>([this]() { return this->val; }, [this](int val) {this->val = val; });
};

/**
* constructor template specialization
*/
template <>
Adorner::Adorner(NormalClass* obj) : Adorner((void*)(obj)) {
	printf("Implicit NormalClass instance cast using 0x%p\n", (void*)(obj));
	this->adornment->Type = "NormalClass";
}

So now we have defined the following:

  • An Adornment structure
  • An Adorner class to map Adornment to objects
  • An object to Adorner dictionary (to support Adorner lifecycle)
  • An example Class.
Which means it's now time to test.


/// <summary>
///		The main function
/// </summary>
/// <param name="argc">The argument count.</param>
/// <param name="argv">The argument vector.</param>
/// <returns>0 for success</returns>
int main(int argc, char** argv)
{
	// Create a normal class and set its value to something non-default
	printf("\nCreate a new NormalClass instance and set the Val:\n");
	NormalClass* normalClassPointer = new NormalClass();
	normalClassPointer->Value = 50;
	printf("pointer->Value = %d\n", (int)normalClassPointer->Value);
	// Adorn our NormalClass different ways
	printf("\nNow add adorners in different ways:\n");
	Adorner explicitCastPointer = (Adorner)(normalClassPointer);
	Adorner implicitCastPointer = normalClassPointer;
	NormalClass* normalClassPointer2 = new NormalClass();
	Adorner implicitCastPointer2 = normalClassPointer2;
	// Set the value of the adornment attributes
	explicitCastPointer.Attribute = 5;
	implicitCastPointer.Attribute = 7;
	implicitCastPointer2.Attribute = 17;
	implicitCastPointer2.Type = "Custom";
	// Check the adornment property values (they should be the same)
	printf("\nFor an adorner, all adornment properties should be the same:\n");
	printf("explicitCastPointer.Attribute = %d\n", (int)explicitCastPointer.Attribute);
	printf("explicitCastPointer.Type = %s\n", (char*)explicitCastPointer.Type);
	printf("implicitCastPointer.Attribute = %d\n", (int)implicitCastPointer.Attribute);
	printf("implicitCastPointer2.Attribute = %d\n", (int)implicitCastPointer2.Attribute);
	printf("implicitCastPointer2.Type = %s\n", (char*)implicitCastPointer2.Type);
	// Now get the original class back form the adorner
	NormalClass* ncPointerFromImplicitCast = explicitCastPointer;
		
	// Even though the value was only set once, both instance and pointer should have the same value
	printf("pointer->Value = %d\n", (int)ncPointerFromImplicitCast->Value);
	printf("\nDelete instances believed to have Adorners:\n");
	delete normalClassPointer;
	delete normalClassPointer2;
	// Make a class we never adorn just so we can see what happens when we delete it
	printf("\nDelete instnaces having no Adorners:\n");
	NormalClass* normalClassPointerNoAdorn = new NormalClass();
	delete normalClassPointerNoAdorn;

    return 0;
}

When running the code, the results are:

Create a new NormalClass instance and set the Val:
pointer->Value = 50
Now add adorners in different ways:
Adorner created on 0x000001747C730E00
Implicit NormalClass instance cast using 0x000001747C730E00
Adorner re-used on 0x000001747C730E00
Implicit NormalClass instance cast using 0x000001747C730E00
Adorner created on 0x000001747C730ED0
Implicit NormalClass instance cast using 0x000001747C730ED0
For an adorner, all adornment properties should be the same:
explicitCastPointer.Attribute = 7
explicitCastPointer.Type = NormalClass
implicitCastPointer.Attribute = 7
implicitCastPointer2.Attribute = 17
implicitCastPointer2.Type = Custom
pointer->Value = 50
Delete instances believed to have Adorners:
No adorner found on 0x000001747C1C8150
No adorner found on 0x0000017461BE3AC0
Adorner deleted on 0x000001747C730E00
No adorner found on 0x000001747C1C7110
No adorner found on 0x0000017461BE3B30
Adorner deleted on 0x000001747C730ED0
Delete instances having no Adorners:
No adorner found on 0x000001747C730440

We see that Adorners are created, re-used, and delete successfully. Each Adorner maintains its own set of values, which can be read or set. Only one Adorner is created for each object. Success!

What's next? You can try the example online.