C# 不变的设计:处理构造函数的疯狂
出于各种原因,我想开始在设计中使用更多的不可变类型。目前,我正在处理一个现有类的项目,该类如下:C# 不变的设计:处理构造函数的疯狂,c#,immutability,C#,Immutability,出于各种原因,我想开始在设计中使用更多的不可变类型。目前,我正在处理一个现有类的项目,该类如下: public class IssueRecord { // The real class has more readable names :) public string Foo { get; set; } public string Bar { get; set; } public int Baz { get; set; } public string Pr
public class IssueRecord
{
// The real class has more readable names :)
public string Foo { get; set; }
public string Bar { get; set; }
public int Baz { get; set; }
public string Prop { get; set; }
public string Prop2 { get; set; }
public string Prop3 { get; set; }
public string Prop4 { get; set; }
public string Prop5 { get; set; }
public string Prop6 { get; set; }
public string Prop7 { get; set; }
public string Prop8 { get; set; }
public string Prop9 { get; set; }
public string PropA { get; set; }
}
public class IssuerRecord
{
public string PropA { get; set; }
public IList<IssuerRecord> Subrecords { get; set; }
}
public class ImmutableIssuerRecord
{
public ImmutableIssuerRecord(IssuerRecord record)
{
PropA = record.PropA;
Subrecords = record.Subrecords.Select(r => new ImmutableIssuerRecord(r));
}
public string PropA { get; private set; }
// lacks Count and this[int] but it's IReadOnlyList<T> is coming in 4.5.
public IEnumerable<ImmutableIssuerRecord> Subrecords { get; private set; }
// you may want to get a mutable copy again at some point.
public IssuerRecord GetMutableCopy()
{
var copy = new IssuerRecord
{
PropA = PropA,
Subrecords = new List<IssuerRecord>(Subrecords.Select(r => r.GetMutableCopy()))
};
return copy;
}
}
这个类表示一些磁盘上的格式,这些格式确实有这么多属性,所以将其重构为更小的位在这一点上几乎是不可能的
这是否意味着这个类上的构造函数在一个不可变的设计中真的需要有13个参数?如果不是,如果要使此设计不可变,我可以采取哪些步骤来减少构造函数中接受的参数数量?您可以创建一个结构,但仍然需要声明该结构。但总有数组之类的。如果它们都是相同的数据类型,则可以通过多种方式对它们进行分组,例如数组、列表或字符串。不过,看起来您是对的,您的所有不可变类型都必须以某种方式通过构造函数,通过13个参数,或通过结构、数组、列表等进行处理。也许可以保持当前类的原样,如果可能,提供合理的默认值,并重命名为IssuerRecordOptions。将其用作不可变IssuerRecord的单个初始化参数。您可以在构造函数中使用命名参数和可选参数的组合。如果值总是不同的,那么是的,你被一个疯狂的构造函数困住了 这是否意味着这个类上的构造函数在一个不可变的设计中真的需要有13个参数 一般来说,是的。一个具有13个属性的不可变类型需要一些方法来初始化所有这些值 如果它们没有全部使用,或者某些属性可以基于其他属性来确定,那么您可能会有一个或多个参数较少的重载构造函数。但是,构造函数(无论类型是否不可变)确实应该以类型在逻辑上“正确”和“完整”的方式完全初始化类型的数据 这个类表示一些磁盘上的格式,这些格式确实有这么多属性,所以将其重构为更小的位在这一点上几乎是不可能的
如果“磁盘格式”是在运行时确定的,您可能会有一个工厂方法或构造函数,它接受初始化数据(即:文件名?等)并为您构建完全初始化的类型。要减少参数数量,您可以将其分组到合理的集合中,但要拥有真正的不可变对象,您必须在构造函数/工厂方法中初始化它 一些变体是创建“builder”类,您可以使用fluent接口进行配置,然后请求最终对象。如果您实际上计划在代码的不同位置创建许多这样的对象,那么这是有意义的,否则在一个位置创建许多参数可能是可以接受的折衷
var immutable = new MyImmutableObjectBuilder()
.SetProp1(1)
.SetProp2(2)
.Build();
如果您的目的是在编译期间禁止赋值,那么您必须坚持构造函数赋值和私有setter。然而,它有许多缺点-您不能使用新成员初始化,也不能使用xml反序列化等等 我建议这样做:
public class IssueRecord
{
// The real class has more readable names :)
public string Foo { get; set; }
public string Bar { get; set; }
public int Baz { get; set; }
public string Prop { get; set; }
public string Prop2 { get; set; }
public string Prop3 { get; set; }
public string Prop4 { get; set; }
public string Prop5 { get; set; }
public string Prop6 { get; set; }
public string Prop7 { get; set; }
public string Prop8 { get; set; }
public string Prop9 { get; set; }
public string PropA { get; set; }
}
public class IssuerRecord
{
public string PropA { get; set; }
public IList<IssuerRecord> Subrecords { get; set; }
}
public class ImmutableIssuerRecord
{
public ImmutableIssuerRecord(IssuerRecord record)
{
PropA = record.PropA;
Subrecords = record.Subrecords.Select(r => new ImmutableIssuerRecord(r));
}
public string PropA { get; private set; }
// lacks Count and this[int] but it's IReadOnlyList<T> is coming in 4.5.
public IEnumerable<ImmutableIssuerRecord> Subrecords { get; private set; }
// you may want to get a mutable copy again at some point.
public IssuerRecord GetMutableCopy()
{
var copy = new IssuerRecord
{
PropA = PropA,
Subrecords = new List<IssuerRecord>(Subrecords.Select(r => r.GetMutableCopy()))
};
return copy;
}
}
如果你用C++术语思考,你可以看到IsMaable S发行记录为“Suffer-Realth-const”。不过,为了保护不可变对象所拥有的对象,您必须使用extracare,这就是为什么我建议为所有子对象创建一个副本(例如子记录)
ImmutableIssuerRecord.Subrecors在这里是IEnumerable的,没有Count和this[],但IReadOnlyList将在4.5版本中推出,如果需要,您可以从文档中复制它(并使以后的迁移更容易) 还有其他方法,例如Freezable:public class IssuerRecord
{
private bool isFrozen = false;
private string propA;
public string PropA
{
get { return propA; }
set
{
if( isFrozen ) throw new NotSupportedOperationException();
propA = value;
}
}
public void Freeze() { isFrozen = true; }
}
这会降低代码的可读性,并且不提供编译时保护。但您可以创建正常的对象,然后在它们准备好后冻结它们
Builder模式也是需要考虑的,但是从我的观点来看,它添加了太多的“服务”代码。
哦,我刚刚意识到它们大部分是字符串,所以我说使用数组是正确的。一个数组真的不起作用,因为它破坏了结构中参数的语义含义。使用该类的人必须记住,数组中的第5个元素表示“Details”或类似的意思,这比让该类保持可变更糟糕。对,那么结构是唯一的其他实际选项。另外,如果我是正确的,在C#中,结构可以使用默认值,这在您的情况下可能会很有用。结构不会真的有帮助-如果要创建不可变结构,您仍然需要构造函数来初始化数据。(也就是说,这违反了关于何时选择结构(大于16字节,包含引用类型,不表示单个逻辑值等)的几乎所有规则,因此类在这里几乎肯定更合适…@ReedCopsey:在这里使用具有公开字段的结构是最合适的。在构造函数外部修改此
的结构是有问题的,但与具有公开读写属性的结构不同,具有公开字段的结构从不修改此
。这种结构的语义不同于类的语义,但在许多情况下,它们比可变类的语义更清晰、更清晰,同时比不可变类或所谓的“不可变”结构的语义更方便、更清晰。类外部是否需要设置值,或者类本身可以设置值吗?如果类可以这样做,那么您就可以拥有一个无参数构造函数。请参阅:您现在如何初始化这些属性?另请参阅:如果系统的一部分只需要不变性要求,那么这是一个很好的选项。但是,您仍然在处理可变类型,因为您必须改变原始的“选项”类型。您只需要使用DataContractSerializer而不是