简体   繁体   English

公开接口而不是具体的类

[英]Exposing interfaces instead of concrete classes

I've recently read something about using interfaces when exposing collections instead of concrete implementations (IEnumerable instead of List). 我最近阅读了一些有关在公开集合而不是具体实现时使用接口的信息(IEnumerable而不是List)。 I'm trying to do that now in my code. 我现在正在尝试在我的代码中执行此操作。 However, when I expose a property that return IEnumerable, I'm having some difficulty of not allowing nulls as a return value. 但是,当我公开一个返回IEnumerable的属性时,我遇到了一些困难,即不允许将空值作为返回值。 Example: 例:

public class HumanResource
{
    public IEnumerable<EmployeeModel> Employees
    {
        get
        {
            // return what?
        }
    }
}

What should I return in the getter? 我应该在吸气剂中返回什么? I don't want to use automatic properties for this as I want to avoid nulls. 我不想为此使用自动属性,因为我想避免使用null。 What I want is to return a new collection with no items. 我想要的是退回没有任何物品的新收藏。 Of course I can return any type that implements IEnumerable but how will the external user of the class know that? 当然,我可以返回实现IEnumerable的任何类型,但是该类的外部用户将如何知道呢? Or did I understand this exposing interface instead of concrete implementations wrong? 还是我了解这个公开的界面,而不是错误的具体实现?

EDIT: Removed setter 编辑:删除二传手

Of course I can return any type that implements IEnumerable but how will the external user of the class know that? 当然,我可以返回实现IEnumerable的任何类型,但是该类的外部用户将如何知道呢?

They don't have to know that, that's exactly the point. 他们不必知道这一点,这就是重点。

Your property promises to return an IEnumerable<EmplyeeModel> , and that's exactly what happens. 您的媒体资源承诺会返回IEnumerable<EmplyeeModel> ,而这正是发生的情况。 It doesn't matter which class implementing this interface your code returns. 您的代码返回哪个实现此接口的类都没有关系。

What I want is to return a new collection with no items. 我想要的是退回没有任何物品的新收藏。

So, Enumerable.Empty<EmplyeeModel>() or new List<EmployeeModel>() will do just fine. 因此, Enumerable.Empty<EmplyeeModel>()new List<EmployeeModel>()都可以。


When designing an API you need to think about what the consumers will do with the data types you return, and decide upon that accordingly. 在设计API时,您需要考虑使用者将如何处理返回的数据类型,并据此做出决定。

Usually an IEnumerable<T> for collections suits everyone. 通常,集合的IEnumerable<T>适合每个人。 When they want it in a list, they can do new List<T>(yourEnumerable) , or yourEnumerable.ToArray() to use it as an array. 当他们想要它在列表中时,他们可以执行new List<T>(yourEnumerable)yourEnumerable.ToArray()以将其用作数组。

What I want is to return a new collection with no items. 我想要的是退回没有任何物品的新收藏。

Properties let you do that very easily: 属性使您可以很容易地做到这一点:

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 {};
    }
}

The code returns an empty array when employees variable is set to null . employees变量设置为null时,代码将返回一个空数组。 You can set employees to a collection of any type that implements IEnumerable<EmployeeModel> , or even to an array if you prefer. 您可以将employees设置为实现IEnumerable<EmployeeModel>的任何类型的集合,或者根据需要设置为数组。 This is possible because you return by interface. 这是可能的,因为您通过接口返回。

The flip side of this, of course, is that the clients would have no direct access to methods of properties that are not exposed through the interface. 当然,另一方面,客户端将无法直接访问未通过接口公开的属性方法。 For example, if employees is actually a List , the callers would have to use LINQ's Count() instead of obtaining .Count directly. 例如,如果employees实际上是一个List ,则调用者将不得不使用LINQ的Count()而不是直接获取.Count Of course you can expose a different interface, say, IList<EmployeeModel> , to let your clients use additional methods. 当然,您可以公开另一个接口,例如IList<EmployeeModel> ,以使您的客户端使用其他方法。

You still need to provide an internal backing collection for the property in your class. 您仍然需要为类中的属性提供内部支持集合。 You can initialize the collection in the constructor, or in the field declaration: 您可以在构造函数中或在字段声明中初始化集合:

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
  }
}

As an aside, note that even if you did use an automatic property (eg List<EmployeeModel> ), that it would assume a default value of null, unless otherwise initialized elsewhere, so nothing changes in this respect. 顺便说一句,请注意,即使您确实使用了自动属性(例如List<EmployeeModel> ),它也会采用默认值null,除非在其他地方进行了初始化,所以这方面没有任何改变。

Edit, Re : What are the benefits? 编辑,回复:有什么好处?

  • By removing the setter, or making it private, we prevent a caller from reassigning the internal collection of a HumanResource 通过移除设置器或将其设置为私有,我们可以防止调用者重新分配HumanResource的内部集合。
  • By softening the collection from a List<> to an IEnumerable<> , it means the caller can only do read-only actions on the internal collection, such as to iterate it. 通过将集合从List<> IEnumerable<>IEnumerable<> ,这意味着调用方只能对内部集合执行只读操作,例如对其进行迭代。 In addition, IEnumerable<> can be used in a lazy iteration, allowing the caller to quit enumerating as soon as it has the data it needs. 另外, IEnumerable<>可以用于延迟迭代中,从而允许调用方在拥有所需数据后立即退出枚举。
  • As per the comment below, if the caller requires the data represented in a different collection, such as an Array , then LINQ extension methods such as .ToArray() , .ToList() , .ToDictionary() can be used. 根据下面的评论,如果调用者需要以其他集合(例如Array表示的数据,则可以使用LINQ扩展方法,例如.ToArray() .ToList().ToDictionary() Doing so will create new collections for the caller, but with references to the same EmployeeModel objects. 这样做将为调用者创建新的集合,但是会引用相同的EmployeeModel对象。 The performance penalties of doing this are minimal. 这样做对性能的影响很小。

One final note is that there is usually no point in making the setter on an IEnumerable property private, or declaring the backing field as an IEnumerable , as this will prevent the class itself from using impure methods to manipulate the collection (ie add or remove objects from it), as doing so would require a cast, eg: 最后一点要注意的是,通常没有必要将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());
    }

We would have the same problem with the manual backing field approach with an internal IEnumerable<> 使用内部IEnumerable<>的手动支持字段方法会遇到相同的问题

// Unlikely to be useful
private readonly IEnumerable<EmployeeModel> _employees = new List<EmployeeModel>();

TL;DR TL; DR

  • Use a collection type which is appropriate for the internal usage to the class (OO composition) 使用适合于类内部使用的集合类型(OO组成)
  • But on the external interfaces (eg public getter / property), hide the internal implementation to the minimum necessary (OO encapsulation) 但是在外部接口(例如公共获取器/属性)上,将内部实现隐藏到最小(OO封装)
  • Initializing the internal collection in the constructor (or inline) will prevent a null collection being exposed 在构造函数(或内联)中初始化内部集合将防止暴露空集合

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 将接口映射到具体类 - Mapping Interfaces to concrete classes 统一通用接口和具体类 - unity generic interfaces and concrete classes 类设计,接口或具体类 - Class design, Interfaces or Concrete classes 我可以让实体框架使用具体类而不是接口(用于Web服务序列化) - Can I get the entity framework to use concrete classes instead of interfaces (for web service serialisation) 使用将接口而不是具体类传递给@ChildContent 的 [CascadingParameter]<cascadingvalue> 在 blazor</cascadingvalue> - Passing interfaces instead of concrete classes into @ChildContent's [CascadingParameter] using <CascadingValue> in blazor ExpectedObjects 比较具体的对象,而不仅仅是接口 - ExpectedObjects compares concrete objects instead of only interfaces 当具体类包含其他接口时,如何反序列化接口集合 - How to deserialize collection of interfaces when concrete classes contains other interfaces 为什么我不能将具体类添加到具体类实现的接口列表中? - Why can't I add concrete classes to a list of interfaces which the concrete classes implement? 为什么要为我的具体DataProvider类创建接口 - Why should I create interfaces for my concrete DataProvider classes 更喜欢在接口上嵌入依赖关系,还是将其交给具体的类? - Preferable to embeb dependencies on interfaces or let that job to the concrete classes?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM