当你有一个复杂的属性时,你应该实例化它还是留给用户实例化它?
例如(C#)
A)
class Xyz{
List<String> Names {get; set;}
}
当我尝试使用时,我必须设置它。
...
Xyz xyz = new Xyz();
xyz.Name = new List<String>();
xyz.Name.Add("foo");
...
我好像修改了代码
B)
class Xyz{
public Xyz(){
Names = new List<String>();
}
List<String> Names {get; }
}
在这种情况下,我可以将List设为只读。
可能会出现另一种情况,我想你有意不想设置它。例如在
中C)
class Xyz{
String Name {get; set;}
}
我认为初始化是不好的做法。
这种情况是否有一些经验法则?
答案 0 :(得分:3)
根据经验(意味着该规则总会有例外)在构造函数中设置您的成员。这就是构造函数的用途。
请记住,其中一个想法是OO是抽象的。要求'用户'(即想要在源代码中实例化对象的程序员)这样做是 违反抽象原则。在使用对象之前,用户必须考虑的另一件事是,这是另一个潜在的错误来源。
答案 1 :(得分:2)
这是我的正常解决方案:
class XYZ
{
public XYZ () { Names = new List<string>(); }
public List<string> Names { get; private set; }
}
(请注意,它不适用于XmlSerialization,因为您需要在所有XmlSerialized属性上使用getter和setter。)(您可以覆盖它,但似乎需要付出太多努力)。
正如OregonGhost所指出的那样 - 您需要为此添加[XmlArray]
才能使用XmlSerialization。
这仍然违反了封装规则,就像你想要完全正确一样,你会有:
class XYZ
{
public XYZ () { AllNames = new List<string>(); }
private List<string> AllNames { get; set; }
public void AddName ( string name ) { AllNames.Add(name); }
public IEnumerable<string> Names { get { return AllNames.AsReadOnly(); } }
}
由于这违反了.Net框架几乎所有其余部分的设计,我通常最终会使用第一个解决方案。
但是,这确实有一个额外的好处,即XYZ可以跟踪对其名称集合的更改,并且XYZ是唯一可以修改名称集合的地方。
我已经针对少数情况实现了这一点,但是当我为所有事情做这件事时,它会导致与其他程序员的摩擦过多。
答案 2 :(得分:2)
是的,有经验法则。代码分析工具是一个良好的开端。
有关您的代码的一些经验法则:
允许setter收集属性的不良做法。这是因为它很容易处理空集合,就像代码中的完整集合一样。强迫人们对集合进行空检查会让你受到殴打。请考虑以下代码段:
public bool IsValid(object input){
foreach(var validator in this.Validators)
if(!validator.IsValid(input)
return false;
return true;
}
无论验证者的集合是否为空,此代码都有效。如果您希望验证,请将验证器添加到集合中。如果没有,请将集合留空。简单。允许集合属性为null会导致上面的恶臭代码版本:
public bool IsValid(object input){
if(this.Validators == null)
return false;
foreach(var validator in this.Validators)
if(!validator.IsValid(input)
return false;
return true;
}
更多代码行,不那么优雅。
其次,对于除集合之外的参考类型,您必须在确定是否要设置属性值时考虑对象的行为方式。该属性是否有一个明显的默认值?或者属性的null值是否具有有效含义?
在您的示例中,您可能希望始终在setter中检查Name值,并在分配null时将其设置为默认值“(No name given)”。在将此对象与UI绑定时,这可能会更容易。或者,名称可能为null,因为您需要一个有效的名称,并且当调用者尝试对该对象执行操作而不先设置名称时,您将检查它并抛出InvalidOperationException。
与编程中的大多数事情一样,有很多不同的方法可以做某事,其中一半是坏的,另一半的每一种方式只在某些情况下才有效。
答案 3 :(得分:2)
构造函数的目的是构造对象。不需要进一步的“设置”。如果调用者知道某些信息,则应将其传递给构造函数:
<强>错误:强>
MyClass myc = new MyClass();
myc.SomeProp = 5;
myc.DoSomething();
从右:强>
MyClass myc = new MyClass(5);
myc.DoSomething();
如果需要为myc设置SomeProp以使其处于有效状态,则尤其如此。
答案 4 :(得分:1)
这真的取决于。
如果该类应该使用null属性正确运行,则可以将其保留为未初始化。否则,你最好在构造函数中初始化它。
并且,如果您不希望在施工后更改属性,则可以将其设为私有
class Xyz
{
public Xyz(string name)
{
this.Name = name;
}
String Name
{
get;
private set;
}
}
答案 5 :(得分:0)
如果可能,您应该尝试封装数据。将对象公开为列表要求用户知道关于该类的太多私密细节。例如,他们必须知道他们是否必须从头开始创建列表以避免空引用异常。
public class Xyz
{
private List<String> _names = new List<String>(); // could also set in constructor
public IEnumerable<String> Names
{
get
{
return _names;
}
}
public void AddName( string name )
{
_names.Add( name );
}
}
现在使用List,HashTable,Dictionary等无所谓