Below is the code I have tried, is there a better way to do this?
Edit: I used this again and simplified it to remove the redundant value-checking in setters.
I recently implemented a version that is working out nicely.
Builders are factories which cache the most recent instance. Derived builders create instances and clear the cache when anything changes.
The base class is straightforward:
public abstract class Builder : IBuilder
{
public static implicit operator T(Builder builder)
{
return builder.Instance;
}
private T _instance;
public bool HasInstance { get; private set; }
public T Instance
{
get
{
if(!HasInstance)
{
_instance = CreateInstance();
HasInstance = true;
}
return _instance;
}
}
protected abstract T CreateInstance();
public void ClearInstance()
{
_instance = default(T);
HasInstance = false;
}
}
The problem we are solving is more subtle. Let's say we have the concept of an Order
:
public class Order
{
public string ReferenceNumber { get; private set; }
public DateTime? ApprovedDateTime { get; private set; }
public void Approve()
{
ApprovedDateTime = DateTime.Now;
}
}
ReferenceNumber
does not change after creation, so we model it read-only via the constructor:
public Order(string referenceNumber)
{
// ... validate ...
ReferenceNumber = referenceNumber;
}
How do we reconstitute an existing conceptual Order
from, say, database data?
This is the root of the ORM disconnect: it tends to force public setters on ReferenceNumber
and ApprovedDateTime
for technical convenience. What was a clear truth is hidden to future readers; we could even say it is an incorrect model. (The same is true for extension points: forcing virtual
removes the ability for base classes to communicate their intent.)
A Builder
with special knowledge is a useful pattern. An alternative to nested types would be internal
access. It enables mutability, domain behavior (POCO), and, as a bonus, the "prototype" pattern mentioned by Jon Skeet.
First, add an internal
constructor to Order
:
internal Order(string referenceNumber, DateTime? approvedDateTime)
{
ReferenceNumber = referenceNumber;
ApprovedDateTime = approvedDateTime;
}
Then, add a Builder
with mutable properties:
public class OrderBuilder : Builder
{
private string _referenceNumber;
private DateTime? _approvedDateTime;
public override Order Create()
{
return new Order(_referenceNumber, _approvedDateTime);
}
public string ReferenceNumber
{
get { return _referenceNumber; }
set { SetField(ref _referenceNumber, value); }
}
public DateTime? ApprovedDateTime
{
get { return _approvedDateTime; }
set { SetField(ref _approvedDateTime, value); }
}
}
The interesting bit is the SetField
calls. Defined by Builder
, it encapsulates the pattern of "set the backing field if different, then clear the instance" that would otherwise be in the property setters:
protected bool SetField(
ref TField field,
TField newValue,
IEqualityComparer equalityComparer = null)
{
equalityComparer = equalityComparer ?? EqualityComparer.Default;
var different = !equalityComparer.Equals(field, newValue);
if(different)
{
field = newValue;
ClearInstance();
}
return different;
}
We use ref to allow us to modify the backing field. We also use the default equality comparer but allow callers to override it.
Finally, when we need to reconstitute an Order
, we use OrderBuilder
with the implicit cast:
Order order = new OrderBuilder
{
ReferenceNumber = "ABC123",
ApprovedDateTime = new DateTime(2008, 11, 25)
};
This got really long. Hope it helps!