开发者

Loose coupling via using only primitive types / delegates

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开发者_JAVA技巧 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.


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.


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜