-1

I had a class hierarchy with several classes that interact with each other. After introducing new feature that is optional (but it depends on external libraries) I have the following code (just simplification):

#ifdef (FEATURE1)
    #include "feature1.h"
#endif

class Engine
{
public:
    void useFeature(bool value)
    {
                ...
    }

    void run()
    {
        // statements
#ifdef (FEATURE1)
        // feature-specific statements
#endif
        // statements
        action.makeSomething1();
        // statements
    }
private:
    Action action;
};

class Action
{
public:
    void makeSomething1()
    {
        // statements
#ifdef (FEATURE1)
        // feature-specific statements
#endif
        // statements
    }

    void makeSomething2()
    {
        // statements
#ifdef (FEATURE1)
        // feature-specific statements
#endif
        // statements
#ifdef (FEATURE1)
        // feature-specific statements
#endif
        // statements
    }
    private:
        ...
};

From my points of view it looks bad and unreadable and now I'm thinking how to refactor this code in the right way. What's the best design pattern approach for introducing optional feature to current code base? What about having Engine and EngineWithFeature, Action and ActionWithFeature? It seems that it will cause a lot of code duplication.

  • Got a story for you to read. – candied_orange Feb 17 at 23:02
  • 1
    I am getting a little lost on the concept of Feature in respect to Engine or Action. Could you provide a little more detail in context of an Engine/Action to some Feature? At a glance though, this sounds like you have some base implementation (abstract) and need concrete implementations that overrides base functionality. If so, you could push the decision back to whatever instantiates the base Engine/Action to determine whether to use BaseEngine or Feature1Engine. I don't know if that was the intended direction, or if that is what you were wanting to avoid. – eparham7861 Feb 17 at 23:23
  • 1
    @candied_orange Stories are all good, but he should read GoF. – Nick Alexeev Feb 18 at 0:30
  • @eparham7861 I have an implementation of Engine that works. And now I have a new feature for Engine that forces Engine to work in slightly different way (but it's optional). User can use it or can decided not to use it. – Igor B Feb 18 at 5:36
  • Is a user someone using the UI of some application, or someone developing code around what you are making? In either case when you say "force Engine to work differently", do you mean completely different branches are executed, different values are referenced for conditions, a different query is used, or some UI thing changes functionality? Before getting too much more into some design pattern, it would be beneficial to make partial sentences or phrases in respect to either the functionality or the customer/business perspective of how a physical form would show account for these options. – eparham7861 Feb 18 at 5:58
1

Strategy

Provide a base class and two (or more) implementations one per feature/feature set.

If the number of combinations are low this may work well, especially if you can use separate abstractions for separate features.

class Feature1Base { virtual ~Feature1Base(); ... };
class Feature1On : public Feature1Base { ... };
class Feature1Off : public Feature1Base { ... };

class Engine
{
     Engine(std::shared_ptr<Feature1Base> feature);

     void run()
     {
         // statements
         *feature();
         // statements
         action.makeSomething1(feature);
         // statements
     }
private:
    std::shared_ptr<Feature1Base> feature;
}
class Action
{
public:
    void makeSomething1(std::shared_ptr<Feature1Base> feature)
    {
        // statements
        feature->xyz();
        // statements
    }

    void makeSomething2(std::shared_ptr<Feature1Base> feature)
    {
        // statements

        feature->abc();
        // statements

        feature->def();
        // statements
    }
    private:
        ...
};

This allows runtime selection of the feature from configuration, or from the UI, etc... It also allows it to be run simultaneously with different features.

Separate Compilation Units

If the code requires the library in some sense for compilation/proper runtime loading try writing several seperate compilation units.

This is particularly usefull if the feature is platform specific say windows/unix/max/linux/... have one header file expose the interface, but have several implementation files.

feature.h
feature_osx.c
feature_win32.c
feature_nix.c
...

Now simply have your build select the correct implementation file during compilation.

You could also use this to control for specific third-party libraries.

  • Thank you for the answer. What I should do if I don't need feature in Engine. User of this API can decised not to use this feature. – Igor B Feb 18 at 5:40
  • That would depend on the option gone for. Under option 1 simple do not create that strategy at runtime. The functionality is compiled in, just not used. Alternately option 2 gives the user the option to choose the appropriate implementation at compile time. If the user does not want it, they do not compile it. Unfortunately they cannot change their minds later without recompiling. – Kain0_0 Feb 20 at 3:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.