我应该有一个单独的接口组件吗?

发布于 2024-09-12 04:08:39 字数 1082 浏览 3 评论 0原文

目前,我们的项目中有相当多的类,并且每个类都实现一个接口,主要是出于 DI 的原因。

现在,我个人的感觉是,这些接口应该放入同一程序集中的单独命名空间中(因此我们有一个 MyCompany.CoolApp.DataAccess 程序集,其中有一个 Interfaces) code> 命名空间提供 MyCompany.CoolApp.DataAccess.Interfaces)。

然而,有人建议这些接口实际上应该在它们自己的程序集中。我的问题是——他们是对的吗?我可以看到有一些好处(例如,其他项目只需要使用接口程序集),但最终所有这些程序集都需要加载。在我看来,可能存在稍微复杂的部署问题,因为 Visual Studio 不会自动将实现程序集拉入目标的 bin 文件夹中。

有这方面的最佳实践指南吗?

编辑:

为了让我的观点更清楚一些:我们已经将 UI、DataAccess、DataModel 和其他东西分离到不同的程序集中。目前,我们还可以轻松地将我们的实现替换为不同的实现,因为我们使用 Unity(IOC 框架)将实现类映射到接口。我应该指出,除了出于多态性和为单元测试创​​建模拟的原因之外,我们永远不会编写同一接口的两个实现。因此,除了单元测试之外,我们目前不会“交换”实现。

我认为将接口与实现放在同一程序集中的唯一缺点是整个程序集(包括未使用的实现)都将被加载。

然而,我可以看到,将它们放在不同的程序集中意味着开发人员不会意外地“新建”实现类,而不是使用 IOC 包装器创建它。

我从答案中没有理解的一点是部署问题。如果我只依赖于接口程序集,我将具有类似以下结构的结构:

MyCompany.MyApplication.WebUI
    References:
        MyCompany.MyApplication.Controllers.Interfaces
        MyCompany.MyApplication.Bindings.Interfaces
        etc...

当我构建此结构时,自动放入 bin 文件夹中的程序集就是那​​些接口程序集。然而,我在统一中的类型映射将不同的接口映射到它们的实际实现。包含我的实现的程序集如何最终出现在 bin 文件夹中?

We currently have quite a few classes in a project, and each of those classes implement an interface, mostly for DI reasons.

Now, my personal feeling is that these interfaces should be put into a separate namespace within the same assembly (so we have a MyCompany.CoolApp.DataAccess assembly, and within that there's an Interfaces namespace giving MyCompany.CoolApp.DataAccess.Interfaces).

However, somebody has suggested that these interfaces should actually be in their own assembly. And my question is - are they right? I can see that there are some benefits (eg. other projects will only need to consume the interface assembly), but at the end of they day all of these assemblies are going to need to be loaded. It also seems to me that there could be a slightly more complex deployment issue, as Visual Studio will not automatically pull the implementing assembly into the target's bin folder.

Are there best practice guidelines for this?

EDIT:

To make my point a little clearer: We already separate UI, DataAccess, DataModel and other things into different assemblies. We can also currently swap out our implementation with a different implementation without any pain, as we map the implementing class to the interface using Unity (IOC framework). I should point out that we never write two implementations of the same interface, except for reasons of polymorphism and creating mocks for unit testing. So we don't currently "swap out" an implementation except in unit tests.

The only downside I see of having the interface in the same assembly as the implementation is that the whole assembly (including the unused implementation) will have been loaded.

I can, however, see the point that having them in a different assembly means that developers won't accidentally "new" the implementing class rather than have it created using the IOC wrapper.

One point I haven't understood from the answers is the deployment issue. If I am just depending on the interface assemblies, I'll have a something like the following structure:

MyCompany.MyApplication.WebUI
    References:
        MyCompany.MyApplication.Controllers.Interfaces
        MyCompany.MyApplication.Bindings.Interfaces
        etc...

When I build this, the assemblies that are automatically put into the bin folder are just those interface assemblies. However, my type mappings in unity map different interfaces to their actual implementations. How do the assemblies that contain my implementations end up in the bin folder?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

浅听莫相离 2024-09-19 04:08:39

通常预期实践是将它们放在自己的程序集中,因为这样使用这些接口的给定项目不需要对这些接口的实现进行硬引用。从理论上讲,这意味着您可以轻松地更换实现,甚至没有痛苦。

也就是说,我不记得上次这样做是什么时候,就 @David_001 的观点而言,这不一定是“通常的”。我们倾向于让我们的接口与实现保持一致,这是我们测试接口的最常见用途。

我认为根据你所生产的产品,可以采取不同的立场。我倾向于生成 LOB 应用程序,这些应用程序需要与其他应用程序和团队进行内部互操作,因此任何给定应用程序的公共 API 都有一些利益相关者。然而,这并不像为许多未知的客户端生成库或框架那么极端,在这些客户端中,公共 API 突然变得更加重要。

在部署场景中,如果您更改了实现,理论上您可以只部署该单个 DLL,从而仅保留 UI 和接口 DLL。如果您将接口和实现一起编译,则可能需要重新部署 UI DLL...

另一个好处是代码的干净隔离 - 拥有接口(或共享库)DLL 明确声明开发中的任何内容团队在哪里放置新类型等。 我不再将此视为一个好处,因为我们没有遇到任何不这样做的问题,无论接口放置在何处,仍然可以轻松找到公共合约。

我不知道是否存在支持或反对的最佳实践,可以说重要的是在代码中,您始终使用接口并且永远不会让任何代码泄漏到使用实现中。

The usual expected? practice is to place them in their own assembly, because then a given project consuming those interfaces doesn't require a hard reference to the implementation of those interfaces. In theory it means you can swap out the implementation with little or no pain.

That said, I can't remember when I last did this, to @David_001's point this isn't necessarily "usual". We tend to have our interfaces in-line with an implementation, our most common use for the interfaces being testing.

I think there are different stances to take depending on what you are producing. I tend to produce LOB applications, which need to interoperate internally with other applications and teams, so there are some stakeholders to the public API of any given app. However, this is not as extreme as producing a library or framework for many unknown clients, where the public API suddenly becomes more important.

In a deployment scenario, if you changed the implementation you could in theory just deploy that single DLL - thus leaving, say, the UI and interface DLLs alone. If you compiled your interfaces and implementation together, you might then need to redeploy the UI DLL...

Another benefit is a clean segregation of your code - having an interfaces (or shared library) DLL explicitly states to any on the development team where to place new types etc. I'm no longer counting this as a benefit as we haven't had any issues not doing it this way, the public contract is still easily found regardless of where the interfaces are placed.

I don't know if there are best practices for or against, the important thing arguably is that in code, you are always consuming the interfaces and never letting any code leak into using the implementation.

羁客 2024-09-19 04:08:39

到目前为止的答案似乎表明,将接口放入自己的程序集中是“通常”的做法。我不同意将不相关的接口放入一个“共享”公共程序集中,因此这意味着我需要为每个“实现”程序集提供 1 个接口程序集。

然而,进一步思考,我想不出这种做法的许多现实世界的例子(例如,做 log4net< /a> 或 NUnit 提供公共接口组件,以便消费者可以决定不同的实现方式?我可以使用 nunit 的实现吗?)。我花了很长时间通过谷歌搜索,找到了很多资源。

The answers so far seem to say that putting the interfaces in their own assembly is the "usual" practice. I don't agree with putting unrelated interfaces into one "shared" common assembly, so this would imply I will need to have 1 interface assembly for each "implementation" assembly.

However, thinking about it further, I can't think of many realy world examples of this practice (eg. do log4net or NUnit provide public interface assemblies so that consumers can then decide on different implementations? If so, what other implementation of nunit can I use?). Spending ages looking through google, I've found a number of resources.

数理化全能战士 2024-09-19 04:08:39

我同意勾选的答案。对你有好处,大卫。事实上,看到答案我松了一口气,以为我要疯了。

我经常在企业 C# 自由职业中看到这种有趣的“笔筒里的笔”模式,人们遵循人群的惯例,团队必须遵守,不遵守就是制造麻烦。

另一个疯狂的是每个程序集一个命名空间的废话。因此,您会得到一个 SomeBank.SomeApp.Interfaces 命名空间,并且所有内容都在其中。

对我来说,这意味着类型分散在命名空间和程序集中,其中包含大量我不关心的东西,必须在各处引用。

至于接口,我什至在我的私人应用程序中都不使用接口; DI 适用于类型、具体的虚拟、基类或接口。我进行相应的选择,并根据类型的用途将类型放置在 DLL 中。

我从来没有遇到过 DI 或后来交换逻辑的问题。

• .NET 程序集是安全性、API 范围和部署的单位,并且独立于命名空间。

• 如果两个程序集相互依赖,则它们不能单独部署和版本控制,而应合并。

• 拥有许多DLL 通常意味着将大量内容公开,因此很难区分实际的公共API 和必须公开的类型成员,因为它们被任意放入自己的程序集中。

• 我的DLL 外部的代码是否需要使用我的类型?

• 开始保守;我通常可以轻松地将类型移出图层,反之则有点困难。

• 我能否将我的功能区域或框架整齐地打包到 NuGet 包中,使其像任何其他包一样完全可选且可版本控制?

• 我的类型是否与功能的交付保持一致,是否可以将它们放置在功能名称空间中?

• 许多真正的库和框架都是品牌化的,使它们易于讨论,并且它们不会消耗暗示其用途或含糊不清的命名空间名称,我可以使用 Steelcore 等“代码名称”而不是通用名称来对我的应用程序的组件进行品牌化吗陈词滥调且令人困惑的术语,呃“服务”?

编辑

这是我今天在开发中看到的被误解的事情之一。太糟糕了。

您有一个 API,因此请将其所有类型放入单个 API 项目中。仅当您需要共享/重用它们时才将它们移出。当您将它们移出时,请将它们直接移至 NuGet 包,该包具有清晰的名称,该名称包含包的意图和焦点。如果你正在为一个名字而苦恼,并考虑使用“Common”,那可能是因为你正在创建一个垃圾场。

您应该将 NuGet 包纳入一系列相关包中。您的“核心”包应该对其他包具有最小的依赖性。里面的类型通过用途相关,并且相互依赖。

然后,您可以为需要额外依赖集的更专业的类型和子类型创建一个新包;更清楚的是:您可以根据库的外部依赖关系来划分库,而不是根据类型的种类或者它是接口还是异常来划分库。

库分解

因此,您可能会将所有类型放在一个大库中,但一些更专业的类型(彩色点)依赖于某些外部库,因此现在您的库需要引入所有这些依赖项。这是不必要的,您应该将这些类型分解为进一步专门的库,以获取所需的依赖项。

包 A 和 B 中的类型可以属于相同命名空间。引用 A 引入一组类型,然后可选地引用 B 用更多类型补充命名空间。

就是这样。

卢克

I agree with the ticked answer. Good for you, David. In fact, I was relieved to see the answer, thought I was going mad.

I see this interesting "pens in a pen pot" pattern in enterprise C# freelance jobs all the time, where people follow the convention of the crowd and the team must conform, and not conforming is making trouble.

The other crazy is the one namespace per assembly nonsense. So you get a SomeBank.SomeApp.Interfaces namespace and everything is in it.

For me, it means types are scattered across namespaces and assemblies containing a whole slew of stuff I don't care about has to be referenced all over the place.

As for interfaces, I don't even use interfaces in my private apps; DI works on types, concrete with virtuals, base classes or interfaces. I choose accordingly and place types in DLLs according to what they do.

I have never had a problem with DI or swapping logic later.

• .NET assemblies are a unit of security, API scope and deployment, and are independent of namespaces.

• If two assemblies depend on each other, then they cannot be deployed and versioned separately and should be merged.

• Having many DLLs often means making lots of stuff public such that it’s hard to tell the actual public API from the type members that had to be made public because they were arbitrarily put in their own assembly.

• Does code outside of my DLL ever need to use my type?

• Start conservative; I can usually easily move a type out a layer, it’s a bit harder the other way.

• Could I neatly package up my feature area or framework into a NuGet package such that it is completely optional and versionable, like any other package?

• Do my types align to the delivery of a feature and could they be placed in a feature namespace?

• Many real libraries and frameworks are branded, making them easy to discuss, and they don’t burn up namespace names that imply its use or are ambiguous, could I brandify the components of my app using 'code names' like Steelcore instead of generic clichéd and confusing terms, errm 'Services'?

Edit

This is one of the misunderstood things I see in development today. It's so bad.

You have an API, so put all its types within the single API project. Move them out only when you have a need to share/reuse them. When you move them out, move them straight to a NuGet package with a clear name that carries the intent and focus of the package. If you're struggling for a name, and considering "Common", its probably because you're creating a dumping ground.

You should factor your NuGet package into a family of related packages. Your "core" package should have minimal dependencies on other packages. The types inside are related by usage and depend on each other.

You then create a new package for the more specialised types and subtypes that require additional sets of dependencies; more clearly: you split a library by its external dependencies, not by the kind of type or whether its an interface or an exception.

Library factoring

So you might stick all your types in a single big library, but some more specialised types (coloured spots) depend on certain external libs so now your library needs to pull-in all these dependencies. That's unnecessary, you should instead break out those types into further specialised libraries that do take the dependencies needed.

Types in package A and B can belong to the same namespace. Referencing A brings in one set of types and then optionally referencing B supplements the namespace with a bunch more.

That's it.

Luke

极度宠爱 2024-09-19 04:08:39

我所遵循的共享类型(我也使用 DI)的模式是拥有一个单独的程序集,其中包含以下应用程序级别概念(而不是进入公共程序集的公共概念):

  1. 共享接口。
  2. DTO。
  3. 例外情况。

通过这种方式,可以管理客户端和核心应用程序库之间的依赖关系,因为客户端不能直接依赖于具体实现,也不能作为添加直接程序集引用然后访问任何旧公共类型的意外结果。

然后,我进行了运行时类型设计,在应用程序启动时或在一组单元测试开始时设置 DI 容器。通过这种方式,实现和我如何通过 DI 改变它们之间有明显的区别。我的客户端模块从来没有直接引用实际的核心库,只有“SharedTypes”库。

我的设计的关键是为客户端(无论是 WPF 应用程序还是 NUnit)提供一个通用的运行时概念,用于设置所需的依赖项,即具体实现或某种模拟\存根。

如果上述共享类型没有被分解出来,而是客户端添加对具有具体实现的程序集的引用,那么客户端很容易以明显和非明显的方式使用具体实现而不是接口。随着时间的推移,很容易逐渐陷入过度耦合,如果不付出大量的努力,更重要的是时间,这几乎是不可能解决的。

更新

通过示例阐明依赖项如何最终出现在目标应用程序中。

在我的情况下,我有一个 WPF 客户端应用程序。我使用 Prism 和 Unity(用于 DI),其中重要的是,Prism 用于应用程序组合。

使用 Prism,您的应用程序程序集只是一个 Shell,功能的实​​际实现驻留在“模块”程序集中(您可以为每个概念模块拥有一个单独的程序集,但这不是必需的,我有一个模块程序集 ATM)。 shell 负责加载模块 - 这些模块的组合就是应用程序。模块使用 SharedTypes 程序集,但外壳引用具体程序集。我讨论的运行时类型设计负责初始化依赖项,这是在 Shell 中完成的。

这样,具有所有功能的模块组件不依赖于具体实现。它们由 shell 加载,并排序依赖项。 shell 引用具体的程序集,这就是它们进入 bin 目录的方式。

依赖关系草图:

Shell.dll <-- Application
  --ModuleA.dll
  --ModuleB.dll
  --SharedTypes.dll
  --Core.dll
  --Common.dll + Unity.dll <-- RuntimeDI

ModuleA.dll
  --SharedTypes.dll
  --Common.dll + Unity.dll <-- RuntimeDI

ModuleB.dll
  --SharedTypes.dll
  --Common.dll + Unity.dll <-- RuntimeDI

SharedTypes.dll
  --...

The pattern I follow for what I call shared types (and I too use DI) is to have a separate assembly which contains the following for application level concepts (rather than common concepts which go into common assemblies):

  1. Shared interfaces.
  2. DTOs.
  3. Exceptions.

In this way dependencies between clients and core application libraries can be managed, as clients can not take a dependency on a concrete implementation either directly or as an unintended consequence of adding a direct assembly reference and then accessing any old public type.

I then have a runtime type design where I set up my DI container at application start, or the start of a suite of unit tests. In this way there is a clear separation between implementations and how I can vary them via DI. My client modules never have a direct reference to the actual core libraries, only the "SharedTypes" library.

The key for my design is having a common runtime concept for clients (be it a WPF application or NUnit) that sets up the required dependencies i.e. concrete implementations or some sort of mocks\stubs.

If the above shared types are not factored out, but instead clients add a reference to the assembly with the concrete implementation, then it is very easy for clients to use the concrete implementations rather than the interfaces, in both obvious and non-obvious ways. It's very easy to gradually end up with over-coupling over time which is near impossible to sort out without a great deal of effort and more importantly time.

Update

To clarify with an example of how the dependencies end up in the target application.

In my situation I have a WPF client application. I use Prism and Unity (for DI) where importantly, Prism is used for application composition.

With Prism your application assembly is just a Shell, actual implementations of functionality reside in "Module" assemblies (you can have a separate assembly for each conceptual Module, but this is not a requirement, I have one Modules assembly ATM). It is the responsibility of the shell to load the Modules - the composition of these Modules is the application. The Modules use the SharedTypes assembly, but the shell references the concrete assemblies. The runtime type design I discussed is responsible for initializing dependencies, and this is done in the Shell.

In this way module assemblies which have all the functionality do not depend on concrete implementations. They are loaded by the shell which sorts the dependencies out. The shell references the concrete assemblies, and this is how they get in the bin directory.

Dependency Sketch:

Shell.dll <-- Application
  --ModuleA.dll
  --ModuleB.dll
  --SharedTypes.dll
  --Core.dll
  --Common.dll + Unity.dll <-- RuntimeDI

ModuleA.dll
  --SharedTypes.dll
  --Common.dll + Unity.dll <-- RuntimeDI

ModuleB.dll
  --SharedTypes.dll
  --Common.dll + Unity.dll <-- RuntimeDI

SharedTypes.dll
  --...
王权女流氓 2024-09-19 04:08:39

最近我一直主张接口与实现分离。

即使团队中有人说“这些接口的 99% 的实现永远不会改变”,也永远不要说永远。

最近,当将大型项目从 EntityFramework 移动到 EntityFrameworkCore 时,拆分库节省了我们的重构工作。我们刚刚使用 EntityFramework 更改了 10 个项目的实现,然后去喝咖啡了。

Recently, I have always advocated separating interfaces from implementation.

Even if someone on the team says "99% of the implementation of these interfaces will never change", never say never.

Splitting libraries saved our refactoring recently when moving a large project from EntityFramework to EntityFrameworkCore. We just changed the implementation in 10 projects with EntityFramework and went to drink coffee.

情话已封尘 2024-09-19 04:08:39

我正在对象浏览器中查看 System.Data.dll (4.0),我可以看到它本身是自治的,不仅包含接口,还包含所有工具类,如 DataSet、DataTable、DataRow、DataColumn 等。此外,浏览一下它所拥有的命名空间列表,例如 System.Data、System.Data.Common、System.Configuration 和 System.Data。 System.Xml,它建议首先将接口包含在自己的程序集中,并将所有相关和必需的代码放在一起,其次,更重要的是在整个应用程序(或框架)中重复使用相同的名称空间,以虚拟地隔离类。

I'm looking at System.Data.dll (4.0) in Object Browser and i can see that it is autonomous in itself with not just interfaces but all instrumental classes like DataSet, DataTable, DataRow, DataColumn etc in it. Moreover, skimming over the list of namespaces it holds like System.Data, System.Data.Common, System.Configuration & System.Xml, it suggests first to have interfaces contained in their own assemblies with all relevant and required code held together and second and more importantly to re-use same namespaces across the overall application (or framework) to seggregate classes virtually as well.

毅然前行 2024-09-19 04:08:39

我知道这个帖子真的非常非常老了,但我对此有一个想法,我想把它放在那里。

我得到了用于“重用”的组件。但是我们不能为我们的特定解决方案更进一步吗?

如果我们的解决方案中有一个程序集,其中包含适当的接口,那么我们可以构建该程序集并在任何有意义的地方使用它,包括重用。

但是,对于我们同一解决方案中的其他项目,为什么不简单地通过 LINK 将接口文件添加到需要定义接口的其他项目中呢?

通过这样做,您的特定项目的部署将得到简化(不需要部署接口程序集)。另一方面,如果您想在不同的解决方案中重用该接口,您可以选择复制接口文件,或者简单地引用接口程序集。

似乎是两全其美。我们可以选择如何获取接口,并且它仍然是版本控制的。

坦率

I know this thread is really, really old, but I have a thought on this that I want to put out there.

I get the assembly for "reuse". But can't we go a step further for our particular SOLUTION?

If we have an assembly in our SOLUTION that has the appropriate interfaces in it, we can build that assembly and use it wherever it makes sense including reuse.

But, for other projects within OUR SAME solution, why not simply add the interface file by LINK to the other projects that need the interface defined?

By doing that, deployment for your particular project will be simplified (don't need to deploy the interface assembly). On the other hand, if you want to reuse the interface in a different solution of yours, you have the choice of copying the interface file, or simply referencing the interface assembly.

Seems like the best of both worlds. We have the choice of how to get the interface, and it is still version controlled.

Frank

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文