Sitting with an awesome view of Seattle ... Last night I spent some time over at Eleutian with Aaron and during our discussions we talked a bit about Fluent Builders. He brought up an interesting idea to me of whether or not builders should be immutable and I think I agree with his mutable version so I am going to provide another way of implementing the Fluent Builders from DDDD 5 [Messages have Fluent Builders].
The basics of the discussion revolve around the methods used for setting fields in the builder. It is best seen in code comparing the builder in the last post of ...
public class EquityQuoteMessageBuilder {
internal readonly ExchangeGateways m_Gateway;
internal readonly string m_Security;
internal readonly DateTime m_Time;
internal readonly decimal m_BidPrice;
internal readonly decimal m_AskPrice;
[DebuggerStepThroughAttribute]
public EquityQuoteMessageBuilder OnGateway(ExchangeGateways _Gateway) {
return new InsertEquityQuoteCommandBuilder(_Gateway,m_Security,m_Time,m_BidPrice,m_AskPrice);
}
[DebuggerStepThroughAttribute]
public EquityQuoteMessageBuilder ForSecurity(string _Symbol) {
return new InsertEquityQuoteCommandBuilder(m_Gateway,_Symbol,m_Time,m_BidPrice,m_AskPrice);
}
[DebuggerStepThroughAttribute]
public EquityQuoteMessageBuilder At(DateTime _Time) {
return new InsertEquityQuoteCommandBuilder(m_Gateway,m_Security,_Time,m_BidPrice,m_AskPrice);
}
[DebuggerStepThroughAttribute]
public EquityQuoteMessageBuilder WithSpreadOf(decimal _BidPrice, decimal _AskPrice) {
return new InsertEquityQuoteCommandBuilder(m_Gateway,m_Security,m_Time,_BidPrice,_AskPrice);
}
private EquityQuoteMessageBuilder(ExchangeGateways _Gateway,string _Symbol,DateTime _Time,decimal _BidPrice,decimal _AskPrice) {
m_Gateway = _Gateway;
m_Security = _Symbol;
m_Time = _Time;
m_BidPrice = _BidPrice;
m_AskPrice = _AskPrice;
}
public EquityQuoteMessageBuilder {}
public static implicit operator EquityQuoteMessage(EquityQuoteMessageBuilder _Builder) {
return new InsertEquityQuoteCommand(_Builder.m_Gateway, _Builder.m_Security, _Builder.m_Time, _Builder.m_BidPrice, _Builder.m_AskPrice);
}
}
With the modified version being ....
public class EquityQuoteMessageBuilder {
internal ExchangeGateways m_Gateway;
internal string m_Security;
internal DateTime m_Time;
internal decimal m_BidPrice;
internal decimal m_AskPrice;
[DebuggerStepThroughAttribute]
public EquityQuoteMessageBuilder OnGateway(ExchangeGateways _Gateway) {
m_Gateway = _Gateway;
return this;
}
[DebuggerStepThroughAttribute]
public EquityQuoteMessageBuilder ForSecurity(string _Symbol) {
m_Symbol = _Symbol;
return this;
}
[DebuggerStepThroughAttribute]
public EquityQuoteMessageBuilder At(DateTime _Time) {
m_Time = _Time;
return this;
}
[DebuggerStepThroughAttribute]
public EquityQuoteMessageBuilder WithSpreadOf(decimal _BidPrice, decimal _AskPrice) {
m_BidPrice = _BidPrice;
m_AskPrice = _AskPrice;
return this;
}
public EquityQuoteMessageBuilder {}
public static implicit operator EquityQuoteMessage(EquityQuoteMessageBuilder _Builder) {
return new InsertEquityQuoteCommand(_Builder.m_Gateway, _Builder.m_Security, _Builder.m_Time, _Builder.m_BidPrice, _Builder.m_AskPrice);
}
}
The main difference between the two implementations of the Fluent Builder being whether or not the builder itself is mutable. This version is much more terse but the question remains for me of ... Do I care? I am generating them ... The one place there is quite a bit of benefit is in terms of performance if this is being called often in production code as it will create many less objects and the JIT could be more intelligent about inlining.
My original reason for making the builders immutable was for some odd edge conditions like
InsertOrderMessageBuilder AlreadyHasSecurityAndBroker = New.InsertOrder.ForSecurity("RIM").FromBroker(0);
InsertOrderMessage FirstMessage = AlreadyHasSecurityAndBroker.ToBuy(1000).AtPrice(15.00).AcceptingOnlyCash;
InsertOrderMessage SecondMessage = AlreadyHasSecurityAndBroker.ToSell(1000).AtPrice(15.50);
But after thinking about this a bit I could do something similar using a mutable builder ... I could extract a method that returned me the AlreadyHasSecurityAndBroker version of the builder to something similar to an object mother. I will have to think a bit more about this but as of now I am considering moving my immutable builders to their mutable brethren.
Another difference that we found in implementation was my use of the DebuggerStepThrough attribute. For those who are not familiar with it; it tells the debugger to not step into this method ... so if I have a long line like ..
InsertOrderMessage message = New.InsertOrder.ForSecurity("RIM").FromBroker(12).ToBuy(2000).AtPrice(15.00);
and I set a break point on it ... then hit F11 to step into the code, it will bypass all of the short "setter" calls and take me directly to the implicit cast which is generally where my problem would be happening as the builder doesn't really care about its state. I really like the way this works! It is a good tool to remember for scenarios like this
A side discussion came up with someone else in regard to why I define the internal state of the builder as internal and not private. The reason why I do this is because I write (read: generate, the reason why I generate tests is that these classes are generated then manually edited from that point forward) tests for the builders and want to check the fields one by one when issuing a set ... As an example of this kind of test consider:
[Test]
public void Should_Set_Gateway() {
EquityQuoteMessageBuilder builder = new EquityQuoteMessageBuilder().OnGateway(ExchangeGateways.CNQToronto);
Assert.AreEqual(ExchangeGateways.CNQToronto,builder.m_Gateway);
}
I could just as easily put properties on the builder to expose its internal state or could make the fields public but when a builder is being used outside of these tests I don't want code to be able to access the internal state of the builder (I like to consider them write only). The need to have these things working properly kind of fits in with the want to have the debugger step through the code (since the code is simple and I have tests to make sure the simple code is working, I can safely guess that I wouldn't need to be debugging the code).
Posted
Wed, Apr 16 2008 6:22 PM
by
Greg