3

I have a conceptual / theoretical question about loose coupling and interfaces.

So one way to use an interface might be to encapsulate the parameters required by a certain constructor:

class Foo
{
    public Foo(IFooInterface foo)
    {
       // do stuff that depends on the members of IFooInterface
    }
}

So as long as the object passed in implements the contract, everything will work. From my understanding the main benefit here is that it enables polymorphism, but I'm not sure whether this really has anything to do with loose coupling.

Lets say for the sake of argument that an IFooInterface is as follows:

interface IFooInterface
{
   string FooString { get; set; }
   int FooInt { get; set; }

   void DoFoo(string _str); 
}

From a loose coupling standpoint, wouldnt it much better to NOT to use an IFooInterface in the above constructor, and instead set up the Foo like so:

class Foo
{
    public Foo(string _fooString, int _fooInt, Action<string> _doFoo)
    {
       // do the same operations
    }
}

Because say I want to drop the functionality of Foo into another project. That means that other project also has to reference IFooInterface, adding another dependency. But this way I can drop Foo into another project and it expresses exactly what it requires in order to work. Obviously I can just use overloaded constructors, but lets say for the sake of argument I dont want to and/or cannot modify Foo's constructors.

The most salient downside (to me atleast) is that if you have a method with a bunch of primitive parameters it gets ugly and hard to read. So I had the idea to create a sort of wrapping function that allows you to still pass in an interface rather than all the primitive types:

    public static Func<T, R> Wrap<T, R>(Func<T, object[]> conversion)
    {
        return new Func<T, R>(t =>
        {
            object[] parameters = conversion(t);

            Type[] args = new Type[parameters.Length];

            for (int i = 0; i < parameters.Length; i++)
            {
                args[i] = parameters[i].GetType();
            }

            ConstructorInfo info = typeof(R).GetConstructor(args);

            return (R)info.Invoke(parameters);
        });
    }

The idea here is that I can get back a function that takes an instance of some interface which conforms to the requirements of Foo, but Foo literally doesnt know anything about that interface. It could be used like so:

public Foo MakeFoo(IFooInterface foo)
{
    return Wrap<IFooInterface, Foo>(f => 
       new object[] { f.FooString, f.FooInt, f.DoFoo })(foo);  
}

I've heard discussion about how interfaces are supposed to enable loose-coupling, but was wondering about this.

Wondering what some experienced programmers think.

Sean Thoman
  • 7,429
  • 6
  • 56
  • 103
  • 3
    I think you are making things unnecessarily complicated. – Mitch Wheat Jun 24 '11 at 01:02
  • The goal of loose coupling is *not* that *neither* side should have a reference to the other. Interfaces are a (very) useful language feature. No reason to eschew them. – Kirk Woll Jun 24 '11 at 01:18
  • My impression of loose coupling is that I should be able to take a class (or more abstractly a set of properties / behaviors) and understand its purpose irrespective of some particular context, or, like I said drop it into another project and not see a bunch of messages like "the type or namespace x could not be found". My question isnt whether we should eschew interfaces, my question is whether they are really intended to promote loose coupling, or are they moreso meant for polymorphism / inheritance? – Sean Thoman Jun 24 '11 at 01:24
  • All code exists within a certain context. Loose coupling just means a class shouldn't need to know intimate details of a class on which it depends. Interfaces can help with this by making explicit what public members of a class are meant for public consumption and which are implementation-specific. – dahlbyk Jun 24 '11 at 01:33

2 Answers2

3

In your initial example you're pretty close to the Parameter Object pattern, though it's more common to use a simple class (often with auto-properties) here without the extra abstraction of an interface.

Typically when you hear about passing an interface into a constructor, it's not to replace primitives but as a form of dependency injection. Instead of depending on MyFooRepository directly, one would take a dependency on IFooRepository which would remove the coupling to a specific implementation.

dahlbyk
  • 75,175
  • 8
  • 100
  • 122
  • I guess what I am getting at is that even though you arent coding to a specific implementation, you are coding to a syntactic contract. So there is still tight-coupling to the contract, which of course is better than tight-coupling to an implementation, but is introducing coupling nevertheless. I suppose there could be the possibility of too much loose coupling, in certain cases, but I would think its easier to make changes from loose to tight rather than vice versa. – Sean Thoman Jun 24 '11 at 01:36
  • Well at some point you have to depend on something. The point is to depend on as little as possible. Don't depend on a class if an interface will do. Don't depend on a 20-method interface when a 2-method interface will do. Et cetera. – dahlbyk Jun 24 '11 at 01:38
  • @Sean, If the wheels weren't coupled to your car, it wouldn't go anywhere. – Dan Bryant Jun 24 '11 at 01:40
  • True I agree that you have to depend on something, but sticking to primitives certainly qualifies as using "as little as possible" dont you think? The signature of a method's parameters or a constructor's parameters are essentially a contract in themselves, it seems to me. Though there is a cost to using this method, I acknowledge, potentially in hurting readability and potentially overcomplicating things. Also I'm not sure how often interfaces really end up being used in the way I described (to hold properties + behavior, or moreso just behavior?). – Sean Thoman Jun 24 '11 at 01:53
  • You're exactly right - interfaces are most often used for behavior, not state. A collection of primitive parameters is just state, thus using an interface for a parameter object probably doesn't make sense. – dahlbyk Jun 24 '11 at 03:10
2

My first thought is that you did not provide Action<string> and Action<int> for the setters of FooString and FooInt, respectively. The implementation of IFooInterface may have rules concerning those setters, and may require access to other implementation details not exposed on the interface.

In the same vein, you should accept a Func<string> and Func<int> as well: the implementation of IFooInterface may have rules about what FooString and FooInt are as time progresses. For example, DoFoo may recalculate those values; you can't assume that they are just pass-throughs to fields that never change.

Taking this even further, if the getters, setters, or DoFoo require access to common state, the functions and actions will need to close over the same set of variables when you create them. At that point, you will be doing some mental gymnastics to comprehend the variable lifetimes and the relationships between the delegates.

This pairing of state and behavior is exactly what a class expresses, and the hiding of implementation details is exactly what an interface provides. Breaking those concepts into their component elements is certainly achievable, but it also breaks the coherence gained by grouping the members with a type.

To put it another way, you can give me noodles, sauce, vegetables, and hamburger, but that's not spaghetti and meatballs :-)

Bryan Watts
  • 44,911
  • 16
  • 83
  • 88