Construction Time as Fluent Pattern

N

Nephi Immortal

Last time, I posted the topic -- Read / Write Pattern Design. I did
a lot of research trying to find a good solution, but I could not find
anything except I found one website below:

http://en.wikipedia.org/wiki/Fluent_interface

It is called Fluent Pattern. It is the best solution I can use.
Looking at the website’s source code in C++. It does not make any
sense to me. How can mutable class using fluent pattern modify
immutable object?

I wrote my own code. It looks better now. I created Digits class.
It is read only such as constant. It has only getters and it does not
have any setters. It can either be mutable or immutable unless
Fluent_Digits class is a friend with Digits class.

Fluent_Digits has setters at construction-time only.

Please answer my question. Is const_cast safe to be used or will it
lead to undefined behavior? const is an optional, but it is not
necessary because Digits class does not have setters.

Please express your opinion what you think about my code and fluent
pattern.

class Digits
{
private:
enum
{
Digit_Zero,
Digit_One,
Digit_Two,
Digit_Three,
Digit_Four,
Digit_Five,
Digit_Six,
Digit_Seven,
Digit_Eight,
Digit_Nine
};

char digits[ 10 ];

public:
friend class Fluent_Digits;

Digits() {}

char Get_Digit_Zero() const { return digits[ Digit_Zero ]; }
char Get_Digit_One() const { return digits[ Digit_One ]; }
char Get_Digit_Two() const { return digits[ Digit_Two ]; }
char Get_Digit_Three() const { return digits[ Digit_Three ]; }
char Get_Digit_Four() const { return digits[ Digit_Four ]; }
char Get_Digit_Five() const { return digits[ Digit_Five ]; }
char Get_Digit_Six() const { return digits[ Digit_Six ]; }
char Get_Digit_Seven() const { return digits[ Digit_Seven ]; }
char Get_Digit_Eight() const { return digits[ Digit_Eight ]; }
char Get_Digit_Nine() const { return digits[ Digit_Nine ]; }

int Begin() const { return 0; }
int End() const { return 9; }

char operator[]( int index ) const
{
if( 0 <= index && index <= 9 )
return digits[ index ];
else
return 0xFF; // Invalid
}
};


class Fluent_Digits
{
private:
Digits& _digits;

public:
Fluent_Digits( const Digits& digits )
: _digits( const_cast< Digits& >( digits ))
{
}

Fluent_Digits& with_Digit_Zero( char digit_zero = '0' )
{
_digits.digits[ _digits.Digit_Zero ] = digit_zero;
return *this;
}

Fluent_Digits& with_Digit_One( char digit_one = '1' )
{
_digits.digits[ _digits.Digit_One ] = digit_one;
return *this;
}

Fluent_Digits& with_Digit_Two( char digit_two = '2' )
{
_digits.digits[ _digits.Digit_Two ] = digit_two;
return *this;
}

Fluent_Digits& with_Digit_Three( char digit_three = '3' )
{
_digits.digits[ _digits.Digit_Three ] = digit_three;
return *this;
}

Fluent_Digits& with_Digit_Four( char digit_four = '4' )
{
_digits.digits[ _digits.Digit_Four ] = digit_four;
return *this;
}

Fluent_Digits& with_Digit_Five( char digit_five = '5' )
{
_digits.digits[ _digits.Digit_Five ] = digit_five;
return *this;
}

Fluent_Digits& with_Digit_Six( char digit_six = '6' )
{
_digits.digits[ _digits.Digit_Six ] = digit_six;
return *this;
}

Fluent_Digits& with_Digit_Seven( char digit_seven = '7' )
{
_digits.digits[ _digits.Digit_Seven ] = digit_seven;
return *this;
}

Fluent_Digits& with_Digit_Eight( char digit_eight = '8' )
{
_digits.digits[ _digits.Digit_Eight ] = digit_eight;
return *this;
}

Fluent_Digits& with_Digit_Nine( char digit_nine = '9' )
{
_digits.digits[ _digits.Digit_Nine ] = digit_nine;
return *this;
}

void Create()
{
// Nothing
}
};

int main()
{
const Digits digits_english;
const Digits digits_other;

Fluent_Digits( digits_english )
.with_Digit_Zero ()
.with_Digit_One ()
.with_Digit_Two ()
.with_Digit_Three()
.with_Digit_Four ()
.with_Digit_Five ()
.with_Digit_Six ()
.with_Digit_Seven()
.with_Digit_Eight()
.with_Digit_Nine ()
.Create();

Fluent_Digits( digits_other )
.with_Digit_Zero ( 0xB0 )
.with_Digit_One ( 0xB1 )
.with_Digit_Two ( 0xB2 )
.with_Digit_Three( 0xB3 )
.with_Digit_Four ( 0xB4 )
.with_Digit_Five ( 0xB5 )
.with_Digit_Six ( 0xB6 )
.with_Digit_Seven( 0xB7 )
.with_Digit_Eight( 0xB8 )
.with_Digit_Nine ( 0xB9 )
.Create();

for( int i = digits_english.Begin(); i <= digits_english.End(); ++i )
cout << digits_english[ i ] << " ";

cout << endl;

for( int i = digits_other.Begin(); i <= digits_other.End(); ++i )
cout << digits_other[ i ] << " ";

cout << endl;

return 0;
}
 
G

Goran

        Please express your opinion what you think about my code and fluent
pattern.

What Wikipedia said:

"This style is marginally beneficial in readability due to its ability
to provide a more fluid feel to the code".

Otherwise, it's your second post "digits" class. Is spending so much
time on it really the best use of your time? And, did you try using
"const" for immutability? That normally works well in C++.

Goran.
 
J

Jorgen Grahn

What Wikipedia said:

"This style is marginally beneficial in readability due to its ability
to provide a more fluid feel to the code".

I didn't read it carefully enough to really get what they meant; I
just saw the

meeting_time = five.to(six_thirty); // paraphrased from memory

But I strongly believe this: it is time well spent to design your
concrete classes, helper functions etc so that the main "logic" code
flow can be short, elegant and readable.

Perhaps Fowler et al just gave a new name to something old? Stroustrup
designed C++ to be able to "work on a higher level of abstraction".
Doesn't that boil down to the same thing? -- to minimize the
conceptual gap between the problem and the solution.

/Jorgen
 
N

Nephi Immortal

What Wikipedia said:

"This style is marginally beneficial in readability due to its ability
to provide a more fluid feel to the code".

Otherwise, it's your second post "digits" class. Is spending so much
time on it really the best use of your time? And, did you try using
"const" for immutability? That normally works well in C++.

You are correct. Fluent pattern is ideal for code readability. I
don’t care if I waste a lot of time in couple hours, couple months, or
couple years in writing a very long code with hundreds or thousands of
functions. It is more important that code readability can improve
flexibility.
The client simply calls default constructor and does not need to use
setters during the initialization as long as default constructor does
the initialization itself for the client. I have not created default
constructor with initialization yet, but you know what I mean.
Digits class can turn into list of foreign language choice. If the
client chooses digits_other instead of digits_english, they can simply
change the pointer to access different digits array in memory.
Changing pointer to digits array in memory does not affect to change
the client’s code.
If digits_array is customized allowing the client to choose different
character sets through Digits class’ setter functions.
Same example, you want to change the fonts as far as data is not
affected to be modified.
 
P

Pavel

Nephi Immortal wrote:
....
Please answer my question. Is const_cast safe to be used or will it
lead to undefined behavior? const is an optional, but it is not
necessary because Digits class does not have setters.

Please express your opinion what you think about my code and fluent
pattern.
....

For const_cast issue: I am not sure if your example code is well representative
of your intended use; if it is, you could avoid const-cast by having a
modifiable initializer and then defining your constant digit object, something like:

class DigitsConfig {.../* your fluent pattern here */};
class Digits { public: Digits(const DigitsConfig &); ... };
const Digits english(DigitsConfig().withZero().withOne()/*...*/.endOfConfig());
const Digits others(DigitsConfig().withZero(0xBO).withOne(0xB2)
/*...*/.endOfConfig());

From my reading of the standard, your const_cast shall not cause UB.

From practical perspective, I never know with what my code will be debugged /
coverage tested etc; thus I prefer having short statements, one per line, even
if does not look so sophisticated; not every debugger / coverage analyzer will
nicely stop at individual calls in a chain.

Also, (this is not to criticize fluent pattern in general but specifically wrt
initialization/configuration), I prefer initializing from data aggregates rather
than with method calls (chained or not). Somehow it feels better to emphasize
that there is no code logic here but just building an object from a bunch of
parameters (e.g., call order is not very important etc.). This may require some
more infrastructure, but the basic idea is to either initialize from a long
string or, if parsing overhead is not acceptable, from an array of initializers
(disclaimer: I did not compile this, although I used similar initialization
tricks in practice):

template <unsigned idx>
class DigitDInitializer {
char repr_;
public:
DigitDInitializer(char repr):repr_(repr){}
char repr() const { return repr; }
};

struct DigitConfig {
template<unsigned idx>
DigitConfig(DigitDInitializer<idx> ddi):
repr_(ddi.repr), idx_(idx) {}
int idx_;
char repr_;
};

Then the client code would become (assuming it's always radix 10, you don't need
to pass the size to Digits).

const struct DigitConfig englishDigitConfig[] = {
DigitDInitializer<0>('0'), // you could typedef those specializations
/*...*/
DigitDInitializer<9>('9') // to Digit0, Digit1 etc if you like
};
Digits englishDigits(englishDigitConfig);

const struct DigitConfig otherDigitConfig[] = {
DigitDInitializer<0>(0xB0),
/*...*/
DigitDInitializer<9>(0xB9)
};
Digits otherDigits(otherDigitConfig);

Heterogeneous initializers would look same in client code but implementation
could be trickier than simply assigning character at index (e.g. they could
store pointer-to-members in DigitConfig or even simple switch on enum).

HTH
-Pavel

PS. IMHO big part of the issue is C++'s (including C++11) lack of powerful
enough syntax for aggregate literals. Even some C99-ish syntax like compound
literals and designated initializers would make ninety percent of this wheel
re-inventing unnecessary. But obviously lambdas were way more important...

class Digits
{
private:
enum
{
Digit_Zero,
Digit_One,
Digit_Two,
Digit_Three,
Digit_Four,
Digit_Five,
Digit_Six,
Digit_Seven,
Digit_Eight,
Digit_Nine
};

char digits[ 10 ];

public:
friend class Fluent_Digits;

Digits() {}

char Get_Digit_Zero() const { return digits[ Digit_Zero ]; }
char Get_Digit_One() const { return digits[ Digit_One ]; }
char Get_Digit_Two() const { return digits[ Digit_Two ]; }
char Get_Digit_Three() const { return digits[ Digit_Three ]; }
char Get_Digit_Four() const { return digits[ Digit_Four ]; }
char Get_Digit_Five() const { return digits[ Digit_Five ]; }
char Get_Digit_Six() const { return digits[ Digit_Six ]; }
char Get_Digit_Seven() const { return digits[ Digit_Seven ]; }
char Get_Digit_Eight() const { return digits[ Digit_Eight ]; }
char Get_Digit_Nine() const { return digits[ Digit_Nine ]; }

int Begin() const { return 0; }
int End() const { return 9; }

char operator[]( int index ) const
{
if( 0<= index&& index<= 9 )
return digits[ index ];
else
return 0xFF; // Invalid
}
};


class Fluent_Digits
{
private:
Digits& _digits;

public:
Fluent_Digits( const Digits& digits )
: _digits( const_cast< Digits& >( digits ))
{
}

Fluent_Digits& with_Digit_Zero( char digit_zero = '0' )
{
_digits.digits[ _digits.Digit_Zero ] = digit_zero;
return *this;
}

Fluent_Digits& with_Digit_One( char digit_one = '1' )
{
_digits.digits[ _digits.Digit_One ] = digit_one;
return *this;
}

Fluent_Digits& with_Digit_Two( char digit_two = '2' )
{
_digits.digits[ _digits.Digit_Two ] = digit_two;
return *this;
}

Fluent_Digits& with_Digit_Three( char digit_three = '3' )
{
_digits.digits[ _digits.Digit_Three ] = digit_three;
return *this;
}

Fluent_Digits& with_Digit_Four( char digit_four = '4' )
{
_digits.digits[ _digits.Digit_Four ] = digit_four;
return *this;
}

Fluent_Digits& with_Digit_Five( char digit_five = '5' )
{
_digits.digits[ _digits.Digit_Five ] = digit_five;
return *this;
}

Fluent_Digits& with_Digit_Six( char digit_six = '6' )
{
_digits.digits[ _digits.Digit_Six ] = digit_six;
return *this;
}

Fluent_Digits& with_Digit_Seven( char digit_seven = '7' )
{
_digits.digits[ _digits.Digit_Seven ] = digit_seven;
return *this;
}

Fluent_Digits& with_Digit_Eight( char digit_eight = '8' )
{
_digits.digits[ _digits.Digit_Eight ] = digit_eight;
return *this;
}

Fluent_Digits& with_Digit_Nine( char digit_nine = '9' )
{
_digits.digits[ _digits.Digit_Nine ] = digit_nine;
return *this;
}

void Create()
{
// Nothing
}
};

int main()
{
const Digits digits_english;
const Digits digits_other;

Fluent_Digits( digits_english )
.with_Digit_Zero ()
.with_Digit_One ()
.with_Digit_Two ()
.with_Digit_Three()
.with_Digit_Four ()
.with_Digit_Five ()
.with_Digit_Six ()
.with_Digit_Seven()
.with_Digit_Eight()
.with_Digit_Nine ()
.Create();

Fluent_Digits( digits_other )
.with_Digit_Zero ( 0xB0 )
.with_Digit_One ( 0xB1 )
.with_Digit_Two ( 0xB2 )
.with_Digit_Three( 0xB3 )
.with_Digit_Four ( 0xB4 )
.with_Digit_Five ( 0xB5 )
.with_Digit_Six ( 0xB6 )
.with_Digit_Seven( 0xB7 )
.with_Digit_Eight( 0xB8 )
.with_Digit_Nine ( 0xB9 )
.Create();

for( int i = digits_english.Begin(); i<= digits_english.End(); ++i )
cout<< digits_english[ i ]<< " ";

cout<< endl;

for( int i = digits_other.Begin(); i<= digits_other.End(); ++i )
cout<< digits_other[ i ]<< " ";

cout<< endl;

return 0;
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,776
Messages
2,569,603
Members
45,188
Latest member
Crypto TaxSoftware

Latest Threads

Top