The company I work for follows a business model where a core product exists (originally written in C, now in C++) with some default implementation but details are tailor-made for each customer (there are cca. 10-20 of them). What’s the best programming approach in such a scenario? Is there a good design pattern/methodology/paradigm/whatever out there that makes code reuse and maintenance optimal? (Versioning is just as much a headache, maybe it will be another SO question later).
Example: the system provides a service implemented as a function:
int doServiceA(const string & params);
A common dispatcher routine calls this function:
void dispatch(const string & cmd, const string & params)
{
int ret = 0;
if (cmd == "serviceA") {
ret = doServiceA(params);
}
//...
}
In our earliest approach the implementation of doServiceA was littered with #ifdef’s to handle customers’ unique requirements.
After a couple of years we switched to using virtual functions:
Core module:
class ServiceA
{
virtual int execute(const string & params); // with default implementation
};
ServiceA & getServiceA(); // factory method declaration
void dispatch(const string & cmd, const string & params)
{
int ret = 0;
if (cmd == "serviceA") {
ret = getServiceA().execute(params);
}
//...
}
Extension for customer XXX:
class XXXServiceA : public ServiceA
{
virtual int execute(const string & params); // implementation containing XXX-specific parts
};
ServiceA & getServiceA() // factory method implementation
{
static XXXServiceA s;
return s;
}
Extension for customer YYY:
class YYYServiceA : public ServiceA
{
virtual int execute(const string & params); // implementation containing YYY-specific parts
};
ServiceA & getServiceA() // factory method implementation
{
static YYYServiceA s;
return s;
}
So far we’ve been OK with this technique, but I have some concerns:
-
How do I differentiate from the default implementation when a customer requests a tiny change in the middle of my service? I guess I should break up ServiceA::execute() to pieces as small as possible and make them all virtual (as I really can’t foresee which parts are going to be customized later)?
-
Creating an object with no state feels like I’m violating the concept of OOP for techincal reasons.
-
Sometimes a change makes sense for multiple customers which results in either code duplication (XXXServiceA and YYYServiceA with partially same implementations) or complex inheritance hierarchies (when adding a class between ServiceA and its descendants to accommodate common code).
-
Just a feeling that virtuals may not be the right tool here as it’s a means to achieve runtime polymorphism whereas what I need is compile-time polymorphism (no XXXServiceA and YYYServiceA objects will exist in the same binary).
I experimented with a lot of things I read about (policies, mixins, CRTP) but so far I failed to improve much on the above design. I am wondering if I am missing something really simple here?
2
1.Tiny changes
There’s no magic available for this aspect:
- either you can define the execution as a set of more primitive virtual functions that you can override for tailoring purpose;
- or you can use a policy based design using to obtain the same effect, but at compile time (but with similar structural issues as above)
- or you could consider feature toggles
- or you have to duplicate and maintain redundant code.
2.Stateless objects
This is a dogmatic question that will not bring you closer to any solution. I can ensure you that your bad feelings won’t affect the accuracy of your code.
3.Common changes for multiple customers
Sharing common code parts for multiple customers is something that is a good candidate for refactoring the code with some template based policy design.
4.runtime vs. compile-time
As your problems seems to relate to customer specific variations, and you compile the code per customers, compile-time approach seems indeed to be more adapted. Use templates: the client specific classes would become a template argument.
template <class SerA>
ServiceA & getServiceA() // factory method implementation
{
static SerA s;
return s;
}
For the creation of the objects you could very well use an abstract factory base on templates, at compile time.
1