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:
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?
SolutionThe 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)?
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 mapAdornment
to objects - An
object
toAdorner
dictionary (to supportAdorner
lifecycle) - An example
Class
.
/// <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.