公开接口而不是具体类
本文关键字:接口 | 更新日期: 2023-09-27 18:35:56
我最近读了一些关于在公开集合而不是具体实现时使用接口的内容(IEnumerable而不是List)。我现在正在尝试在我的代码中做到这一点。但是,当我公开返回 IEnumerable 的属性时,我很难不允许 null 作为返回值。例:
public class HumanResource
{
public IEnumerable<EmployeeModel> Employees
{
get
{
// return what?
}
}
}
我应该在吸气机中返回什么?我不想为此使用自动属性,因为我想避免空值。我想要的是返回一个没有项目的新集合。当然,我可以返回任何实现 IEnumerable 的类型,但是类的外部用户如何知道呢?还是我错误地理解了这个公开接口而不是具体的实现?
编辑:删除了二传手
当然,我可以返回任何实现 IEnumerable 的类型,但是类的外部用户如何知道呢?
他们不必知道这一点,这正是重点。
您的财产承诺退还IEnumerable<EmplyeeModel>
,这正是发生的事情。代码返回哪个实现此接口的类并不重要。
我想要的是返回一个没有项目的新集合。
所以,Enumerable.Empty<EmplyeeModel>()
或new List<EmployeeModel>()
就可以了。
在设计 API 时,您需要考虑使用者将如何处理您返回的数据类型,并相应地做出决定。
通常,收藏IEnumerable<T>
适合所有人。当他们想要它在列表中时,他们可以做new List<T>(yourEnumerable)
,或者yourEnumerable.ToArray()
将其用作数组。
我想要的是返回一个没有项目的新集合。
属性使您可以非常轻松地执行此操作:
public class HumanResource
{
// This is the real employees that gets returned when its not null
private IEnumerable<EmployeeModel> employees; // may be null
// This is the empty IEnumerable that gets returned when employees is null
private static readonly IEnumerable<EmployeeModel> EmptyEmployees =
new EmployeeModel[0];
public IEnumerable<EmployeeModel> Employees
{
get
{
return employees ?? EmptyEmployees;
}
set {};
}
}
当变量设置为 null
时employees
代码返回一个空数组。您可以将employees
设置为实现IEnumerable<EmployeeModel>
的任何类型的集合,甚至可以设置为数组(如果您愿意)。这是可能的,因为您通过接口返回。
当然,另一方面,客户端无法直接访问未通过接口公开的属性方法。例如,如果employees
实际上是一个List
,则调用方必须使用 LINQ 的Count()
而不是直接获取.Count
。当然,您可以公开不同的接口,例如IList<EmployeeModel>
,以允许您的客户端使用其他方法。
您仍然需要为类中的属性提供内部支持集合。可以在构造函数或字段声明中初始化集合:
public class HumanResource
{
private readonly IList<EmployeeModel> _employees = new List<EmployeeModel>();
public IEnumerable<EmployeeModel> Employees
{
get
{
return _employees;
}
// No Setter - callers may only enumerate the collection
}
}
顺便说一句,请注意,即使您确实使用了自动属性(例如 List<EmployeeModel>
),除非在其他地方另有初始化,否则它将假定默认值为 null,因此在这方面没有任何变化。
编辑,回复:有什么好处?
- 通过删除资源库或将其设为私有,我们可以防止调用方重新分配
HumanResource
的内部集合 - 通过将集合从
List<>
软化到IEnumerable<>
,这意味着调用者只能对内部集合执行只读操作,例如迭代它。此外,IEnumerable<>
可以在延迟迭代中使用,允许调用方在获得所需数据后立即退出枚举。 - 根据下面的注释,如果调用方需要不同集合(如
Array
)中表示的数据,则可以使用 LINQ 扩展方法,例如.ToArray()
、.ToList()
、.ToDictionary()
。这样做将为调用方创建新集合,但引用相同的EmployeeModel
对象。这样做的性能损失很小。
最后需要注意的是,将IEnumerable
属性上的setter设为私有或将支持字段声明为IEnumerable
通常没有意义,因为这将防止类本身使用不纯方法来操作集合(即从中添加或删除对象),因为这样做需要强制转换, 例如:
public class HumanResource
{
public IEnumerable<EmployeeModel> Employees
{
get;
private set;
}
public HumanResource()
{
// Although the property can be assigned in the CTor to prevent the null issue ...
Employees = new List<EmployeeModel>();
}
private void AddProductiveEmployee()
{
// ... We need to continually cast within the class, which is just silly.
(Employees as IList).Add(new EmployeeModel());
}
使用内部IEnumerable<>
的手动支持字段方法,我们会遇到同样的问题
// Unlikely to be useful
private readonly IEnumerable<EmployeeModel> _employees = new List<EmployeeModel>();
博士
- 使用适合类内部用法的集合类型(OO 组合)
- 但是在外部接口(例如公共getter/属性)上,将内部实现隐藏到最低限度的必要(OO封装) 在
- 构造函数(或内联)中初始化内部集合将防止公开 null 集合