【问题标题】:Design(How-to) of classes containing collections of other classes包含其他类集合的类的设计(操作方法)
【发布时间】:2011-03-08 05:06:47
【问题描述】:

如何设计涉及其他类集合的类?

一般示例:

一个工作区包含许多项目
项目包含大量资源
每个资源可能包含大量文件

所以这里标识的类可以是 Workspace、Project、Resource 和 File。 工作区将具有项目列表。项目将具有资源列表,资源将具有文件列表。当然每个类都有其相关的设置。

现在的基本问题是:
a) 谁创建一个类并将其添加到特定集合中?另一个类或包含该集合的类?
b) 还有如何跟踪特定的集合以及如何存储它们?
c) 谁审核特定集合的更改?
d) 在这种情况下可以应用哪些不同的设计模式?

基本上我想减少不同类之间的耦合。

谢谢大家

【问题讨论】:

标签: design-patterns class-design abstraction


【解决方案1】:

在设计软件时,我发现从类型理论的角度看待事物并了解它引导我的方向很有用。

WorkSpace 的类型为 Project + Project^2 + Project^3...(意味着项目列表中的任何 true 都是 true 工作区)

同样,

项目的类型为 Resource + Resource^2 + Resource^3...

资源的类型为 File + File^2 + File^3 ...

所以在像 C#† 这样的语言中,您可以这样定义 WorkSpace 类:

public class WorkSpace : IList<Project> //the important thing here is that you're declaring that things that are true for a list of Projects is true for a WorkSpace. The WorkSpace class may in fact do other stuff too...
{

}

对于其他类也是如此。

然后在你的客户端代码中你会这样使用它:

foreach (var project in WorkSpace)
{
    //do stuff
}

Projects.Add(new Resource() { file1, file2, file3, file4, /* etc */});

首先考虑类型及其关系。封装是一个低级的管理概念。原则是让相关代码靠近在一起,让不相关的代码相距很远(相隔很远意味着被某种边界分隔或在其后面,例如函数或类或语言可能提供的任何其他边界概念)。

†根据您的发帖历史,我推测您熟悉 C#,但这也适用于其他语言。

【讨论】:

  • +1 表示WorkSpace : IList&lt;Project&gt; 方法。从来没有想过会这样做。
  • 这里更喜欢包含而不是继承。从某种意义上说,工作区并不是真正的项目列表,因为您希望能够在使用项目列表的每个地方都替换工作区。此外,工作区可能也是许多其他内容的列表,例如项目设置列表和用户偏好列表。使用包含意味着更好的封装和更简单的用户界面。
  • @nstoertz 您可以将 WorkSpace 设为 IList 和 IList,因为您正在实现一个接口(尽管我可能不会实现那些其他接口)。您所说的只是项目列表中的所有内容也适用于 WorkSpace,但 WorkSpace 中的所有内容可能不适用于项目列表。此外,虽然实现继承可能很危险(我确实希望默认情况下密封非抽象类)接口继承非常安全且没有争议。
  • IList 派生自非泛型 IEnumerable 接口。如果一个类实现了两个 IList 和 IList,非泛型 IEnumerable-implementation 应该做什么?无论哪种方式,这都不是 IList 或 IList 的客户所期望的。
  • @nikie 您可以将它们实现为显式接口,但就像我说的,我个人不会让类实现这些接口。
【解决方案2】:

创建良好的 OO 设计有点像艺术形式,但需要牢记一些原则。

想想您的用户将如何使用您的界面。 你的用户是谁?只是您,出于特定目的,还是您为不同的客户设计了一些东西。界面设计很难,但是想想你的用户实际上想要如何使用你的界面的真实例子。做到这一点的一种好方法是编写测试,这会迫使您使用自己的东西。另一种方法是查看执行相同操作的现有库并了解它们的使用方式。

保持简单,愚蠢。 也就是说,从调用者的角度来看,让它们保持简单。这适用于封装,您应该只根据需要公开实现的内部。它适用于创建易于理解的一致界面。这意味着您应该避免为每个可以想象的类创建一个对象、创建大量属性并尽可能通用的陷阱。最后一点是特别重要的。作为开发人员,我们之所以优秀,是因为我们具有推测和抽象的能力,但正因为如此,我们经常陷入使系统变得比它们需要的复杂程度更高的陷阱。

考虑所有权。 每个对象要么被另一个拥有,这意味着它应该作为该对象的一部分被创建和销毁,或者它将被另一个引用。您应该相应地设计您的界面。所有权是一种耦合形式,但它通常是正确的设计。它确实简化了您的界面,并减轻了调用者维护自己的对象的负担。

虽然应该知道并使用你的收藏库。 了解您正在使用的任何语言/框架的集合库,并使用它们。此外,尝试使您自己的接口在命名和行为方面与这些库保持一致。

关于审计。 您可以在类中进行一些审计(记录、保存统计信息),但如果您的审计需求很复杂(需要知道数据何时发生变化),使用观察者模式可能会更好。

【讨论】:

    【解决方案3】:

    有很多种关系 - 考虑一下

    • 汽车和车轮
    • 汽车和司机
    • 汽车和注册车主
    • 客户和订单以及订单行
    • 学校和班级以及班级的实例

    如果您查看 UML 建模,您会看到诸如基数和方向之类的概念,以及聚合和组合之间的区别以及与相关对象的生命周期相关的问题。

    因此,我们需要一系列技术和模式来处理不同类型的关系也就不足为奇了。

    关于 d)。有一个最重要的原则Law of Demeter 或最少知识原则。

    然后,一个重要的技术是,封装通过隐藏信息来减少耦合。汽车可能对人的许多细节不感兴趣,所以我们可能在我们的 Person 类上有一个 IDriver 接口, IDriver 提供了汽车关心的特定方法。一般原则是倾向于对接口进行编程。

    之后,我们可以考虑 a)。创建。由于我们倾向于使用接口,因此使用工厂模式通常很有意义。这确实留下了谁给工厂打电话的问题。我们更喜欢:

       IPerson aPerson = myAutomobile.createDriver( /* params */);  
    

      IPerson aPerson = aPersonFactory.create( /* params */);
      myAutomobile.addDriver(aPerson);
    

    这里我觉得很明显,汽车对人的了解不多,因此第二个是更好的职责分工。但是,也许 Orders 可以合理地创建 OrderLines,Classes 创建 ClassInstances?

    b)。注意动向?这就是为什么我们有丰富的 Collection 类集。使用哪些取决于关系的性质(一对一、一对多等)以及我们如何使用它。所以我们根据需要选择Arrays和HashMaps等。对于汽车/车轮,我们甚至可以使用汽车的名称属性——毕竟汽车正好有六个车轮(前左、前右、后左、后右、备用和转向)。如果“存储”是指持久化,那么我们正在研究关系数据库中的外键等技术。 RDBMS 和内存对象之间的映射越来越多地通过 JPA 等良好的持久性机制进行管理。

    c)。审计?我还没有看到专门在关系级别应用审计。显然,汽车.addDriver() 方法可能非常复杂。如果有审核此操作的业务需求,那么很明显这是一个不错的地方。这只是一个围绕谁拥有信息的标准 OO 设计问题。一般原则:“不要重复自己”非常清楚,我们不希望调用 addDriver() 的每个对象都需要记住进行审计,因此这是 Auto 的工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多