构建大型对象层次结构的模式

8
我有一个涉及相对较大的对象层次结构的问题,如下所示:
- 游戏拥有一个社区经理 - 社区经理拥有一个网络 - 网络拥有许多玩家 - 网络有许多友谊 - 玩家拥有一个策略经理 - 玩家拥有一个记忆 - 玩家拥有一个邻居 - 邻居有很多玩家 - 策略经理有许多策略
如您所见,这个层次结构相对复杂,并且至少在一个地方存在循环(网络有许多代理,每个代理都有一个网络)。目前,我正在使用 GameFactory 类上的静态构造方法来构建整个层次结构,但我相信这是最不灵活的方法!
在构建复杂的对象层次结构方面,使用什么样的模式最好?我已经阅读了关于“工厂方法”、“抽象工厂”和“建造者”设计模式的相关资料,并认为工厂模式之一是适合的,但我真的不知道如何将它应用于如此复杂的层次结构?
我认为我需要为系统的每个部分创建许多工厂,但这将导致一组反映模型类的工厂类。
编辑:从迄今为止给出的建议的讨论中清楚地得知,我应该解释更多关于为什么需要工厂来构建对象层次结构而不是允许构造函数本身构建它们的依赖项。
这是因为我正在使用依赖注入来辅助测试驱动开发。我采用一种模拟主义方法进行测试,这需要能够注入代表对象依赖项的模拟对象,因此我必须避免在任何构造函数中使用 new
从迄今为止给出的建议中,似乎有许多可能的方法:
- 在每个类中创建一个辅助构造函数,用于构建对象构造所需的任何依赖项。这允许依赖注入和简单的对象层次结构构建。 - 对每个域类使用一个工厂类(单个工厂类或层次结构取决于使用的工厂模式类型)来处理其构造。 - 在构建过程中只有某些情况下不同的子类需要构建时,使用这些方法的组合。
我倾向于采取最后一条路线。对于这种情况,使用哪种方法最佳?

最好提出一个新问题(引用此问题),以便更清晰地表达。 - Patrick Karcher
3个回答

4
重要的是要理解为什么需要工厂。与许多“好”的实践一样,工厂可以产生某些优势,这些优势有时是需要的,并且只有在正确使用它们时才能获得。
通常,工厂是对象模型中的另一个类。例如,您经常会得到一个“player”,因为您有一个带有“players”集合的“neighborhood”,并且您将遍历该集合,对玩家进行操作。或者您从一个“network”对象开始。或者(我猜)您已经从中得到了一个“player”,您从中得到了一个“friendship”,并且您从中得到了“player”。因此,“friendship”是“player”对象的工厂,“player”是“friendship”对象的工厂!
你可能需要非域、专用工厂对象。 你可能有一个工厂类作为逻辑起点的对象。通常在一些对象需要以许多棘手的方式实例化时使用,而工厂是组织它们的好方法。有时您的对象需要与数据层、用户和上下文一起实例化,工厂可以维护这些内容,制作您的对象,并使您免于在许多不同的构造函数中重复自己。有时工厂可以自动生成各种工具所需的常见引用。
有一种工厂的用法正在快速增长并值得特别提及。这是使用工厂类向注入依赖添加一层错位的用法,特别是用于单元测试

我的建议是,如果你没有特定的原因需要使用工厂对象,那就不用担心它们。只需使用好的构造函数构建你的对象。然后,自然而然地添加返回相关对象的属性; 这将是你的第一个工厂。这可能是你不想一开始就担心设计模式的一个很好的例子; 设计模式并不能带来成功。你才能带来成功。像各种工厂模式这样的模式可以帮助,但不能替代基本的面向对象思维。

你有特定的原因需要专门的工厂对象吗?


我之前没想到这两者有关联,但是是的,我在模型层级中使用了依赖注入,因为我尝试在整个开发过程中采用行为驱动开发和模拟驱动开发的方法。根据我的理解,注入模拟对象需要使用依赖注入。那么问题就是,是选择使用两个构造函数,一个用于依赖注入,另一个用于构造自身依赖,还是使用工厂来生成和提供依赖。前一种方法常见吗?我更倾向于使用后者。 - tobyclemson
你提到的每个选项都可以很好地使用。我更喜欢构造函数,并且在可能的情况下,我喜欢只有一个构造函数(或者如果有多个构造函数,则依赖项以相同方式注入)。我的大多数对象都是由“工厂”(通常是其他业务对象)创建并返回这些对象,使用构造函数,但构造函数使对象自包含。这具有帮助类成为真正黑盒子的实质优势。 - Patrick Karcher
在我的当前项目中,所有进行数据访问的类都是使用数据对象构建的。页面会自动为我创建它,并将其放置在我的用户上下文对象中,这是大多数其他对象的起点。在整个页面中,我通常不需要手动注入它,因为我有一个大型的对象链创建其他对象。注入在生产环境中很好用(可以很好地池化连接),但在测试中更棒。并且由于它在构造函数中,即使通常情况下它是由系统自动创建的,我也拥有完全的单元级别控制。我喜欢它。 - Patrick Karcher

2
如果没有任何变化,即如果您的构建过程是固定的,我建议采用最简单的方法:让对象自己负责构建其依赖对象,并建立链接(可能是双向的)。
例如:对于具有子项集合的父对象,父对象可以拥有一个addChild方法,以确保双向关系的一致性:
1. 获取子项的先前父项;如果不为null,则从旧父项中删除该子项。 2. 更改子项中的父项字段为新父项 3. 将子项添加到新父项的子项集合中。
如果有任何变化,例如在某些上下文中应使用子类,则必须精确定义变化内容。只有在找到准确需求后,才能出现正确的模式。:-)
我们可以帮助您完成最后一部分,但只有您才能提供所需的信息... ;-)

刚才对Patrick说过,我实际上需要依赖注入,这就是为什么我采用工厂方式的原因。抱歉,在我的最初帖子中没有表明。除此之外,一些领域类确实有所不同,特别是玩家。每个玩家在游戏中都有一种选择结果的方法,目前我有一个玩家类的层次结构。那是否意味着我应该有一个工厂来处理生成玩家,让构造函数处理其他实体呢?我也不确定依赖注入在这里应该怎么用... - tobyclemson
@toby 好的。您是否考虑更新您的问题,为读者提供一些背景信息,并解释为什么选择了某个解决方案? - KLE

2

KLE关于根据关系的静态程度来不同处理是正确的。我想补充一点,通常工厂类会反映对象模型。可以把它们看作是智能对象,它们知道如何连接您模型中不同的类。


这是常见的做法吗?对于每个领域类都有一个工厂类层次结构似乎有些臃肿? - tobyclemson
1
这是相当普遍的。另一种选择是让对象拥有更多的智能,这会导致不同类型的膨胀。最好的例子是期望一个圆形对象知道如何绘制自己。这很快会导致类变得负责所有交互,比如序列化或变形。这样的代码很快就会使类的初始重点变得微不足道。最好将这样的行为集中在单独的类中。最终,使用工厂等工具是帮助您维护代码所需的耦合度和内聚性水平的工具。 - Kelly S. French

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接