通过仅使用原始类型/委托来实现松散耦合

发布于 2024-11-16 20:01:49 字数 1836 浏览 3 评论 0原文

我有一个关于松散耦合和接口的概念/理论问题。

因此,使用接口的一种方法可能是封装某个构造函数所需的参数:

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

因此,只要传入的对象实现了契约,一切都会起作用。根据我的理解,这里的主要好处是它可以实现多态性,但我不确定这是否真的与松散耦合有关。

为了论证,我们可以说 IFooInterface 如下:

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

   void DoFoo(string _str); 
}

从松散耦合的角度来看,在上面的构造函数中不要使用 IFooInterface,而是像这样设置 Foo 不是更好吗:

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

因为说我想删除将 Foo 的功能移植到另一个项目中。这意味着其他项目也必须引用 IFooInterface,添加另一个依赖项。但这样我就可以将 Foo 放入另一个项目中,它准确地表达了它工作所需的内容。显然我可以只使用重载的构造函数,但是为了论证我不想和/或不能修改 Foo 的构造函数。

最显着的缺点(至少对我来说)是,如果你有一个带有一堆原始参数的方法,它会变得丑陋且难以阅读。因此,我想到创建一种包装函数,它允许您仍然传递接口而不是所有原始类型:

    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);
        });
    }

这里的想法是,我可以取回一个函数,该函数采用符合以下条件的某个接口的实例 : Foo 的要求,但 Foo 实际上并不知道有关该接口的任何信息。它可以像这样使用:

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

我听说过关于接口应该如何实现松散耦合的讨论,但我想知道这一点。

不知道一些有经验的程序员是怎么想的。

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.

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

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

发布评论

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

评论(2

め七分饶幸 2024-11-23 20:01:49

在您最初的示例中,您非常接近 参数对象 模式,尽管它更常用这里是一个简单的类(通常具有自动属性),没有接口的额外抽象。

通常,当您听说将接口传递给构造函数时,它并不是要替换原语,而是作为依赖项注入的一种形式。我们不直接依赖 MyFooRepository,而是依赖 IFooRepository,这将消除与特定实现的耦合。

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.

迷荒 2024-11-23 20:01:49

我的第一个想法是您没有为 FooStringFooInt< 的设置器提供 ActionAction /代码>,分别。 IFooInterface 的实现可能具有与这些 setter 相关的规则,并且可能需要访问接口上未公开的其他实现细节。

同样,您还应该接受 FuncFuncIFooInterface 的实现可能有以下规则:随着时间的推移,FooStringFooInt 是什么。例如,DoFoo 可能会重新计算这些值;你不能假设它们只是传递到永远不会改变的字段。

更进一步,如果 getter、setter 或 DoFoo 需要访问公共状态,则函数和操作在创建它们时需要关闭同一组变量。那时,您将做一些心理体操来理解变量的生命周期和代表之间的关系。

这种状态和行为的配对正是类所表达的,而实现细节的隐藏正是接口所提供的。将这些概念分解为其组成元素当然是可以实现的,但这也破坏了通过将成员与类型分组而获得的连贯性。

换句话说,你可以给我面条、酱汁、蔬菜和汉堡,但那不是意大利面和肉丸:-)

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

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