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 ?
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'.
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
-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 ... )
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
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
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.
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?
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.
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.
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.
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
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.
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.
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.
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 ...
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().
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
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?
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..
wow. That's ... technically very clever.
However, my brain exploded the first 10 times I tried to read and
understand the code
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().
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
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
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
Or are you showing off your vb.net / c# skills
Java?
come on guys... I did made the effort to use 'method/constructor/end', not to mention adding 'undo' for throw statement
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