fluent style

Posted by jmls on 02-Jul-2011 14:05

For all of you using the "fluent" way of writing objects, any guidelines on what makes good fluent-style names ?

for example, here is some fluent coding examples of an sdk for telephony - does this make sense to a developer wanting to integrate this into their product ?

using dotr.hash42.Model.*.

def var MyCall as PhoneCall no-undo.

MyCall = (new PhoneCall())

            :From("1234")

            :To("4321")

            :Dial().

MyCall = (new PhoneCall())

            :From(new PersonModel:Get("jmls"))

            :To(new PersonModel:Get("fred"))

            :Dial().

MyCall = (new PhoneCall())

            :From(new PersonModel:Get("jmls"))

            :To(new PersonModel:Get("fred"))

            :WithCallerID("+441702444711")

            :Dial().

the "normal" way is thus

using dotr.hash42.Model.*.

def var MyCall as PhoneCall no-undo.

MyCall = new PhoneCall().

assign Mycall:FromExtension = "1234"

       Mycall:ToExtension   = "1234".

MyCall:Dial().

MyCall= new PhoneCall().

assign MyCall:FromPerson = new PersonModel:Get("jmls")

       MyCall:ToPerson   = new PersonModel:Get("fred").

MyCall:Dial().

I "like" the fluent version because the From and To are methods which can be overloaded (char extension or PersonModel) but retain the same "Name". I'm just not sure about the naming conventions.

Would SetFrom("1234") be more readable or understandable ?

All Replies

Posted by Admin on 05-Jul-2011 02:55

As much as I like 'fluent' style myself it looks to me that you should consider to have the mandatory properties set in constructor ... I won't go as far as defining all setters to return the same object instance just to be able to use 'fluent style'.

Posted by Peter Judge on 05-Jul-2011 08:01

marianedu wrote:

As much as I like 'fluent' style myself it looks to me that you should consider to have the mandatory properties set in constructor ...

Amen. Like. +1. Good. Yes.

I really like this approach - mandatory info in constructors rather than optionally through property setters. I sometimes wish that an interface would allow you to specify the constructor signature for precisely this reason: what I end up doing is to define the properties as PUBLIC ... GET. (ie no SET in the interface) and have a PRIVATE SET in the implementing class, which is set via the constructor.

-- peter

Posted by jmls on 05-Jul-2011 08:36

-1. I don't like constructor parameters for one reason - you don't get

no help in OEA about what the parameters are. Very confusing. At

least with method names (fluent) you get that.

However, as long as there is one constructor (from,to) (or is it

to,from ?) - see what I mean ... )

Posted by Peter Judge on 05-Jul-2011 08:45

I "like" the fluent version because the From and To are methods which can be overloaded (char extension or PersonModel) but retain the same "Name".

Me too. However. I would say that if you cannot explicitly determine the input parameter Type-as-in-Entity from the signature, you should specify that in the name. For example, From(PersonModel) or From(ExtensionModel) are clear in what they expect; From(character) is not especially. I'd think about changing it to FromExtension(character) instead.

 I'm just not sure about the naming conventions.

Indeed. I think the names/design of a fluent API differs from a 'normal' API; the intent of the fluent interface is that it, um. flow, whereas the normal approach doesn't care about that.

Some reading at http://stackoverflow.com/questions/2476265/method-names-with-fluent-interface and http://martinfowler.com/bliki/FluentInterface.html .

Would SetFrom("1234") be more readable or understandable ?

No, I don't think so. The addition of "Set" doesn't add anything; as with property names IMO it's not necessary in ABL.

-- peter

Posted by Peter Judge on 05-Jul-2011 10:29

However, as long as there is one constructor (from,to) (or is it

to,from ?) - see what I mean ... )

Fair enough. But at least you *know* that a parameter has been set. There's no way of knowing wheter a property has been set, nor any way of enforcing that.

Now you might have a fluent API that enforces the setting of an object's properties, but there's no guarantee that a developer will actually use that API.

-1. I don't like constructor parameters for one reason - you don't get

no help in OEA about what the parameters are. Very confusing.  At

least with method names (fluent) you get that.

A bug in the tool shouldn't dissuade you from the right design.

-- peter

Posted by Thomas Mercer-Hursh on 05-Jul-2011 11:27

However .... there is a school of thought which suggests that one should not build objects which potentially contain invalid data.  If null is invalid for a given property, this suggests that one should set it via the constructor so that at no time in the object's life is it in an invalid state.  I would put such design considerations above the convenience of the IDE UI.

Posted by Admin on 05-Jul-2011 12:46

A bug in the tool shouldn't dissuade you from the right design.

Missing Code completion for parameters a bug? Is that the official PSC view on this?

So, may we assume, that get's fixed in 10.2B05?

Posted by Thomas Mercer-Hursh on 05-Jul-2011 12:48

A bug in the tool shouldn't dissuade you from the right design.

Amen.

A request for auto-completion of method signatures was made at the Info Exchange at PCA.  I think I recall seeing this demod in an editor for C or something in 1985.  Yes, it would be nice.

But, one can also help oneself.  E.g., If I had a constructor or method which had a from parameter and a to parameter, I would always use them in that order.

Posted by Admin on 05-Jul-2011 13:06

state. I would put such design considerations above the convenience of the IDE UI.

Agreed. Especially since cut and paste from the class browser is a practical workaround.

Shouldn't limit the urgency of fixing what Peter classified as a bug though.

Posted by Admin on 05-Jul-2011 13:31

But, one can also help oneself.  E.g., If I had a constructor or method which had a from parameter and a to parameter, I would always use them in that order.

If it's as simple, that there is only a single From-To pair. How about FromOrderDate, ToOrderDate, FromDeliveryDate, ToDeliveryDate?

Yes, you could always say that the order comes before delivery - but there are other cases where this might not be that simple.

Posted by Peter Judge on 05-Jul-2011 13:51

mikefe wrote:

A bug in the tool shouldn't dissuade you from the right design.

Missing Code completion for parameters a bug? Is that the official PSC view on this?

So, may we assume, that get's fixed in 10.2B05?

I can't find a bug that exactly describes this behaviour, so probably not as things stand today.

-- peter

Posted by Thomas Mercer-Hursh on 05-Jul-2011 13:57

To be sure, one can have very complex signatures and those are exactly the place that an improvement in the IDE would be most helpful.  My point was just that often the signatures aren't that complex and that a combination of natural order and developing standards can make the problem easier.

Posted by jmls on 06-Jul-2011 00:31

so adding extra constructors now give us this

using dotr.hash42.Model.*.

def var MyCall as PhoneCall no-undo.

MyCall = (new PhoneCall("1234","4321))

         :Dial().

MyCall = (new PhoneCall(new PersonModel:Get("jmls"),

                     new PersonModel:Get("fred"))

      :Dial().

MyCall = (new PhoneCall(new PersonModel:Get("jmls"),

                     new PersonModel:Get("fred"))

      :WithCallerID("+441702444711")

      :Dial().

this is my problem : Unless you *know* the api when reading this code, am I calling 1234 from 4321, or am I calling 4321 from 1234 ? Yeah, I can add comments in the code explaining what this is doing, which was not required with original snippet because the code was self-explanatory.

Posted by Admin on 06-Jul-2011 00:46

Yeah, I can add comments in the code explaining what this is doing, which was not required with original snippet because the code was self-explanatory.

Comments would be my solution here. And for me, comments are part of the code, so it's still self-explaining.

Posted by jmls on 06-Jul-2011 01:11

Looks like I am on the losing side here

It still doesn't feel right to me. I like to the look of the code

(sans constructor parameters) - it seems more intuitive to me .

But then, I'm just a small fish swimming against the school. Perhaps I

need to turn around and swim with the school.

But not yet ...

Posted by Admin on 06-Jul-2011 01:26

Looks like I am on the losing side here

not really, just that having valid state in an object (at all time) is something you don't think you argue with

But then, I'm just a small fish swimming against the school. Perhaps I

need to turn around and swim with the school.

But not yet ...

ah, swimming in the opposite direction... something we all like to experiment (some more than others)

what  you can try is to add a phone call factory and then use methods instead  of constructors... you might find a more self-explanatory names for the  methods

MyCall = Operator:CallFromTo("1234", "4321"):Dial().

Posted by Peter Judge on 06-Jul-2011 07:40

marianedu wrote:

Looks like I am on the losing side here

not really, just that having valid state in an object (at all time) is something you don't think you argue with

But then, I'm just a small fish swimming against the school. Perhaps I

need to turn around and swim with the school.

But not yet ...

ah, swimming in the opposite direction... something we all like to experiment (some more than others)

what  you can try is to add a phone call factory and then use methods instead  of constructors... you might find a more self-explanatory names for the  methods

MyCall = Operator:CallFromTo("1234", "4321"):Dial().


I'm a fan of using the constructor to enforce mandatory properties. I'm also a fan of the fluent approach, for it's readability. But there are times when these approaches can be at odds with each other.

I would think that a call needs a single "From" and at least one "To". That tells me there are 2 constructors: PhoneCall(PersonModel) and PhoneCall(charExtension), and potentially only the first one (since I'd guess that the extension is on the PersonModel or can be derived from there). But it's not also out of the realm of possibility to use the default constructor (as Julian's done) and have that be perfectly OK.

The Dial() method in this fluent style would obviously need to validate the state of the PhoneCall instance before proceeding, whereas in the more traditional way of building objects, it might not have to.

-- peter

Posted by Thomas Mercer-Hursh on 06-Jul-2011 11:22

new PhoneCall("1234","4321)

may not tell you much ... other than knowing that you always have from first

but what about

new PhoneCall(inFromNumber, inToNumber)

??

You might argue that your other forms show examples of getting the numbers direct from the appropriate source, but I am wondering about the nested news.  Isn't there a need to localize the reference.  E.g.

Caller = new Person(SomeKey).

Callee = new Person(OtherKey).

MyCall = new PhoneCall(Caller:Phone(), Callee:Phone()).

Doesn't seem to need comments at all and if you are going to make more than one reference to the Caller and Callee objects, why new them in-line?

Posted by cwills on 27-Jul-2011 23:47

There is an alternative which gives you the best of both worlds, but it's a little more work.

You can control the path through your fluent API using interfaces (the syntax). This way, the required properties can be set before allowing the dependent methods to be executed.

For a simple example, using a similar phone call fluent API above, you could have two (or more) interfaces INotDialable and IDialable :

INTERFACE INotDialable:

 

  METHOD PUBLIC IDialable To(INPUT num AS CHARACTER).

 

END INTERFACE.

----------------------------------------------------------------------------------------------------

INTERFACE IDialable:

 

  METHOD PUBLIC IDialable WithCallerID(INPUT id AS CHARACTER).

METHOD PUBLIC IDialable Dial(). 

 

END INTERFACE.

----------------------------------------------------------------------------------------------------

CLASS PhoneCall IMPLEMENTS INotDialable, IDialable:

 

  CONSTRUCTOR PRIVATE PhoneCall():

  END CONSTRUCTOR.

 

  METHOD PUBLIC STATIC INotDialable New():

    RETURN NEW PhoneCall().

  END METHOD. 


  /* ... Implement IDialable and INotDialable interfaces here */

 

END CLASS.

----------------------------------------------------------------------------------------------------

And then the valid usage would look like this:

PhoneCall:New()

  :To("1234")

  :Dial().

Where as this would give a compile time error:

PhoneCall:New()

  :Dial().

Just my two cents..

Posted by jmls on 28-Jul-2011 01:26

wow. That's ... technically very clever.

However, my brain exploded the first 10 times I tried to read and

understand the code

Posted by Admin on 28-Jul-2011 02:35

wow. That's ... technically very clever.

right said... technically, still not exactly the most appropriate use of interfaces imho

However, my brain exploded the first 10 times I tried to read and

understand the code

mine too, just imagine how many having to deal with that code later will get through the same experience

I've already comment that in that case my choice will be to go with a factory and if you think about it there can't be any PhoneCall object with some underlying infrastructure to actually make the connection... so what ever the infrastracture (let's call it simply PBX) this should be a factory for the PhoneCall.

class PBX:

     method public static PhoneCall openCall (toNumber as character):

        if not validNumber(toNumber) then

             undo, throw new exception('invalid number').

        return new PhoneCall(this, toNumber).

     end.

     method public static void dial (phoneCall as PhoneCall):

          // TODO

     end method.

     method public static logical validNumber (phoneNo as character):

          //TODO

     end method.

end class.

class PhoneCall:

     define private variable pbx      as PBX       no-undo.

     define private variable toNumber as character no-undo.

     define private variable callerId as character no-undo.

     constructor PhoneCall (pbx as PBX, toNumber as character):

          if not valid-object(pbx) then

               undo, throw new exception().

          assign

               this-object:pbx = pbx

               this-object:toNumber = toNumber.

     end constructor.

     method public PhoneCall withCallerId (callerId as character):

          this-object:callerId = callerId.

          return this-object.

     end method.

     method public void dial ():

          this-object:pbx:dial(this-object).

     end method.

end class.

and then use it fluently as you like most

pbx:openCall('to'):withCallerId('id'):dial().

Posted by jmls on 28-Jul-2011 02:46

isn't there some issues with this ?

1) You can't use this-object within a static method or property

2) the dial() method is static, so you don't need to be passing the pbx around

I do, however, agree with and understand the principles that you are

trying to explain.

Thanks

Posted by Admin on 28-Jul-2011 02:55

jmls wrote:

isn't there some issues with this ?

1) You can't use this-object within a static method or property

2) the dial() method is static, so you don't need to be passing the pbx around

I do, however, agree with and understand the principles that you are

trying to explain.

Thanks

well, coding in a web editor isn't any better than doing it in the email client

Posted by jmls on 28-Jul-2011 03:11

lol

I just thought that you had discovered some secret keyword that worked

in statics

"this"

Or are you showing off your vb.net / c# skills

Julian

Posted by Admin on 28-Jul-2011 03:16

Or are you showing off your vb.net / c# skills

 

Java?

Posted by Admin on 28-Jul-2011 03:22

come on guys... I did made the effort to use 'method/constructor/end', not to mention adding 'undo' for throw statement

Posted by Peter Judge on 28-Jul-2011 07:51

It seems to me that there are 2 concepts in play here - the phone call, and the dialling thereof - and these should be different functions/objects. The PhoneCall object can be constructed in a fluent way, without (any/many) requirements of order.

The pbx:MakeCall() method is then responsible for calling poPhoneCall:Validate() or whatever.

pbx:MakeCall(new PhoneCall():To('1234'):From('6789'):Spoofing('2211')).

-- peter

This thread is closed