公开接口而不是具体类

本文关键字:接口 | 更新日期: 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 {};
    }
}

当变量设置为 nullemployees代码返回一个空数组。您可以将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 集合