.net 我应该有一个单独的接口程序集吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3363312/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 14:34:20  来源:igfitidea点击:

Should I have a separate assembly for interfaces?

.netnamespacesassemblies

提问by David_001

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

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

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.DataAccessassembly, and within that there's an Interfacesnamespace giving MyCompany.CoolApp.DataAccess.Interfaces).

现在,我个人的感觉是这些接口应该放在同一个程序集中的一个单独的命名空间中(所以我们有一个MyCompany.CoolApp.DataAccess程序集,其中有一个Interfaces命名空间给出 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.

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

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.

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

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.

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

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?

当我构建这个时,自动放入 bin 文件夹的程序集只是那些接口程序集。但是,我在 unity 中的类型映射将不同的接口映射到它们的实际实现。包含我的实现的程序集如何最终出现在 bin 文件夹中?

采纳答案by David_001

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.

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

However, thinking about it further, I can't think of many realy world examples of this practice (eg. do log4netor NUnitprovide 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.

然而,进一步思考,我想不出很多现实世界中这种做法的例子(例如,log4netNUnit 是否提供公共接口程序集,以便消费者可以决定不同的实现?如果是这样,nunit 的其他实现可以我用?)。花了很长时间浏览谷歌,我找到了许多资源。

回答by Adam Houldsworth

The usualexpected?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.

也就是说,我不记得我上次这样做是什么时候了,在@David_001 看来,这不一定是“通常的”。我们倾向于让我们的接口与实现一致,我们最常使用的接口是测试。

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.

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

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...

在部署场景中,如果您更改了实现,理论上您可以只部署该单个 DLL - 这样就可以单独使用 UI 和界面 DLL。如果您将接口和实现一起编译,则可能需要重新部署 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.

另一个好处是您的代码的干净隔离 - 有一个接口(或共享库)DLL 向开发团队中的任何人明确说明在哪里放置新类型等。我不再认为这是一种好处,因为我们没有任何不这样做的问题,无论接口放置在哪里,仍然很容易找到公共合同。

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.

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

回答by Tim Lloyd

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):

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

  1. Shared interfaces.
  2. DTOs.
  3. Exceptions.
  1. 共享接口。
  2. DTO。
  3. 例外。

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.

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

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.

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

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.

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

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 isthe 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.

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

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

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
  --...

回答by Luke Puplett

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.

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

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

另一个疯狂是每个程序集一个命名空间的废话。所以你得到了一个SomeBank.SomeApp.Interfaces命名空间,一切都在里面。

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.

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

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

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

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

? .NET 程序集是一个安全单元、API 范围和部署,并且独立于命名空间。

? 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.

? 拥有许多 DLL 通常意味着要公开很多东西,这样就很难将实际的公共 API 与必须公开的类型成员区分开来,因为它们被随意放入自己的程序集中。

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

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

? 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?

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

? 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'?

? 许多真正的库和框架都有品牌标识,使它们易于讨论,并且它们不会烧毁暗示其用途或模棱两可的名称空间名称,我是否可以使用 Steelcore 之类的“代码名称”而不是通用的陈词滥调来对我的应用程序的组件进行品牌化和令人困惑的术语,错误的“服务”?

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.

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

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.

您应该将 NuGet 包分解为一系列相关包。您的“核心”包应该对其他包的依赖最小。里面的类型按用法相关,相互依赖。

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 samenamespace. Referencing A brings in one set of types and then optionally referencing B supplements the namespace with a bunch more.

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

That's it.

就是这样。

Luke

卢克

回答by Frank B

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.

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

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?

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

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

坦率

回答by Talha Yousuf

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.

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