以下是我尝试的代码,有更好的方法吗?
public class NutritionFacts
{
public static NutritionFacts.Builder Build(string name, int servingSize, int servingsPerContainer)
{
return new NutritionFacts.Builder(name, servingSize, servingsPerContainer);
}
public sealed class Builder
{
public Builder(String name, int servingSize,
int servingsPerContainer)
{
}
public Builder totalFat(int val) { }
public Builder saturatedFat(int val) { }
public Builder transFat(int val) { }
public Builder cholesterol(int val) { }
//... 15 more setters
public NutritionFacts build()
{
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) { }
protected NutritionFacts() { }
}
我们如何扩展这样的课程?做 我们需要编写单独的构建器 每个派生的类 类?
public class MoreNutritionFacts : NutritionFacts
{
public new static MoreNutritionFacts.Builder Build(string name, int servingSize, int servingsPerContainer)
{
return new MoreNutritionFacts.Builder(name, servingSize, servingsPerContainer);
}
public new sealed class Builder
{
public Builder(String name, int servingSize,
int servingsPerContainer) {}
public Builder totalFat(int val) { }
public Builder saturatedFat(int val) { }
public Builder transFat(int val) { }
public Builder cholesterol(int val) { }
//... 15 more setters
public Builder newProperty(int val) { }
public MoreNutritionFacts build()
{
return new MoreNutritionFacts(this);
}
}
private MoreNutritionFacts(MoreNutritionFacts.Builder builder) { }
}
答案 0 :(得分:22)
在Protocol Buffers中,我们实现了这样的构建器模式(大大简化):
public sealed class SomeMessage
{
public string Name { get; private set; }
public int Age { get; private set; }
// Can only be called in this class and nested types
private SomeMessage() {}
public sealed class Builder
{
private SomeMessage message = new SomeMessage();
public string Name
{
get { return message.Name; }
set { message.Name = value; }
}
public int Age
{
get { return message.Age; }
set { message.Age = value; }
}
public SomeMessage Build()
{
// Check for optional fields etc here
SomeMessage ret = message;
message = null; // Builder is invalid after this
return ret;
}
}
}
这与EJ2中的模式不完全相同,但是:
StringBuilder
所做的。Build()
后构建器变为无效以保证不变性。遗憾的是,它不能像EJ2版本那样用作一种“原型”。this
的setter。我还没有真正研究过构建模式的继承 - 无论如何它都不支持Protocol Buffers。我怀疑这很棘手。
答案 1 :(得分:4)
C#中模式的一个简洁变化是使用隐式强制转换运算符来最终调用Build():
public class CustomerBuilder
{
......
public static implicit operator Customer( CustomerBuilder builder )
{
return builder.Build();
}
}
答案 2 :(得分:2)
修改:我再次使用它并简化它以删除setter中的冗余值检查。
我最近实现了一个运行良好的版本。
构建器是缓存最新实例的工厂。派生的构建器在发生任何更改时创建实例并清除缓存。
基类很简单:
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;
}
}
我们正在解决的问题更加微妙。假设我们有Order
:
public class Order
{
public string ReferenceNumber { get; private set; }
public DateTime? ApprovedDateTime { get; private set; }
public void Approve()
{
ApprovedDateTime = DateTime.Now;
}
}
ReferenceNumber
在创建后不会更改,因此我们通过构造函数将其建模为只读:
public Order(string referenceNumber)
{
// ... validate ...
ReferenceNumber = referenceNumber;
}
我们如何从数据库数据中重建现有的概念Order
?
这是ORM断开连接的根源:为了方便技术,它会强制ReferenceNumber
和ApprovedDateTime
上的公共设置者。未来的读者隐藏着什么是明确的事实;我们甚至可以说这是一个不正确的模型。 (对于扩展点也是如此:强制virtual
删除了基类传达其意图的能力。)
具有特殊知识的Builder
是一种有用的模式。嵌套类型的替代方法是internal
访问。它支持可变性,域行为(POCO),以及作为奖励的Jon Skeet提到的“原型”模式。
首先,将internal
构造函数添加到Order
:
internal Order(string referenceNumber, DateTime? approvedDateTime)
{
ReferenceNumber = referenceNumber;
ApprovedDateTime = approvedDateTime;
}
然后,添加一个具有可变属性的Builder
:
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); }
}
}
有趣的是SetField
次来电。由Builder
定义,它封装了“设置备份字段,如果不同,然后清除实例”的模式,否则将在属性设置器中:
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;
}
我们使用ref
来修改支持字段。我们还使用默认的相等比较器,但允许调用者覆盖它。
最后,当我们需要重新构建Order
时,我们会使用OrderBuilder
隐式转换:
Order order = new OrderBuilder
{
ReferenceNumber = "ABC123",
ApprovedDateTime = new DateTime(2008, 11, 25)
};
这真的很长。希望它有所帮助!
答案 3 :(得分:0)
使用Joshua Bloch的构建器模式的原因是用部件创建一个复杂的对象,并使其不可变。
在这种特殊情况下,在C#4.0中使用可选的命名参数更清晰。您放弃了一些设计灵活性(不要重命名参数),但是您可以更轻松地获得更好的可维护代码。
如果NutritionFacts代码是:
public class NutritionFacts
{
public int servingSize { get; private set; }
public int servings { get; private set; }
public int calories { get; private set; }
public int fat { get; private set; }
public int carbohydrate { get; private set; }
public int sodium { get; private set; }
public NutritionFacts(int servingSize, int servings, int calories = 0, int fat = 0, int carbohydrate = 0, int sodium = 0)
{
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.carbohydrate = carbohydrate;
this.sodium = sodium;
}
}
然后客户端将其用作
NutritionFacts nf2 = new NutritionFacts(240, 2, calories: 100, fat: 40);
如果结构更复杂,则需要进行调整;如果卡路里的“构建”不仅仅是整数,那么可以想象其他辅助对象是必需的。