Tuesday 29 April 2014

Design pattern - Builder design pattern

1. Introduction
Builder design pattern is one of four creation design patterns. For more details about it please read the chapter in [1]. As shown in Figure 1 it resides between Product and Director. Director is to call buildPart() and then call getResult() to retrieve a reference/pointer to product.

                                                   Figure 1 [2]
             
Depend on how complex the application is and decide if both abstract and concrete builder layers are needed.
Builder design pattern is usually come to play when the product is quite complex (a lot of components to configure) and has a lot of options to set, which could lead to a lot of constructors that need to implement based on the possible combinations of options. (However factory method pattern is suitable for classes with under handful of constructors.)

Builder design pattern has been proven quite useful in scientific computing from my personal experience, where some solvers or systems/problems could have a list of parameters to configure. Builder design pattern can not only be used for object creation of such classes but also be very good fit for implementing unit test for such classes.

2. Used for object creation of final class (before C++11)
Before C++11 the constructor of a final class has to be declared as "private" in order to force this class not to be inheritable. Therefore some creation techniques need to be employed to create the instance of final class. Builder design pattern provides as one of solutions along with factory method pattern and friend standalone creation method. Simply declaring builder class as the friend of the final class will solve this problem. More details please see my other blog entry, http://cpluspluslearning-petert.blogspot.co.uk/2014/04/final-non-inheritable-class.html.

Note: C++11 introduced a class attribute "final" to solve this problem gracefully.

3. Replacement for telescoping constructor
This has been proved quite a case in scientific computing. Some solvers can have a list of parameters to configure. It is quite common to have more than a dozen of parameters for solvers. For instance, fmincon solver in Matlab has more than 30 solution parameters [4]. Therefore it is impossible to telescope all the combinations of constructors. Builder design pattern is a very good solution for this kind of problem.

Telescoping constructor: example solver with 12 options
//********************************************************************************
class Solver {
public:
    // telescoping constructor
    Solver(); // take default options
    Solver(double op1); // override option_1 and take the rest as default
    Solver(double op2); // override option_1 and option_2 and take the rest as default 
    // more combinations of arguments
    Solver(double op1;
           double op2;
           double op3;
           double op4;
           int op5;
           int op6;
           int op7;
           int op8;
           bool op9;
           bool op10;
           bool op11;
           bool op12); // override all options
 
private:
    void InitOptions() {
        // set the default options
    }
    // Configuration: solver's parameters/options
    double option_1;
    double option_2;
    double option_3;
    double option_4;
    int option_5;
    int option_6;
    int option_7;
    int option_8;
    bool option_9;
    bool option_10;
    bool option_11;
    bool option_12;
};
//********************************************************************************

The total number of combination [5]: C(12, 0) + C(12, 1) + C(12, 2) + ... + C(12, 12) = 2^12 = 4096. So given a list of n options, then the total number of combinations will be
   sigma(C(n, k)) , where k bounded to [0, n]
   which is eqaul to 2^n. 
Of course this is the worse case with the assumption that all the options are independent. Normally not this bad. Even the dependency of options is considered, the combination is still unbearable to enumerate all the constructors. Here builder design pattern is the right one for this issue.

Solution with builder design pattern
//********************************************************************************
class Solver {
    // sole constructor
    Solver(double op1;
           double op2;
           double op3;
           double op4;
           int op5;
           int op6;
           int op7;
           int op8;
           bool op9;
           bool op10;
           bool op11;
           bool op12); // override all options
 
private:
    // Configuration: solver's parameters/options
    double option_1;
    double option_2;
    double option_3;
    double option_4;
    int option_5;
    int option_6;
    int option_7;
    int option_8;
    bool option_9;
    bool option_10;
    bool option_11;
    bool option_12;
};

class SolverBuilder{
public:
    SolverBuilder() {
        InitOptions();
    }
    
    SolverBuilder& SetOption1(double op1) {
        op_1 = op1;
        return *this;
    }
    // ......
    void SetOption12(bool op12) {
        op_12 = op12;
        return *this;
    }

    Solver* CreateInstance() {
        return new Solver(op_1, op_2, op_3, op_4, op_5, op_6,
                          op_7, op_8, op_9, op_10, op_11, op_12);
    }
private:
    void InitOptions() {
        // set the default options
    }
    // Configuration: solver's parameters/options
    double op_1;
    double op_2;
    double op_3;
    double op_4;
    int op_5;
    int op_6;
    int op_7;
    int op_8;
    bool op_9;
    bool op_10;
    bool op_11;
    bool op_12;
};

void foo () {
    SolverBuilder sb;
    Solver* solverWithDefaultOps = sb.CreateInstance();
    Slover* solverOverrideOp1and12 = sb.SetOption1(1.0).SetOption12(true).CreateInstance();

    //......
}
//********************************************************************************

Here I have demonstrated the power of builder design pattern as an excellent replacement of telescoping constructors. Simply there is only one constructor needed and the default options are enforced in the builder.
In C++11 the solution is even more beautiful, as default value is directly supported. Read my other blog entry for more details, http://cpluspluslearning-petert.blogspot.co.uk/2014/03/c11-features-definitely-worth-learning_20.html.

C++11 solution: default value supported
//******************************************************************************
class SolverBuilder{
public:
    // use default constructor

    SolverBuilder& SetOption1(double op1) {
        op_1 = op1;
        return *this;
    }
    // ......
    SolverBuilder& SetOption12(bool op12) {
        op_12 = op12;
        return *this;
    }

    Solver* CreateInstance() {
        return new Solver(op_1, op_2, op_3, op_4, op_5, op_6,
                          op_7, op_8, op_9, op_10, op_11, op_12);
    }
private:
    // Configuration: solver's parameters/options
    double op_1 = 0.0;
    double op_2 = 0.0;
    double op_3 = 0.0;
    double op_4 = 0.0;
    int op_5 = 0;
    int op_6 = 0;
    int op_7 = 0;
    int op_8 = 0;
    bool op_9 = false;
    bool op_10 = false;
    bool op_11 = false;
    bool op_12 = false;
};
//******************************************************************************

4. Used for object creation of class together with UI
This is the same user case as last one (Section 3). Here I would like to emphasize this because this kind of applications are existing everywhere from daily life to daily work. For instance buying a flight ticket or creating C++ project in Microsoft Visual Studio. Life is getting so simple now by clicking the buttons and select (scroll down) the options.
Builder design pattern can be a very good solution for this kind of application. Each UI is used to present available choices of each options to the users. As long as selected, call function from builder to set the option. Then advance to the next option. When all options are set (with "Confrim" button in GUI or "Yes" in command line), then call function to create the instance from builder. As follows,

->UI for Option 1            ->UI for option 2       -> ... ->UI for all options to confirm
    ->Select choice                ->Select choice                    ->Yes
        ->Set option on builder   -> Set option on builder       ->Create instance from builder

In this application a builder class can always sits behind the UI and take the option from user's choice. When options are confirmed, then create the object and return. I am not a UI design expert but builder design pattern has been widely used by them after consulting with my colleagues in UI team.

5. Better unit tests implementation
I find builder design pattern is extremely useful to indicate what test case is carried out and how this test function body is to be implemented, especially fit/useful against some code that is not very tested. Here is one example that  utilize builder design pattern to build unit tests.

Builder design pattern: unit test
//********************************************************************************
class Solver {
    // sole constructor
    Solver(double op1;
           double op2;
           double op3;
           double op4;
           int op5;
           int op6;
           int op7;
           int op8;
           bool op9;
           bool op10;
           bool op11;
           bool op12); // override all options
    bool Solve(double, double); // implement the unit test
 
private:
    // Configuration: solver's parameters/options
    double option_1;
    double option_2;
    double option_3;
    double option_4;
    int option_5;
    int option_6;
    int option_7;
    int option_8;
    bool option_9;
    bool option_10;
    bool option_11;
    bool option_12;
};

class SolverUnitTestBuilder : public CppUnit::TestFixture
{
public:  
    SolverUnitTestBuilder& SetOption1(double op1) {
        op_1 = op1;
        return *this;
    }
    // ......
    SolverUnitTestBuilder& SetOption12(bool op12) {
        op_12 = op12;
        return *this;
    }
 
    std::unique_ptr<Solver*> CreateInstance() {
        return std::unique_ptr<slover*>(
        new Solver(op_1, op_2, op_3, op_4, op_5, op_6,
                            op_7, op_8, op_9, op_10, op_11, op_12));
    }

protected:
    // Op1 = 3.0
    // Op2 = 4.0
    // Op12 = 0 (false)
    // Test function Solve(0, 0)
    // Expected it return 1 (true)
    void TestSolveGivenOp1_3_Op2_4_Op12_0_Against_0_0_Expected_1() {
        CPPUNIT_ASSERT(
        (*this).SetOption1(3).SetOption2(4).SetOption12(false).CreateInstance()->Solve(0,0));
    }
private:
    CPPUNIT_TEST_SUITE(TestSolveGivenOp1_3_Op2_4_Op12_0_Against_0_0_Expected_1);
};
//********************************************************************************

Builder design pattern is used here to write self-documented code. The other programmers just need to read the function names in the header file and know what this test function is to test. Something is even better when coming to the implementation. Simply read the function name, set the correct option, create the instance, call the function and test against the expected value.
It removes the ambiguity or errors on the comments about what the code will test and how the test is setup. And the programmer does not need to switch between the header file and the function body to check what actually this test is to test against. This is especially useful when the code is under test and the team is to catch up with testing by adding a couple tests more each time when they are touching the code. This is a very good self-documented programming to tell other programmers what have and have not tested. This approach is very helpful for better communication, easy implementation and simply code reviewing.
    - Testing function naming: test function+test setting+test function argument+expected behavior
    - Implementation by using builder design pattern

Note: there is a defect of this method when coming to naming the test method. The value of option involves fractions, for instance op1 = 1.1, because C++'s function name can't have "." in naming. In my case I use "1_dot_1" for naming to represent "1.1". The naming convention is a big issue for every team. Rule is simple. Define rules for all. Or following others good examples, like Google C++ coding standard [4].

Bibliography:
[1] GOF, Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, "Design Patterns - Elements of Reusable Object-Oriented Software", 1994
[2] http://en.wikipedia.org/wiki/Builder_pattern
[3] Google C++ coding standard, http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml
[4] http://www.mathworks.co.uk/help/optim/ug/optimoptions.html
[5] Donald E. Knuth "The Art of Computer Programming", Volume 2, Fundamental Algorithm, Third Edition

No comments:

Post a Comment