我把所有这些接口放在哪里?

发布于 2024-11-15 10:15:52 字数 245 浏览 2 评论 0原文

我正在尝试通过单元测试来实践。我目前不习惯为类编写接口,除非我预见到某些原因我需要交换不同的实现。好吧,现在我预见到了一个原因:嘲笑。

考虑到我的接口将从少数几个增加到数百个,我脑海中浮现的第一件事是,我应该将所有这些接口放在哪里?我是否只是将它们与所有具体实现混合在一起,还是应该将它们放在子文件夹中。例如,控制器接口应该放在 root/Controllers/Interfaces、root/Controllers 中,还是完全放在其他位置?你有什么建议?

I'm trying to get my feet wet with unit testing. I'm currently not in the habit of writing interfaces for classes unless I foresee some reason I would need to swap in a different implementation. Well, now I foresee a reason: mocking.

Given that I'm going to be going from just a handful of interfaces to perhaps hundreds, the first thing that popped into my head was, Where should I put all these interfaces? Do I just mix them in with all the concrete implementations or should I put them in a sub-folder. E.g., should controller interfaces go in root/Controllers/Interfaces, root/Controllers, or something else entirely? What do you advise?

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

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

发布评论

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

评论(6

懒的傷心 2024-11-22 10:15:52

在讨论组织之前:

好吧,现在我预见到一个原因:嘲笑。

您也可以通过类进行模拟。子类化非常适合作为一种选择,而不是总是创建接口。

接口非常有用 - 但我建议仅在有理由创建接口时才创建接口。我经常看到当类可以正常工作并且在逻辑方面更合适时创建接口。您不需要仅仅为了允许自己模拟实现而创建“数百个接口” - 封装和子类化对此非常有效。

话虽如此,我通常会将接口与类一起组织,因为将相关类型分组到相同的命名空间中往往是最有意义的。主要的例外是接口的内部实现 - 这些可以在任何地方,但有时我会创建一个“内部”文件夹+一个内部命名空间,专门用于“私有”接口实现(以及纯粹内部实现的其他类) )。这有助于我保持主命名空间整洁,因此唯一的类型是与 API 本身相关的主要类型。

Before I discuss organization:

Well, now I foresee a reason: mocking.

You can mock with classes, as well. Subclassing works well for mocking as an option instead of always making interfaces.

Interfaces are incredibly useful - but I would recommend only making an interface if there is a reason to make an interface. I often see interfaces created when a class would work fine and be more appropriate in terms of logic. You shouldn't need to make "hundreds of interfaces" just to allow yourself to mock out implementations - encapsulation and subclassing works quite well for that.

That being said - I typically will organize my interfaces along with my classes, as grouping related types into the same namespaces tends to make the most sense. The main exception is with internal implementations of interfaces - these can be anywhere, but I will sometimes make an "Internal" folder + an Internal namespace that I use specifically for "private" interface implementations (as well as other classes that are purely internal implementation). This helps me keep the main namespace uncluttered, so the only types are the main types relating to the API itself.

烂柯人 2024-11-22 10:15:52

这里有一个建议,如果几乎所有接口都只支持一个类,只需将接口添加到与类本身相同的文件中,位于同一名称空间下。这样,您就不会为接口提供单独的文件,这可能会使项目真正混乱,或者只需要一个用于接口的子文件夹。

如果您发现自己使用相同的接口创建不同的类,我会将接口分解到与该类相同的文件夹中,除非它变得完全不守规矩。但我认为不会发生这种情况,因为我怀疑同一文件夹中是否有数百个类文件。如果是这样,应该根据功能对其进行清理和子文件夹,其余的将自行解决。

Here's a suggestion, if almost all of your interfaces are to support only one class, just add the interface to the same file as the class itself under the same namespace. That way you don't have a separate file for the interface which could really clutter the project or need a sub folder just for interfaces.

If you find yourself creating different classes using the same interface, I would break the interface out into the same folder as the class unless it becomes completely unruly. But I don't think that would happen because I doubt you have hundreds of class files in the same folder. If so, that should be cleaned up and subfoldered according to functionality and the rest will take care of itself.

烙印 2024-11-22 10:15:52

接口编码远远超出了测试代码的范围。它在代码中创造了灵活性,允许根据产品要求换入或换出不同的实现。

依赖注入是对接口进行编码的另一个好理由。

如果我们有一个名为 Foo 的对象,由十个客户使用,现在客户 x 希望让 Foo 以不同的方式工作。如果我们已编码为接口 (IFoo),则只需根据 CustomFoo 中的新要求实现 IFoo 即可。只要我们不改变 IFoo 就没有太多需要。客户 x 可以使用新的 CustomFoo,其他客户可以继续使用旧的 Foo,并且需要进行一些其他代码更改来适应。

然而我真正想说的是接口可以帮助消除循环引用。如果我们有一个对象 X 依赖于对象 Y,并且对象 Y 依赖于对象 X。我们有两个选择:1. 对象 x 和 y 必须位于同一个程序集中,或者 2. 我们必须找到某种方法打破循环引用。我们可以通过共享接口而不是共享实现来做到这一点。

/* Monolithic assembly */
public class Foo
{
    IEnumerable <Bar> _bars;
    public void Qux()
    {
       foreach (var bar in _bars)
       {
           bar.Baz();
       }

    }
    /* rest of the implmentation of Foo */
}

public class Bar
{
    Foo _parent;
    public void Baz()
    {
    /* do something here */
    }
    /* rest of the implementation of Bar */
}

如果 foo 和 bar 具有完全不同的用途和依赖关系,我们可能不希望它们位于同一个程序集中,特别是当该程序集已经很大时。

为此,我们可以在其中一个类(例如 Foo)上创建一个接口,并引用 Bar 中的接口。现在我们可以将该界面放入由 FooBar 共享的第三个程序集中。

/* Shared Foo Assembly */
public interface IFoo
{
    void Qux();
}

/* Shared Bar Assembly (could be the same as the Shared Foo assembly in some cases) */
public interface IBar
{
    void Baz();
}
/* Foo Assembly */
 public class Foo:IFoo
{
    IEnumerable <IBar> _bars;
    public void Qux()
    {
       foreach (var bar in _bars)
       {
           bar.Baz();
       }

    }
    /* rest of the implementation of Foo */
}
/* Bar assembly */
public class Bar:IBar
{
    IFoo _parent;
    /* rest of the implementation of Bar */
    public void Baz()
    {
        /* do something here */
}

我认为还有一种观点认为,将接口与其实现分开,并在发布周期中以明显不同的方式对待这些接口,因为这允许并非全部针对相同源编译的组件之间的互操作性。如果完全对接口进行编码,并且接口只能针对主要版本增量进行更改,而不能针对次要版本增量进行更改,那么相同主要版本的任何组件都应该与相同主要版本的任何其他组件一起工作,而不管次要版本如何。
通过这种方式,您可以拥有一个发布周期较慢的库项目,其中仅包含接口、枚举和异常。

Coding to interfaces goes far beyond being able to test code. It creates flexibility in the code allowing a different implementation to be swapped in or out depending on product requirements.

Dependency Injection is another good reason to code to interfaces.

If we have an object called Foo that is used by ten customers and now customer x wants to have Foo work in a different way. If we have coded to an interface (IFoo) we just need to implement IFoo to the new requirements in CustomFoo. As long as we don't change IFoo there is not much needed. Customer x can use the new CustomFoo and other customers can continue to use old Foo and there need be few other code changes to accommodate.

However the point I really wanted to make is that interfaces can help eliminate circular references. If we have an object X that has a dependency on object Y and object Y has a dependency on object X. We have two options 1. with object x and y have to be in the same assembly or 2. we have to find some way of breaking the circular reference. We can do this by sharing interfaces rather than sharing implementations.

/* Monolithic assembly */
public class Foo
{
    IEnumerable <Bar> _bars;
    public void Qux()
    {
       foreach (var bar in _bars)
       {
           bar.Baz();
       }

    }
    /* rest of the implmentation of Foo */
}

public class Bar
{
    Foo _parent;
    public void Baz()
    {
    /* do something here */
    }
    /* rest of the implementation of Bar */
}

If foo and bar have completely different uses and dependencies we probably do not want them in the same assembly especially if that assembly is already large.

To do this we can create an interface on one of the classes, say Foo, and refer to the interface in Bar. Now we can put the interface in a third assembly shared by both Foo and Bar.

/* Shared Foo Assembly */
public interface IFoo
{
    void Qux();
}

/* Shared Bar Assembly (could be the same as the Shared Foo assembly in some cases) */
public interface IBar
{
    void Baz();
}
/* Foo Assembly */
 public class Foo:IFoo
{
    IEnumerable <IBar> _bars;
    public void Qux()
    {
       foreach (var bar in _bars)
       {
           bar.Baz();
       }

    }
    /* rest of the implementation of Foo */
}
/* Bar assembly */
public class Bar:IBar
{
    IFoo _parent;
    /* rest of the implementation of Bar */
    public void Baz()
    {
        /* do something here */
}

I think there is also an argument for maintaining the interfaces separate from their implementations and treating these sightly differently in the release cycle as this allows interoperability between components that were not all compiled against the same sources. If fully coding to interfaces and if interfaces can only be changed for major version increments and not on minor version increments then any component components of the same major version should work with any other component of the same major version regardless of the minor version.
This way you can have a library project with a slow release cycle containing just interfaces, enums and exceptions.

岁月静好 2024-11-22 10:15:52

这取决于。我这样做:如果您必须添加依赖的第 3 方程序集,请将具体版本移至不同的类库。如果没有,它们可以并排放置在同一目录和命名空间中。

It depends. I do this: If you have to add a dependent 3rd party assembly, move the concrete versions out to a different class library. If not, they can stay side byside in the same directory and namespace.

倾其所爱 2024-11-22 10:15:52

我发现当我的项目中需要数百个接口来隔离依赖关系时,我发现我的设计可能存在问题。当许多接口最终只有一种方法时尤其如此。执行此操作的另一种方法是让您的对象引发事件,然后将您的依赖项绑定到这些事件。举个例子,假设您想模拟持久化数据。一种完全合理的方法是这样做:

public interface IDataPersistor
{
    void PersistData(Data data);
}

public class Foo
{
    private IDataPersistor Persistor { get; set; }
    public Foo(IDataPersistor persistor)
    {
        Persistor = persistor;
    }

    // somewhere in the implementation we call Persistor.PersistData(data);

}

另一种无需使用接口或模拟即可做到这一点的方法是这样做:

public class Foo
{
    public event EventHandler<PersistDataEventArgs> OnPersistData;

    // somewhere in the implementation we call OnPersistData(this, new PersistDataEventArgs(data))
}

然后,在我们的测试中,您可以这样做,而不是创建模拟:

Foo foo = new Foo();
foo.OnPersistData += (sender, e) => { // do what your mock would do here };

// finish your test

我发现这是比过度使用模拟更干净。

I find that when I need hundreds of interfaces in my project to isolate dependencies, I find that there may be an issue in my design. This is especially the case when a lot of these interfaces end up having only one method. An alternative to doing this is to have your objects raise events and then bind your dependencies to those events. For an example, let's say you want to mock out persisting your data. One perfectly reasonable way to do this would be to do this:

public interface IDataPersistor
{
    void PersistData(Data data);
}

public class Foo
{
    private IDataPersistor Persistor { get; set; }
    public Foo(IDataPersistor persistor)
    {
        Persistor = persistor;
    }

    // somewhere in the implementation we call Persistor.PersistData(data);

}

Another way you could do this without using interfaces or mocks would be do do this:

public class Foo
{
    public event EventHandler<PersistDataEventArgs> OnPersistData;

    // somewhere in the implementation we call OnPersistData(this, new PersistDataEventArgs(data))
}

Then, in our test, you can instead of creating a mock do this:

Foo foo = new Foo();
foo.OnPersistData += (sender, e) => { // do what your mock would do here };

// finish your test

I find this to be cleaner than using mocks excessively.

无悔心 2024-11-22 10:15:52

我非常不同意接受的答案。

1:虽然技术上是正确的,但您不需要接口,因为您可以选择模拟具体实现,但您应该创建一个接口,原因有两个。

您可以使用接口扩展代码,如果您没有扩展,一旦收到更改请求,具体实现需要修改。

1.1:
你可以进行TDD(测试驱动开发),无需任何实际实现的代码,只要创建接口来测试即可。这也将迫使您在实施之前考虑代码设计。这是一种很好的编码方法。

1.2:

但我建议仅在有理由创建界面时才创建界面。我经常看到当类可以正常工作并且在逻辑方面更合适时创建接口。

制作界面总是有原因的。因为 SOLID 的开放/关闭原则表明您应该致力于扩展代码而不是修改它。
出于多种原因,这是事实。

1.2.1:
通过这种方式编写新的单元测试会更容易。您只需要依赖于您在代码中测试的具体实现作为主题。 (在具体实现之前,您可以使用模拟)

1.2.2:
当您有具体的实现时,对该具体实现的引用将在整个系统中传播。有了接口,所有的引用都将通过接口来完成,而不是具体的实现。这使得扩展成为可能。

1.2.3
如果你跟进所有“叶子”代码段,遵循原则,如果方法有返回,则该方法不能有副作用,如果方法没有返回,则只能有 1副作用是,您还会自动将代码拆分为 SOLID 的“S”部分,这使得您的单元测试很小,并且非常易于维护。

2:
如果您想编写干净的代码,从技术上讲,接口是必需的。如果你想遵循 SOLID,我不知道如何在没有界面的情况下做到这一点。

当您打破职责时,您还需要有效地组织代码,因为代码解耦得越多,您将拥有的接口和接口的实现就越多。因此,您需要有一个良好的项目管理系统,这样您就不会随机出现“数百个接口”。

书籍、youtube、udemy 等都有非常好的指南。它们会教你这些。 (还有一些可怜的,基本上,当你必须为它们付费时,它们的有用性就会增加)。如果您打算就此做出商业决策,那么至少在您这样做之前,您必须对主题有足够的了解,以确定免费的是否足够好。

I disagree quite a bit with the Accepted answer.

1: While technically correct, you do not NEED an interface because you have the option to mock a concrete implementation, you should make an interface for 2 reasons.

You can extend your code with an interface, concrete implementations require modification, if you do not have an extension, once you get a change request.

1.1:
You can make TDD(Test driven development) without any actual implemented code, as long as you only create interfaces to test. This will also force you to consider code design before you make an implementation. Which is an excellent approach to coding.

1.2:

but I would recommend only making an interface if there is a reason to make an interface. I often see interfaces created when a class would work fine and be more appropriate in terms of logic.

There is always a reason to make an interface. Because SOLID's open/close principle says you should aim for extending your code rather than modifying it.
And this is true for multiple reasons.

1.2.1:
It's easier to write new unit tests this way. You will only need the dependency to the concrete implementation you are testing in your code as a subject. (And before you have a concrete implementation you can use a mock)

1.2.2:
When you have a concrete implementation, the reference to that concrete implementation will be propagated throughout the system. With an interface, all references will be done by interface, not concrete implementation. This makes extension possible.

1.2.3
If you follow up with all "leaf" piece of code, to follow the principle, if the method has a return, the method can't have a side effect, if the method doesn't have a return, it can only have 1 side effect, you will also automatically split your code up into the "S" part of SOLID, this makes your unit tests small, and very easy to maintain.

2:
Interfaces are technically needed, if you want to write clean code. If you want to follow SOLID, I don't see how you can do it, without interfaces.

You will also need to organize your code efficiently when you break about responsibilities, as the more decoupled your code is, the more interfaces and implementations of interfaces, you will have. Thus you need to have a good project management system in place, so you don't have "hundres of interfaces" lying around randomly.

There are so very good guides in books and youtube, udemy, etc. That will teach you this. (and also some poor ones, basically, they increase in usefulness when you have to pay for them in general). You will have to know enough about the subject matter to identify if a free one is good enough, if you plan to make business decision on it, before you do so, at least.

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