我可以想到两个原因来选择受保护的属性而不是受保护的字段。首先,考虑这个例子:
public class BaseClass
{
protected readonly List<string> _someValues;
}
public class InheritedClass : BaseClass
{
public void NeedsThoseValues()
{
DoSomethingWith(_someValues);
}
}
现在您决定更改基类以使用延迟实例化:
public class BaseClass
{
protected readonly Lazy<List<string>> _someValues;
}
现在继承的类必须更改为 call _someValues.Value
。那些继承的类真的需要改变吗?如果字段是私有的并且作为属性暴露给继承的类,则更改基类不会破坏继承的类:
public class BaseClass
{
private readonly Lazy<List<string>> _someValues;
protected List<string> SomeValues => _someValues.Value;
}
这需要我们改变我们在脑海中描绘继承类的方式。我们可能会开始将其可视化,就像在小房子周围建造大房子一样。小房子里的所有东西都在大房子里,所以实际上都是一所大房子。没有理由将内屋隐藏在外屋之外。
实际上,它不是那样的。基类是它自己独特的实体,存在于更大的房子中。为了在多个房屋(多个继承的类)中重用它,我们需要封装它,以便继承的类不需要知道更多关于它的信息,就像它们依赖的任何其他类一样。
这有时会产生一种奇怪的关系。我们试图防止继承类和基类的实现细节之间过度耦合,但它们是耦合的,因为没有基类就不能存在继承类。这就提出了一个问题——为什么继承的类应该与基类有这种关系?为什么不将基类的功能分离到它自己的类中,用抽象(如接口)表示它,然后在需要的地方注入呢?
这就是优先组合而不是继承背后的想法。很多很多时候,继承用于在类(基类和子类)之间共享功能,而这不是它的用途。如果我们有两个不同的功能领域,我们应该用两个不同的类来完成,一个可以依赖另一个。如果我们通过继承来实现这一点,当我们的类需要依赖另一个类时,我们就会遇到障碍。我们不能再给它另一个基类。除非我们组合不同的类一起工作,否则我们可能会开始做一些非常邪恶的事情,比如在现有的基类中添加更多功能或创建更多级别的继承。它变得难以理解,最终将我们逼入绝境。
另一个原因是,当有人看到_someValues
基类中的引用时,他们会假设它是在使用它的类中声明的字段,因为这是更常见的约定。这不会造成任何巨大的混乱,但他们需要更长的时间才能弄清楚。
在许多特定情况下,首选受保护的只读属性而不是受保护的只读字段的这些原因可能不是问题。但是很难预先知道这一点,所以最好选择首选的做法,并在理解你为什么这样做的同时养成习惯。