Edit: I used this again and simplified it to remove redundant check of values in setters.
I recently implemented a version that works well.
Builders are factories that cache the most recent instance. Derived collectors instantiate and clear the cache when something changes.
The base class is simple:
public abstract class Builder<T> : IBuilder<T> { public static implicit operator T(Builder<T> 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 them say that we have the concept of 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 through the constructor:
public Order(string referenceNumber) {
How do we restore an existing conceptual Order , for example, from database data?
This is the root of ORM being disabled: it seeks to force public setters on ReferenceNumber and ApprovedDateTime for convenience. What was clear truth is hidden to future readers; we could even say that this is the wrong model. (The same is true for extension points: forcing virtual eliminates the ability of base classes to communicate their intentions.)
A Builder with specialized knowledge is a useful example. An alternative to nested types would be internal access. It allows you to change the domain behavior (POCO) and, as a bonus, the "prototype" template mentioned by Jon Skeet.
First add the internal constructor to Order :
internal Order(string referenceNumber, DateTime? approvedDateTime) { ReferenceNumber = referenceNumber; ApprovedDateTime = approvedDateTime; }
Then add Builder with mutable properties:
public class OrderBuilder : Builder<Order> { 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); } } }
An interesting bit is SetField calls. Defined by Builder , it encapsulates the template "set the support field, if it is different, then clear the instance", which would otherwise be in the properties settings:
protected bool SetField<TField>( ref TField field, TField newValue, IEqualityComparer<T> equalityComparer = null) { equalityComparer = equalityComparer ?? EqualityComparer<TField>.Default; var different = !equalityComparer.Equals(field, newValue); if(different) { field = newValue; ClearInstance(); } return different; }
We use ref so that we can change the support field. We also use the default equalizer, but allow callers to override it.
Finally, when we need to restore Order , we use an implicit cast OrderBuilder :
Order order = new OrderBuilder { ReferenceNumber = "ABC123", ApprovedDateTime = new DateTime(2008, 11, 25) };
It happened a very long time. Hope this helps!