【问题标题】:Where should factories live in a project?工厂应该住在项目的什么地方?
【发布时间】:2015-08-11 22:52:29
【问题描述】:

我有一个名为MudEngine.Core 的项目的解决方案。这个项目包含一些基本类,然后是我的领域对象抽象的所有接口。 IWorldIRealm 等接口。

域接口示例

public interface IWorld : IGameComponent, ICloneableComponent<IWorld>
{
    /// <summary>
    /// Gets how many hours it takes to complete one full day in this world.
    /// </summary>
    int HoursPerDay { get; }

    /// <summary>
    /// Gets or sets the game day to real hour ratio.
    /// </summary>
    double GameDayToRealHourRatio { get; set; }

    /// <summary>
    /// Adds a collection of realms to world, initializing them as they are added.
    /// </summary>
    /// <param name="realms">The realms.</param>
    /// <returns>
    /// Returns an awaitable Task
    /// </returns>
    IRealm[] GetRealmsInWorld();

    /// <summary>
    /// Initializes and then adds the given realm to this world instance.
    /// </summary>
    /// <param name="realm">The realm to add.</param>
    /// <returns>Returns an awaitable Task</returns>
    Task AddRealmToWorld(IRealm realm);

    /// <summary>
    /// Creates and initializes a new instance of a realm.
    /// </summary>
    /// <param name="name">The name of the realm.</param>
    /// <param name="owner">The world that owns this realm.</param>
    /// <returns>Returns an initialized instance of IRealm</returns>
    Task<IRealm> CreateRealm(string name, IWorld owner);

    /// <summary>
    /// Adds a collection of realms to world, initializing them as they are added.
    /// </summary>
    /// <param name="realms">The realms.</param>
    /// <returns>Returns an awaitable Task</returns>
    Task AddRealmsToWorld(IEnumerable<IRealm> realms);

    /// <summary>
    /// Removes the given realm from this world instance, deleting the realm in the process.
    /// If it must be reused, you may clone the realm and add the clone to another world.
    /// </summary>
    /// <param name="realm">The realm to remove.</param>
    /// <returns>Returns an awaitable Task</returns>
    Task RemoveRealmFromWorld(IRealm realm);

    /// <summary>
    /// Removes a collection of realms from this world instance.
    /// If any of the realms don't exist in the world, they will be ignored.
    /// The realms will be deleted during the process.
    /// If they must be reused, you may clone the realm and add the clone to another world.
    /// </summary>
    /// <param name="realms">The realms collection.</param>
    /// <returns>Returns an awaitable Task</returns>
    Task RemoveRealmsFromWorld(IEnumerable<IRealm> realms);
}

我还有一个名为 MudEngine.Mud 的项目,它是所有接口的默认实现。

MudEngine.Core 项目包括我的工厂的接口。像IWorldFactoryIRealmFactory 这样的工厂。 IWorld 接口有一个创建方法,它使用给定的IRealmFactory 创建领域并返回它们。

示例工厂接口

/// <summary>
/// Provides methods for creating an instance of an IRealm implementation
/// </summary>
public interface IRealmFactory
{
    /// <summary>
    /// Creates and initializes a new instance of a realm.
    /// </summary>
    /// <param name="name">The name of the realm.</param>
    /// <param name="owner">The world that owns this realm.</param>
    /// <returns>Returns an initialized instance of IRealm</returns>
    Task<IRealm> CreateRealm(string name, IWorld owner);

    /// <summary>
    /// Creates and initializes a new instance of a realm.
    /// </summary>
    /// <param name="name">The name of the realm.</param>
    /// <param name="owner">The world that owns this realm.</param>
    /// <param name="timeZoneOffset">The time zone offset to apply to the realm.</param>
    /// <returns>Returns an initialized instance of IRealm</returns>
    Task<IRealm> CreateRealm(string name, IWorld owner, ITimeOfDay timeZoneOffset);

    /// <summary>
    /// Creates and initializes a new instance of a realm.
    /// All of the children zones will be initialized prior to being added to the realm.
    /// </summary>
    /// <param name="name">The name of the realm.</param>
    /// <param name="owner">The world that owns this realm.</param>
    /// <param name="zones">A collection of zones that will be initialized and added to the realm.</param>
    /// <returns>Returns an initialized instance of IRealm</returns>
    Task<IRealm> CreateRealm(string name, IWorld owner, IEnumerable<IZone> zones);

    /// <summary>
    /// Creates and initializes a new instance of a realm.
    /// All of the children zones will be initialized prior to being added to the realm.
    /// </summary>
    /// <param name="name">The name of the realm.</param>
    /// <param name="owner">The world that owns this realm.</param>
    /// <param name="timeZoneOffset">The time zone offset to apply to the realm.</param>
    /// <param name="zones">A collection of zones that will be initialized and added to the realm.</param>
    /// <returns>Returns an initialized instance of IRealm</returns>
    Task<IRealm> CreateRealm(string name, IWorld owner, ITimeOfDay timeZoneOffset, IEnumerable<IZone> zones);
}

当我在 IWorld 接口上调用 CreateRealm 时,实现使用 IRealmFactory 来创建它。工厂是通过 IWorld 实现的构造函数传入的。

我现在的问题是,工厂实现应该存在于哪里?包含域接口实现的同一个项目为工厂提供实现是常见的,还是应该由消费层(例如演示/单元测试项目)负责实现工厂并使用它们?

目的是这些组件可以根据您正在构建的基于文本的游戏类型进行交换。所以我倾向于每个实现接口的包都有自己的工厂。我关心的是DI设置。分层中的 IoC 容器(服务器/客户端应用程序)需要知道它应该使用每个包中的哪个工厂,而不是仅使用 IoC 容器所属的消费层中定义的工厂。

对此有任何行业标准指导吗?

【问题讨论】:

  • 不要把事情复杂化。 Di Container 本身就是一个工厂,通常在应用程序启动时进行配置。我个人在任何项目/组件中都有 autofac 模块。如果您需要一个真正的工厂,一个您根据某些特定规则创建对象的工厂,那是您的域的一部分,因为它封装了域规则。并且没有任何行业标准。
  • @JohnathonSullinger "取决于您构建的基于文本的游戏类型" => 这是否意味着应用程序的用户是游戏管理员?甚至,他必须为自己的游戏编写模块,然后自己实现IRealmFactory
  • @guillaume31 想要构建 Mud 的人必须是游戏管理员,负责构建内容。他们可以从一系列不同的设置中进行选择,例如 D&D 构建或探路者构建。我为这些类提供了不同的实现作为插件,它们通过编辑器选择要运行的类。如果他们不喜欢可用的东西,他们只需要构建一个自定义的 Irealm。我不想将引擎装箱以阻止人们扩展它。
  • 与问题无关,但为什么你必须将IWorld 传递给IWorld.CreateRealm?你真的会使用一个世界的实例来创建一个属于另一个世界实例的领域吗?

标签: c# domain-driven-design factory-pattern


【解决方案1】:

您似乎正在尝试使用抽象的子域构建域模型(我猜您可以称它们为子域,因为它们是成熟的游戏系统),即用户可以在运行时动态选择一个子域,这会触发一些与子域无关的行为,然后在一段时间内用它做子域特定的东西,完成后可能会跳转到另一个。

虽然根据我的经验,您在“正常”的业务项目中很少遇到这种情况,但我会这样做:

  • 为通用世界和领域管理创建一个总体有界上下文。把IWorldIRealmFactory放在那里。

  • 为每个游戏系统子域创建一个单独的限界上下文。它们可以是整个项目或只是名称空间。在这些中编写IRealmFactory 的实现。

  • 这是我认为使用Container.Resolve(...) 的少数情况之一可能是合法的。在 IoC 配置中创建命名注册并随时解析其中一个以连接对应于一个游戏系统的对象子图。

【讨论】:

  • 我认为这主要回答了我的问题。我正在努力在我的管道中支持中间件,允许其他人交换各种子系统。比如游戏引擎对战系统规则。使用 nuget 引入 D&D 规则或 Pathfinder 规则等。我认为让每个中间件实现工厂是最有意义的。我的域的构建期望 DI 注入所有内容;它不知道容器,因为我将其留给服务器/客户端应用程序。
  • 我不建议从域层调用Container.Resolve(),而是从应用程序或表示层调用。当我说“连接子图”时,我的意思是例如 DnDRealm Application Service -> DnDRealmFactory 域对象 -> DnDRealm Repository 等。
  • 关于“中间件”的概念,它是一个超载的术语,当一切都发生在进程中时,我不确定它是否合适(从您的描述来看)。
猜你喜欢
  • 1970-01-01
  • 2023-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多