【问题标题】:Composition of objects vs functions: Should I use one method interfaces or delegates?对象与函数的组合:我应该使用一种方法接口还是委托?
【发布时间】:2013-10-03 21:40:37
【问题描述】:

C# 是一种多范式语言。通过嵌套接口和类,可以获得对象的组合。通过这种方式,可以将某个问题分解为多个简单的问题,从而为每个组件提供自己的难题来解决。同样的技巧可以用稍微不同的方式来完成,可以将 函数 组合起来,每个函数负责解决自己的小任务,而所有的组合和相互关联,它们给出了主要问题的答案.

遵循 SOLID 原则,我发现自己处于这样一种情况,即 95% 的接口只带有一个名为 do-something 的方法,并且接口的名称是 something-doer。实现此类接口的类是 DI'ed 与完成该方法应该做的任何事情所需的几个组件。这种方法基本上是手动关闭一个函数。如果是这样,我为什么不与自然且免费(无需打字)的代表一起去?如果我一直将单个方法接口转换为委托,我将从我的代码中消除 95% 的委托,使其看起来像是用函数式语言编写的。但这似乎是一件正确的事情,除非有一些大胆的理由坚持使用接口。有吗?

更新:

@Fendy,让我争论一下。你说过

“您无法从 BLL 控制委托逻辑”。好吧,委托可以在任何地方定义,而不必在调用者中定义。可以完美地做到这一点:

public namespace Bbl
{
    public static class TestableUtils {
        public static Func<A, B> CreateA2B() {
            Func<A, B> a2b = a => new B(a);
            return a2b;
        }
        public static Func<B, C> CreateB2C() {
            Func<B, C> b2c = b => new C(b);
            return b2c;
        }
        public static Func<A, C> CreateA2C(Func<A, B> a2b, Func<B, C> b2c)
        {
            Func<A, C> a2c = a => b2c(a2b(a));
            return a2c;
        }
    }
}

public namespace Caller
{
    using Bbl;
    public class Demo()
    {
        public void RunMe()
        {
            var a = new A();
            var a2b = TestableUtils.CreateA2B();
            var b2c = TestableUtils.CreateB2C();
            var a2c = TestableUtils.CreateA2C(a2b, b2c);
            var c = a2c(a);
        }
    }
}

“一不小心,到处都是重复代码”,确实如此,要偷懒小心,不要重复做同样的工作。类很容易发生同样的问题。所以代表也不例外。

“如果注入构造函数并使用可变实体,则可能导致有状态接口” 很抱歉,我没有看到您的示例有问题。我的意思是它完全按照它的程序设计。如果你改变实体,这就是你所期望的,不是吗?只需防止在构造函数之外设置 Name 即可确保安全。如果你这样做,那么你的例子中的情况将是不可能的。

基本上,在您列出的 4 点中,没有一个与使用委托相比使用接口/类时遇到更多问题有任何关系。或者我只是没听懂。

【问题讨论】:

  • 请问您是基本基于OOP遵循SOLID原则,还是基于函数式编程,与OOP有不同的基础设计?

标签: c# java design-patterns architecture f#


【解决方案1】:

好吧,原来答案是BIG FAT NO,不能使用委托代替单一方法接口,有两个根本原因:

  1. C# 不支持递归匿名函数。

  2. C# 中没有类型别名,因此函数的签名很快就会变得丑陋且难以管理。

运气不好。

【讨论】:

  • 这是一个迟到的评论,但想向任何读者指出,C# 有所谓的using alias directive,它在一定程度上解决了第 2 点中的问题。基本上,你可以这样做 using MyCustomAction = System.Action&lt;int, int, float&gt;; .但是,这仅适用于您正在处理的文件。
【解决方案2】:

答案是,正如大多数应用程序设计所应用的那样,“it取决于”。

如您所知,委托是在调用者中定义的。这意味着:

  1. 您无法从 BLL 控制委托逻辑。这也意味着函数的NO UNIT TEST
  2. 一不小心就会到处重复代码
  3. 在组合根/构造函数注入处注册委托? read this first
  4. 如果注入构造函数并使用可变实体,会导致有状态接口

第 4 点示例:

下面的例子只展示了构造函数注入可变实体的委托如何导致有状态的实现。

public class Employee
{
    public string Name { get; set; }
}

public class EvilDeleg
{
    public EvilDeleg(Action<Employee> act)
    {
        this.act = act;
    }
    Action<Employee> act;
    public void DoSomething(Employee emp)
    {
        Console.WriteLine(emp.Name); // resulting in "First"
        emp.Name = " Evil";
        act(emp);
        Console.WriteLine(emp.Name); // resulting in " Evil Second"
    }
}

public class TestStatefulInterface
{
    public void ConsumeEvilDeleg()
    {
        Employee emp = new Employee();

        EvilDeleg deleg = new EvilDeleg(k => k.Name = k.Name + " Second");
        emp.Name = "First";

        deleg.DoSomething(emp);
        Console.WriteLine(emp.Name); // resulting in " Evil Second"
    }   
}

那么,什么时候适合在单一方法接口上使用委托?

我的 2 美分:

  1. 该代码没有业务相关代码。您想控制系统的业务方面,而让它匿名控制可能会很痛苦
  2. 代码很可能有 UI / DB - 特定的实现。比如从DataTable转换成Entity,或者Entity转换成UIViewModel
  3. 很简单,不需要单元测试
  4. 在方法中注入,而不是构造函数,否则不要使用可变结构作为输入,或者只使用非参数委托

另外,Mark Seeman 在他的博客中说:

仅当您只需要抽象一个方法时才有效。作为 一旦你的抽象需要第二种方法,你就需要 引入适当的接口,或者最好是抽象基类。

【讨论】:

    【解决方案3】:

    委托几乎是一个单一的方法接口。

    闭包正在创建一个新的嵌套类,在幕后,它只是为您做很多相关的样板文件以使代码更简洁。

    简而言之,创建了委托和 lambdas/闭包 来解决接口和嵌套类在没有这些功能的语言中使用的问题。在这种情况下,您并没有滥用这些功能来使用它们。

    也就是说,如果您愿意,使用类/接口并没有什么问题;它只是更明确一点。它也更强大,如果您所表示的操作比具有单个方法的小类更复杂,它通常很有用,因此它不像委托和 lambdas 完全替换接口。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-10
      • 1970-01-01
      • 2014-02-14
      • 1970-01-01
      • 1970-01-01
      • 2012-01-19
      相关资源
      最近更新 更多