Where do I put all these interfaces?

17,492

Solution 1

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.

Solution 2

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.

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

Solution 4

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.

Solution 5

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.

Share:
17,492
devuxer
Author by

devuxer

Updated on June 07, 2022

Comments

  • devuxer
    devuxer almost 2 years

    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?

  • devuxer
    devuxer almost 13 years
    Thanks, Reed, +1. I appreciate the comment about not always needing an interface to mock. I guess I was getting a little uncomfortable with the idea that I'd need to mark all my public methods as virtual (since that seems to be what is required for moq). I had read some answers on here giving me the impression that using virtual too liberally can be a bad idea. But perhaps it's not a major issue. Could you comment on that?
  • Reed Copsey
    Reed Copsey almost 13 years
    @DanM: If you use an interface, instead, you're effectively going to be doing the same thing. Any interface is going to force callvirt to be used. I agree that there is a time and place for it, but mocking tends to have side effects (unfortunately).
  • Matt H
    Matt H almost 13 years
    Disregarding the callvirt issues, using virtual everywhere IS a design issue since it potentially allows classes to modify the existing behavior of other classes. With interfaces, you don't really have that since there is no associated behavior.
  • Reed Copsey
    Reed Copsey almost 13 years
    @Matt H: True - though if you're passing around an interface, you're effectively allowing classes to be injected to replace behavior, as well. If you want to be able to mock out an entire class, you kind of have no choice but to allow this at some level.
  • Yarek T
    Yarek T over 2 years
    Nice idea, but with Nullable compiler check you'll find that you have to inject that handler in constructor, or make it nullable (which may not make sense for the logic), thereby putting you back in the same boat of requiring a new interface.
  • Yarek T
    Yarek T over 2 years
    10 years on, and I'm sitting in front of a project where each class has an interface (for various good reasons listed in other answers). Only reason that they are in separate files is because of a linting rule...
  • Morten Bork
    Morten Bork over 2 years
    An alternative to this advice, which I don't agree with, is to declare all interfaces in the composition root, if you are using DI, and a "service provider". Because composition root, will have all required dependencies set, and if you want to reuse interfaces across projects, this makes more sense