我正在学习DDD,并且遇到了“值 - 对象”应该是不可变的声明。我知道这意味着对象状态在创建后不应该更改。这对我来说是一种新的思维方式,但在很多情况下都是有道理的。
好的,所以我开始创建不可变的值对象。
但是现在我想创建这个包含8个不同数值的值对象。如果我创建一个具有8个数字参数的构造函数,我觉得它不会很容易使用,或者更确切地说 - 传递数字时很容易出错。这不是一个好的设计。
所以问题是:还有其他方法可以让我的不可变对象变得更好..,在C#中可以用来克服构造函数中的长参数列表的任何魔法吗?我很想听听你的想法..
更新:在有人提及之前,我们已经讨论了一个想法: Immutable object pattern in C# - what do you think?
有兴趣听取其他建议或意见。
答案 0 :(得分:22)
使用构建器:
public class Entity
{
public class Builder
{
private int _field1;
private int _field2;
private int _field3;
public Builder WithField1(int value) { _field1 = value; return this; }
public Builder WithField2(int value) { _field2 = value; return this; }
public Builder WithField3(int value) { _field3 = value; return this; }
public Entity Build() { return new Entity(_field1, _field2, _field3); }
}
private int _field1;
private int _field2;
private int _field3;
private Entity(int field1, int field2, int field3)
{
// Set the fields.
}
public int Field1 { get { return _field1; } }
public int Field2 { get { return _field2; } }
public int Field3 { get { return _field3; } }
public static Builder Build() { return new Builder(); }
}
然后创建它:
Entity myEntity = Entity.Build()
.WithField1(123)
.WithField2(456)
.WithField3(789)
.Build()
如果某些参数是可选的,则无需调用WithXXX方法,它们可以具有默认值。
答案 1 :(得分:9)
目前,您必须使用具有大量args或构建器的构造函数。在C#4.0(VS2010)中,您可以使用命名/可选参数来实现类似于C#3.0对象初始化程序的操作 - 请参阅here。博客上的例子是:
Person p = new Person ( forename: "Fred", surname: "Flintstone" );
但是你可以很容易地看到类似的东西如何适用于任何构造函数(或其他复杂的方法)。与C#3.0对象初始化器语法(具有可变类型)相比:
Person p = new Person { Forename = "Fred", Surname = "Flintstone" };
真的不能分别告诉他们。
Jon Skeet也发表了一些关于这个主题的想法,here。
答案 2 :(得分:3)
脱离我的脑海,脑海中浮现出两个不同的答案......
......第一个,也可能是最简单的,是使用对象工厂(或构建器)作为帮助程序,确保您正确处理。
对象初始化将如下所示:
var factory = new ObjectFactory();
factory.Fimble = 32;
factory.Flummix = "Nearly";
var mine = factory.CreateInstance();
...第二个是使用Lock()或Freeze()函数将对象创建为传统的可变对象。你的所有mutators应检查对象是否已被锁定,如果有,则抛出异常。
对象初始化将如下所示:
var mine = new myImmutableObject();
mine.Fimble = 32;
mine.Flummix = "Nearly";
mine.Lock(); // Now it's immutable.
要采取哪种方法很大程度上取决于您的上下文 - 如果要构造一系列类似的对象,工厂的优势是方便,但它确实引入了另一个类来编写和维护。可锁定对象意味着只有一个类,但其他用户可能会遇到意外的运行时错误,并且测试更难。
答案 3 :(得分:1)
虽然它可能是您正在做的域的一部分,因此我的建议可能无效,那么尝试将8个参数分解为逻辑组呢?
每当我看到大量参数时,我觉得对象/方法/构造函数应该更简单。
答案 4 :(得分:1)
我对同样的问题感到困惑,因为复杂的构造函数对我来说也是糟糕的设计。我也不是构建器概念的忠实粉丝,因为它似乎需要太多额外的代码来维护。我们需要的是冰棒不变性,这意味着一个对象开始是可变的,你可以使用属性设置器。设置所有属性后,必须有一种方法将对象冻结为不可变状态。遗憾的是,这种策略在C#语言中不受支持。因此,我最终设计了自己的模式来创建不可变对象,如本问题所述:
Immutable object pattern in C# - what do you think?
Anders Hejlsberg在接下来的采访中谈到从36:30开始对这种不变性的支持:
答案 5 :(得分:1)
您可以使用反射来初始化对象的所有字段和懒惰以使“setter”像方法(使用monadic函数样式)以将设置的方法/函数链接在一起。
例如:
您可以使用此基类:
public class ImmutableObject<T>
{
private readonly Func<IEnumerable<KeyValuePair<string, object>>> initContainer;
protected ImmutableObject() {}
protected ImmutableObject(IEnumerable<KeyValuePair<string,object>> properties)
{
var fields = GetType().GetFields().Where(f=> f.IsPublic);
var fieldsAndValues =
from fieldInfo in fields
join keyValuePair in properties on fieldInfo.Name.ToLower() equals keyValuePair.Key.ToLower()
select new {fieldInfo, keyValuePair.Value};
fieldsAndValues.ToList().ForEach(fv=> fv.fieldInfo.SetValue(this,fv.Value));
}
protected ImmutableObject(Func<IEnumerable<KeyValuePair<string,object>>> init)
{
initContainer = init;
}
protected T setProperty(string propertyName, object propertyValue, bool lazy = true)
{
Func<IEnumerable<KeyValuePair<string, object>>> mergeFunc = delegate
{
var propertyDict = initContainer == null ? ObjectToDictonary () : initContainer();
return propertyDict.Select(p => p.Key == propertyName? new KeyValuePair<string, object>(propertyName, propertyValue) : p).ToList();
};
var containerConstructor = typeof(T).GetConstructors()
.First( ce => ce.GetParameters().Count() == 1 && ce.GetParameters()[0].ParameterType.Name == "Func`1");
return (T) (lazy ? containerConstructor.Invoke(new[] {mergeFunc}) : DictonaryToObject<T>(mergeFunc()));
}
private IEnumerable<KeyValuePair<string,object>> ObjectToDictonary()
{
var fields = GetType().GetFields().Where(f=> f.IsPublic);
return fields.Select(f=> new KeyValuePair<string,object>(f.Name, f.GetValue(this))).ToList();
}
private static object DictonaryToObject<T>(IEnumerable<KeyValuePair<string,object>> objectProperties)
{
var mainConstructor = typeof (T).GetConstructors()
.First(c => c.GetParameters().Count()== 1 && c.GetParameters().Any(p => p.ParameterType.Name == "IEnumerable`1") );
return mainConstructor.Invoke(new[]{objectProperties});
}
public T ToObject()
{
var properties = initContainer == null ? ObjectToDictonary() : initContainer();
return (T) DictonaryToObject<T>(properties);
}
}
可以这样实现:
public class State:ImmutableObject<State>
{
public State(){}
public State(IEnumerable<KeyValuePair<string,object>> properties):base(properties) {}
public State(Func<IEnumerable<KeyValuePair<string, object>>> func):base(func) {}
public readonly int SomeInt;
public State someInt(int someInt)
{
return setProperty("SomeInt", someInt);
}
public readonly string SomeString;
public State someString(string someString)
{
return setProperty("SomeString", someString);
}
}
可以像这样使用:
//creating new empty object
var state = new State();
// Set fields, will return an empty object with the "chained methods".
var s2 = state.someInt(3).someString("a string");
// Resolves all the "chained methods" and initialize the object setting all the fields by reflection.
var s3 = s2.ToObject();