【问题标题】:Avoiding References to Other Assemblies When Using Child Classes of Generic Base Classes使用通用基类的子类时避免引用其他程序集
【发布时间】:2015-12-02 01:20:35
【问题描述】:

假设我有一个具有 3 层(组件)的假设应用程序:DataBizGui。 我的 Biz 程序集有一个通用基类,几乎所有其他 Biz 类都继承自:

namespace Biz
{
   // Base Class
   public abstract class BizBase<T>
   {
      public string GetDataName()
      {
         return typeof(T).Name;
      }

      abstract internal protected T GetDataObject();
   }

   // Child Class
   public class Foo: BizBase<Data.Bar>
   {
       override internal protected Data.Bar GetDataObject()
       {
          Data.Bar ret = null;
          ... //Do stuff
          return ret;
       }
   }
}

显然这是一个非常简单的示例,但我们假设 BizBase 中有很多逻辑依赖于 GetDataObect(),并且 Gui 直接使用 Biz 对象。

namespace Gui
{
    public static void Main(params string[] args)         
    {
        Biz.Foo foo = new Biz.Foo();
        Console.WriteLine(foo.GetDataName);    
    }
}

问题

我希望我的Biz 程序集引用我的Data 程序集,我希望我的Gui 引用Biz,但我不希望我的“Gui”程序集有一个参考我的“数据”程序集。不幸的是,如果我在Gui 中不包含对Data 的引用,则会出现编译时错误。

所以我的问题是

  1. 为什么Gui 需要引用DataGetDataObject() 是内部的,所以我不明白为什么需要引用才能编译。
  2. 如何避免让我的“Gui”程序集需要对“数据”的引用?

澄清

  • Gui 层正是您所想的。在我的例子中是 WPF,但它可以很容易地成为控制台应用程序的 Main 方法。
  • Data 类实际上只是实体(在我的例子中是使用 EF 生成的 POCO)。实际的 DbContext 位于未直接引用的单独程序集中(除非使用 IModules 在我的 IoC 容器中注册它)。
  • Biz 类主要存储我的大部分业务和验证逻辑,并且每个都映射到单个 Data 类(Biz.Color 继承自 Biz.BizBase&lt;Data.Color&gt;)。 Biz 类使用IDataStore 服务(即依赖注入的 DbContext)来检索/保存它们使用的特定数据对象。

最初,我有一个非泛型 BizBase 类,但我发现我在每个 Biz 类中复制了很多 CRUD 逻辑,它们与 IDataStore 和它们使用的特定实体类型进行交互。 BizBase 有几个抽象方法(除了正在使用的特定类型的实体)在每个子类中都以相同的方式实现。

通过将基类设为通用,我能够将几乎所有这些逻辑移至基类,但突然间,我的 Biz 类的任何消费者都需要直接引用我的 Data 类(我的实体)。虽然我的实体实际上不包含与数据库交互的任何逻辑,但我不愿意只为需要使用 Biz 对象的任何内容添加对它们的引用。

【问题讨论】:

    标签: .net generics inheritance


    【解决方案1】:

    Biz 层中查看此类型:

    public class Foo: BizBase<Data.Bar>
    

    这会将您的 Data 类型(至少是这个,但您只需要一个)暴露为您的 Biz 类型的部分。所以任何使用Biz 的东西都需要引用Data 才能理解Foo 是什么,因为Foo 具有对Data.Bar 的内在依赖。

    为了实现您正在寻找的分离,Biz 根本不应该引用Data。这意味着它绝对不应公开Data 中的类型,因此要求其所有消费者引用Data


    您对Data 程序集的引用是向后的。引用应该向内指向业务逻辑,而不是向外指向业务逻辑。 (参见“依赖规则”in this article。)

    对于 Stack Overflow 问题,细节可能有点宽泛,但本质上是:

    • Data 应参考 Biz
    • Gui 应参考 Biz
    • Biz 不应引用任何内容。

    当然,那么Biz 需要一种方法来调用Data 中的操作。这就是依赖注入的用武之地。如果Data 实现了Biz 中的接口,那么Biz 可以对这些接口进行编码。依赖注入器(不一定是框架,但也可能是)会在需要时为那些接口提供实现的实例。

    至少从环境配置的角度来看,了解它需要哪些依赖项是应用程序层的责任。 (例如,在App.config 中配置依赖注入器。)在足够简单的应用程序中,依赖注入可以是应用程序本身的一部分。在这些情况下,Gui应该引用Data,因为它需要知道哪些实现用于接口。

    但正如您所指出的,即使在简单的应用程序中,也希望拥有Gui 引用Data。毕竟,这至少会带来Gui 中的代码直接调用Data 中的代码的风险,这会破坏架构。

    相反,考虑与其他三层正交的第四层。 DI 层。你会得到的是:

    • Biz 没有引用任何内容
    • Data 参考Biz
    • DI 引用 BizData
    • Gui 引用 BizDI

    Gui 将在DI 中初始化依赖注入(通常在依赖注入框架的应用程序启动时),然后使用该系统获取基于接口的实现。 Gui 绝不会知道(或关心)实现是什么。它不需要引用那些库。

    当然,运行时输出仍然需要这些库。但是Gui 中的代码永远不会“看到”(以任何设计时或编译时方式)依赖项(Data)。它只会初始化DI,然后通过Biz 执行所有逻辑,并为其提供从DI 获取的实例。


    有关 .NET 中此架构的一些具体示例,请查看(有些旧的)presentation I put together here。演示文稿are available here 中引用的代码示例。 (注意:我真的应该更新这些示例以使用实体框架。是的,它已经很老了。我确实有一些简单的代码示例in an article here,它演示了一些应用于 EF 的相同概念。)

    【讨论】:

    • 我添加了一些关于每个程序集的用途的说明。看起来(根据您发布的文章)这些参考实际上是正确的方向。但是我正在处理的实际应用程序有超过 3 层,如果可以避免的话,我宁愿不要让我的最外层一直引用到最内层。特别是因为我的任何 Biz 对象的属性/方法都没有公开公开任何 Data 对象。
    • @NuclearProgrammer:这个类在哪个程序集中?:public class Foo: BizBase&lt;Data.Bar&gt; 如果在Biz 中,那么引用显然是向后的,而不是遵循我链接的文章/示例,因为Biz 需要引用Data 以便将其用作类型。这也解释了您看到的问题,因为Biz 不仅引用 Data(它不应该),而且它暴露 @987654380 中的类@ 作为其 自己的 类型的一部分。这会在系统中的任何位置创建对 Data 的硬依赖,从而抵消了分离和注入的所有好处。
    • 感谢您提供文章链接。它非常有帮助,并帮助我发现了我所遇到的误解。我一直认为 Biz 和 Data 是独立的层,而实际上它们都只是我的“模型”层的一部分。这里重要的是,我的示例中的 Data 实际上并不依赖于任何框架。这是因为我的 DbContext 实际上是在一个完全独立的程序集中(我的“DataAccess”程序集,我在原始示例中省略了它以保持简单)并用作我的数据对象(实际上只是模型)的存储库
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-11-27
    • 1970-01-01
    • 1970-01-01
    • 2023-04-08
    • 1970-01-01
    • 2021-02-28
    • 1970-01-01
    相关资源
    最近更新 更多