当我阅读各种教程和我们最喜欢的 StackOverflow 时,我经常看到这样的代码:
namespace MvcApplication2.Models
{
public class Category
{
public int ID { get; set; }
public string Name { get; set; }
}
public class Product
{
public ICollection<int> CategoryID { get; set; }
public Product()
{
CategoryID = new List<int>();
}
}
}
解释为什么一个属性应该CategoryID声明为一个接口ICollection,如果它在构造函数中用List?
设计师试图通过这种方法避免什么?
我了解是否Product通过其构造函数将某种依赖项引入了该类。但这不存在。
我错过了什么概念点?
价值突出,客户代码不知道那里有什么
List。也就是说,在架构师的左脚跟的要求下,新版本的库List可以被替换LinkedList,没有人会受到影响。它是对集合实现的抽象。这里作者在理论上尽量遵循最少知识的原则,不暴露实现细节。
也许这段代码是旧的或者所有的组件都没有显示,但在这个实现中设计不好。
该属性
CategoryID是可变的。客户可以在那里记录null随后的倾斜NullReferenceException。该属性仅试图隐藏实现细节,但做得很差。这在很大程度上取决于上下文,但至少有两种方法可以使这种设计一方面更严格,另一方面更简单。
首先,您可以使类型不可变并在构造函数中获取集合。在这种情况下,该属性可以成为或
CategoryID代替类型。ICollectionIReadOnlyCollectionIReadOnlyList其次,如果不能使类不可变,那么添加一个方法
AddCategory并仍然使属性成为CategoryID类型是有意义的IReadOnlyCollection/IReadOnlyList。没有上下文很难说,但我总是对命名空间中这样的数据对象感到惊讶
Model。命名空间名称中的模型告诉我,应用程序的全部本质、它的域对象、行为和各种花哨的东西都将隐藏在这里。当我在这样的命名空间中看到简单的数据对象时,我的期望与现实之间存在一些差异。换句话说,如果想要创建模型,那么完全隐藏内部而不是删除“真实列表类型”是有意义的。然后可以在不破坏现有客户端的情况下添加更多高级行为(一些按类别过滤的逻辑和其他东西)。
现在谈谈这个话题:
BCL 中的集合接口有点疯狂,因为现在很难说出它们的含义。这尤其适用于类型
ICollection:这个集合是什么?它是可变的吗?似乎是的,有一个方法Add。但麻烦的是,数组还实现了ICollection<T>,其方法Add抛出异常。是的,那里有房产IsReadOnly,但是所有客户都确定要检查吗?好吧,当然,集合有方法
Contains和Remove,但第一个给出 O(N) 的复杂性,这几乎总是不好的,第二个也不适用于所有集合。所以事实证明这个接口经常被这样使用
IEnumerable + Count,但在这种情况下,IReadOnlyXXX视图更适合。结论是:您需要了解您在隐瞒什么,对谁隐瞒,以及您是否在隐瞒任何事情。如果在应用程序中使用此代码,则有两种选择:如果类是数据存储,则使用特定的集合,或者完全隐藏集合并
IReadonlyXXX使用专门的方法公开视图Add。