2

EDIT: note I want a static compile time method, when I know exactly what needs to go where at compile time.

I often find myself having multiple functions which follow the same pattern, but only a few parts change. Often I opt for the Template Method Pattern, however class based approaches (especially in c++) seem very cumbersome. I'm often forced to create actual objects when really all I'm trying to do is change one line for another that can't be prepended or appended to the routine I'm writing, where ideally I'd want to simply generate a function given a pattern at compile time. I even need to create private/protected classes where the procedure only makes sense in the class I'm using. This all seems like a huge waste.

I'd like a static approach to this, where I know exactly which function I would like to use in each situation, but I don't want duplicated code between each function where only a small amount changes. Is it possible in something like C++ with out resorting to creating custom classes for each case this happens? There are "solutions" like creating several custom functions for each step, but with out TMP these functions become coupled to a high degree and require way more parameters to be passed around than normally needed, and still never really seem to get rid of code duplication.

EDIT most recent example I came across since I was asked for examples...

class X{
    A m_class_member_1;
    B m_class_member_2;
    C m_class_member_3;
    D m_class_member_4;
    E m_class_member_5;
    ...
    void duplicateProcedure_1(){
        auto some_value = m_class_member_1.function();
        if(valuePassesTest(some_value)){
            auto some_other_value = m_class_member_2.function(some_value);
            m_class_member_3.function(some_other_value);
            m_class_member_4.function(some_other_value);
            // changed procedure
            m_class_member_5.function_1(some_other_value);
        }
    }

    void duplicateProcedure_2(){
        auto some_value = m_class_member_1.function();
        if(valuePassesTest(some_value)){
            auto some_other_value = m_class_member_2.function(some_value);
            m_class_member_3.function(some_other_value);
            m_class_member_4.function(some_other_value);
            //changed procedure
            m_class_member_5.function_2(some_other_value);
        }
    }
}

the last line in each is the only thing that needs to be changed, everything else needs to be the same, but they also depend on variables created with in the procedure, as well as conditions outside the scope of their block.

Krupip
  • 1,340

3 Answers3

2

You're right to be cautious on abusing of abstractions. This is well described in this article.

I can see 3 alternatives to that:

  • Composition: you keep the 2 duplicated procedures and extract just the common parts to some private function. Easy, readable, there may still have some duplicated parts but it's kept reasonable. The down side is that you may come to create many tiny private functions for that.
  • Anonymous functions / lambdas; except if you have good reason to avoid it (is it why you mention "imperative" oo language?), it solves exactly this kind of issue; but could lead to less readable code eventually.
  • And finally sometimes we focus too much on removing duplicated parts, while we shouldn't. Duplication is not necessarily bad (as explained in the link above). I would say: try to reduce duplicated code up to a point. Compose as long as it's beneficial without doubt, but there's no reason to be ashamed when leaving some duplicated code.
Joel
  • 441
1

I don't know if C++ allows you to pass bound methods around as callbacks, but if it does, the easiest solution is probably something like this:

void consolidatedProcedure(SOME_CALLBACK_TYPE callback){
    auto some_value = m_class_member_1.function();
    if(valuePassesTest(some_value)){
        auto some_other_value = m_class_member_2.function(some_value);
        m_class_member_3.function(some_other_value);
        m_class_member_4.function(some_other_value);
        // changed procedure
        callback(some_other_value);
    }
}

void duplicateProcedure_1(){
    consolidatedProcedure(m_class_member_5.function_1);
}

void duplicateProcedure_2(){
    consolidatedProcedure(m_class_member_5.function_2);
}

You say that you're looking for a general solution for these types of situations. The problem is, the more general the question is, the more general the answer is. The general solution is to pass around objects and/or functions as parameters, or to use templates. If you want a more specific answer, you'll probably need a more specific question.

Sophie Swett
  • 1,349
1

You will be able to avoid code duplication by using a common function that takes a lambda function as an argument.

void duplicateProcedure_1(){
   commonProcedure([&m_class_member_5](some_other_value_type some_other_value)
                   {m_class_member_5.function_1(some_other_value);});
    }
}

void duplicateProcedure_2(){
   commonProcedure([&m_class_member_5](some_other_value_type some_other_value)
                   {m_class_member_5.function_2(some_other_value);});
    }
}

private:

template <typename F>
void commonProcedure(F f){
    auto some_value = m_class_member_1.function();
    if(valuePassesTest(some_value)){
        auto some_other_value = m_class_member_2.function(some_value);
        m_class_member_3.function(some_other_value);
        m_class_member_4.function(some_other_value);
        f(some_other_value);
    }
}

However, I understand that this is illustrative code, not necessarily the production code. If the production code is more involved, it's quite likely that you will lose readability. The cost of lost readability might be more than the cost of duplicated lines of code.

If commonProcedure will be used by 5 or more procedures, I would go with the above solution.

If commonProcedure will be used by 3 or fewer procedures, I would go with the duplicated code.

If commonProcedure will be used by exactly 4 procedures, you have weigh the cost and benefits of the approaches for your situation and make a judgement call.

R Sahu
  • 2,016