【问题标题】:Object Oriented Design - Should first class collections / encapsulated collections implement collection/IEnumerable interfaces?面向对象的设计 - 一流的集合/封装的集合是否应该实现集合/IEnumerable 接口?
【发布时间】:2013-10-17 16:23:35
【问题描述】:

我是 Jeff Bay 的 Object Calisthenics 练习的粉丝:

http://www.xpteam.com/jeff/writings/objectcalisthenics.rtf

通过创建对象来封装集合的实现以及对象集合的迭代,我在实现一流的集合方面取得了很好的成果。

这意味着我有一个 EmployeeList 对象,而不是一个 List。

通常,当我创建这些对象时,我不会实现任何集合接口,因此外部用户无法枚举内部集合,因为我认为这就是重点。然而,一位同事提出了相反的建议 - 实现 ICollect 接口以使其更灵活。

这个重构站点似乎在同一个方向,虽然它并没有完全删除对内部集合的访问:http://sourcemaking.com/refactoring/encapsulate-collection

    // Foreach method
    foreach (var employee in employees)
    {
        if (employee.IsManager)
        {
            managers.Add(employee);
        }
    }

    // Linq method
    managers = employees.Where(e => e.IsManager);


    // Encapsulated iteration method
    managers = employees.GetAllManagers();

    public IEnumerable<Employee> GetAllManagers() // Inside Employees class
    {
        return employees.Where(e => e.IsManager);
    }

我是否说最后一种方法是有利的?例如,您何时希望通过实现 IEnumerable 来公开您的内部集合?

【问题讨论】:

  • GetAllManagers() 确实有意义,但无论如何它都会返回IEnumerable。换句话说,它只是一种方便的方法;它提供了过滤,如果客户有权访问原始集合,客户无论如何都可以自己完成过滤。如果您不希望客户端访问整个集合,这些方法也可用于安全性。
  • 它看起来只是一种(仅仅)方便的方法,上面的(简单)实现。如果实现更复杂,并且您发现多个地方在您的代码库中粘贴了相同的实现行,那么这将违反 DRY,对吗?同样,如果内部集合的实现发生了变化,例如如果它必须去数据库来获取信息,你会希望确保客户端首先批量获取所有员工,而不是对每个员工进行 1 次 sql 调用。这个细节最好包裹在列表对象上,而不是强加给客户端,确定吗?
  • 这取决于您的用户有多成熟。尽管 DRY,您的最后一个带有 Where 子句的示例是存储库对象中非常常见的模式。如果过滤是必要的,这不是多余的。

标签: c# .net oop encapsulation


【解决方案1】:

IEnumerable 可能是一个不错的选择。您可以拥有一个 Employee 对象的私有列表,并将其公开为 IEnumerable,以便人们在循环、LINQ 等中使用它。

由于IEnumerable 不允许您修改被迭代的集合,您不必担心EmployeeList 的使用者会更改内部结构。

提供一个迭代方法并不理想,因为它破坏了POLA,并且需要使用 API 的程序员付出一些努力才能找到他想要调用的方法。仔细想想,在 .NET 中你永远不会有像下面这样的代码。

var list = new List<T>;
foreach (var item in list.GetAllItems())
{
    // Whut? 
}

【讨论】:

  • 为什么我希望人们在循环和 LINQ 中手动迭代它,而不是自己提供方法来做他们想做的事?特别是如果迭代很昂贵并且必须转到数据库等。您最好先批量获取对象然后过滤,您必须在每个客户端调用中实现它,并且再次更好地封装在集合对象上.
  • @MichaelParker,如果您认为您可以提供任何人都想调用您的数据的所有方法,那么我很确定您错了。与其想象某人可能感兴趣的所有具体情况,并认为自己更了解,不如让人们访问信息并让他们决定如何使用它。
  • 我没有意识到你想要更多 active record patternrepository pattern... 我认为这严格来说是一个集合,你可以使用数据库查询的结果创建一次。即使如此,您也可以为消费者隐式缓存从数据库中获得的结果,并过滤/迭代这些结果。
  • @WayneWerner 你是说公开数据比提供行为更好吗?这听起来与 OOP 正好相反。如果你不能在你的对象上封装所有必要的行为,那么我怀疑对象太大而不是单一的责任。
  • @JeffBridgman 没错,我可以随时缓存结果,不幸的是,提供没有显式过滤器的迭代器不允许我针对列表范围的操作进行优化,例如优化数据库查询和批量获取对象(但您可能想要进行许多其他列表范围的优化,甚至不包括数据库访问)。如果行为被显式调用为函数,我可以处理列表范围的行为和优化,这似乎是列表对象存在而不是被绕过的一个很好的理由。
猜你喜欢
  • 1970-01-01
  • 2011-05-06
  • 2017-04-14
  • 1970-01-01
  • 2013-11-12
  • 2011-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多