C89-DP: Prototype
Apparently the goal of this pattern is to cache costly objects and clone them instead of requesting them again. I've never used this before, so it should be a nice challenge!
IClonable
This is what all clonable classes will be implementing.
typedef struct IClonable IClonable;
struct IClonable
{
void (*clone)(IClonable* self, IClonable* other);
};
Something clonable
We'll be re-using Foo
from C89-OOP: Class
but with a minor twist; it's going to have IClonable
implemented.
typedef struct Foo Foo;
struct Foo
{
IClonable* iclonable;
int x;
};
void Foo_clone(IClonable* self, IClonable* other)
{
Foo* a = (Foo*)self;
Foo* b = (Foo*)other;
b->x = a->x;
}
void Foo_ctor(Foo* self, int x)
{
static IClonable iclonable = {
Foo_clone
};
self->iclonable = &iclonable;
self->x = x;
}
Foo_clone
is where the magic happens. We just copy the values over to the other instance. We initialize the cache the first time Foo_clone
is called.
Cache
Normally you would make a list or array for this, but in our case having an indexed static instance is good enough. I use an array of ints (C89 doesn't have a bool type build-in) to keep track of which values are initialized.
#define ISLOADED_LENGTH 1
enum CacheIds
{
CACHE_FOO = 0
};
static int isLoaded[ISLOADED_LENGTH] = { 0 };
void Cache_get(IClonable* instance, int id)
{
static Foo foo = { 0 };
if (!isLoaded[CACHE_FOO])
{
Foo_ctor(&foo, 1);
isLoaded[CACHE_FOO] = 1;
}
switch (id)
{
case CACHE_FOO:
foo.iclonable->clone((IClonable*)&foo, instance);
return;
}
}
The idea shares alot with the Factory pattern discussed before in the article C89-DP: Factory
. But instead of running the constructor, we are cloning the values into the instance of Foo
.
Usage
As simple as it gets:
Foo foo;
int val;
Cache_get((IClonable*)&foo, CACHE_FOO);
val = foo.x;
Thoughts
While this works, I personally would instead just have a clone function like this and leave out the IClonable
interface and Cache_get
:
void Foo_clone(Foo* self)
{
static int isLoaded = 0;
static Foo cache = { 0 };
if (!isLoaded)
{
Foo_ctor(&cache, 1);
isLoaded = 1;
}
self->x = cache.x;
}
Afterwards just call Foo_clone
directly:
Foo foo;
int val;
Foo_clone(&foo);
val = foo.x;
I think the IClonable interface starts to make more sense when you start to use the type system for automatic detection of which type needs to be initialized. Adapt the code from above with minor tweaks, and you got a more robuust system going on for less complexity.
struct IClonable
{
void (*clone)(IClonable* self);
};
void Foo_clone(IClonable* self)
{
static int isLoaded = 0;
static Foo cache = { 0 };
Foo* foo = (Foo*)self;
if (!isLoaded)
{
Foo_ctor(&cache, 1);
isLoaded = 1;
}
foo->x = cache.x;
}
void Cache_get(IClonable* instance)
{
Type* type = (Type*)instance;
if (type->getId() == Foo_getTypeId())
{
instance->clone(instance);
return;
}
}
This system of couse can't have multiple instances of Foo
to clone from, but it works nicer in an environment where generics are important due to automatic detection of types.
Conclusion
It just works. It takes a bit of setup, but its nice that it was doable.