【问题标题】:Overriding Methods vs Assigning Method Delegates / Events in OOP在 OOP 中覆盖方法与分配方法委托/事件
【发布时间】:2012-12-24 13:20:13
【问题描述】:

这是一个有点奇怪的 oop 问题。我想创建一组对象(在设计时已知),每个对象都有与之关联的某些功能。 我可以通过提供可以包含“委托”的对象属性来做到这一点:

public class StateTransition {
    Func<bool> Condition { get; set; }
    Action ActionToTake { get; set; }
    Func<bool> VerifyActionWorked { get; set; }
}

StateTransition foo = new StateTransition {
    Condition = () => {//...}
    // etc
};

或者,我可以使用一个抽象类并为我要创建的每个对象实现它:

public abstract class StateTransition {
    public abstract bool Condition();
    public abstract void ActionToTake();
    public abstract bool VerifyActionWorked();
}

class Foo : StateTransition {
    public override bool Condition() {//...}
    // etc
}

Foo f = new Foo();

我意识到这两种方法的实际结果(在设计时创建与运行时创建)是完全不同的。

如何选择适合我的应用的方法?

【问题讨论】:

    标签: c# oop design-patterns abstract-class first-class-functions


    【解决方案1】:

    如何选择适合我的应用的方法?

    您的应用程序是否需要定义具有其他不同属性或其他不同方法的新过渡对象?然后创建新的子类,覆盖方法,(“多态性”)更好。

    或者。

    您的应用程序是否需要定义只改变方法行为的转换对象?那么方法委托或方法事件会更好。

    总结

    当您的应用程序需要为不同的子类添加不同的特性(如属性或方法)时,覆盖方法(“多态性”)会更好,而不仅仅是更改方法的实现。

    【讨论】:

      【解决方案2】:

      如何选择适合我的应用的方法?

      具有委托的方法使代码更具功能性,因为它从经典的Object Oriented Programming 技术(如继承多态)转变为Functional Programming 技术(如传递函数) em> 并使用 闭包

      我倾向于在任何可能的地方使用委托方法,因为

      1. 我更喜欢组合而不是继承

      2. 我发现使用委托的方法需要更少的代码来编写

      例如,一个具体的StateTransition 实例可以使用标准.NET 初始化机制在委托闭包 的5 行代码中创建:

      dim PizzaTransition as new StateTransition with {
          .Condition = function() Pizza.Baked,
          .ActionToTake = sub() Chef.Move(Pizza, Plate),
          .VerifyActionWorked = function() Plate.Contains(Pizza)
      }
      
      1. 我发现围绕一个类构建 Fluent API 很容易,其中一组附加方法实现为 扩展方法 或在类内部。

      例如,如果将方法 CreateWhenDoVerify 添加到 StateTransition 类:

      public class StateTransition
          public property Condition as func(of boolean)
          public property ActionToTake as Action
          public property VerifyActionWorked as func(of boolean)
      
          public shared function Create() as StateTransition
              return new StateTransition
          end function
      
          public function When(Condition as func(of boolean)) as StateTransition
              me.Condition = Condition
              return me
          end function
      
          public function Do(Action as Action) as StateTransition
              me.ActionToTake = Action
              return me
          end function
      
          public function Verify(Verify as func(of boolean)) as StateTransition
              me.VerifyActionWorked = Check
              return me
          end function
      end class
      

      那么方法链也可以用来创建具体的StateTransition实例:

      dim PizzaTransition = StateTransition.Create.
          When(function() Pizza.Baked).
          Do(sub() Chef.Move(Pizza, Plate).
          Verify(function() Plate.Contains(Pizza))
      

      【讨论】:

        【解决方案3】:
        • 解决方案 1 有更多的活动部件,这允许更细粒度的关注点分离。您可以让一个对象决定给定StateTransitionCondition 应该是什么,另一个对象定义ActionToTake,等等。或者您可以让一个对象决定所有这些,但基于不同的标准。在大多数情况下,IMO 并不是最有用的方法——尤其是考虑到轻微的额外复杂性成本。

        • 在解决方案 2 中,每个 StateTransition 导数都是一个内聚的整体,它检查条件的方式不能与其执行操作或验证它的方式分开。

        这两种解决方案都可以用来实现控制反转 - 换句话说,它们都允许你说StateTransition 的直接消费者不会控制它将使用哪种风格的StateTransition,但决定而是委托给外部对象。

        【讨论】:

          【解决方案4】:

          第一种方法看起来比原始委托更适合事件,但是......无论如何。

          它们之间的关键因素是:谁来控制发生的事情

          如果调用者可以在那里合法地做任何事情,那么事件方法就可以了。毕竟,系统不会强迫您将Button 子类化为添加单击它时发生的情况(尽管您可以这样做)。

          如果“可能发生的事情”得到很好的控制,并且您不希望每个调用者做不同的事情,那么子类方法更合适。这也避免了每个调用者 必须 告诉它要做什么,而“要做的事情”实际上可能是非常少的选项。基类型方法提供了控制子类的能力,例如通过在基类上仅具有internal 构造函数(以便仅在同一程序集中或在指定的程序集中键入通过[InternalsVisibleTo(...)],可以继承它)。

          您还可以通过以下方式组合两者(覆盖与事件):

          public class StateTransition {
              public event Func<bool> Condition;
              protected virtual bool OnCondition() {
                  var handler = Condition;
                  return handler == null ? false : handler();
              }
              public event Action ActionToTake;
              protected virtual void OnActionToTake() {
                  var handler = ActionToTake;
                  if(handler != null) handler();
              }
              public event Func<bool> VerifyActionWorked;
              protected virtual bool OnVerifyActionWorked() {
                  var handler = VerifyActionWorked;
                  return handler == null ? true : handler();
              }
              // TODO: think about default return values
          }
          

          委托/事件方法要考虑的另一件事是:如果委托是 null,你会怎么做?如果您需要全部 3 个,那么在构造函数中要求全部 3 个将是一个好主意。

          【讨论】:

          • 看来,如果我想创建一个StateTransition 内联(匿名,不命名每一个),我必须使用委托。在 Java 中,我可以声明一个匿名内部类来实现我的接口(因为 Java 没有委托),但在 C# 中没有这样的东西。接口方法会导致数百行无用的class Foo : StateTransition 杂乱无章。这个理由足以使用委托方法吗?
          • @Andrew meh...那里的代码量应该不会有太大变化
          【解决方案5】:

          如果您满足以下条件,委托解决方案会很有用:

          • 希望动态创建对象,即根据某些条件为每个方法选择实现。
          • 想要在对象的生命周期内更改实现。

          对于其他情况,我会推荐面向对象的方法。

          【讨论】:

            【解决方案6】:

            如果您说您的对象在设计时已知,那么听起来您不需要动态地更改对象的行为(在运行时)。所以我认为你没有理由使用“委托”方法。

            【讨论】:

              【解决方案7】:

              如果这更像是一种观点而不是坚如磐石的答案......

              如果您只有一个委托或抽象/虚拟方法,我认为这两者或多或少是等价的。毕竟,您可以将委托视为一种方便的快捷方式,以避免为仅一种方法创建实现接口。

              在这种情况下,如果您有三个方法,基类方法将是最实用的。

              为了使两者完全等价,您可以使用具有空虚方法的非抽象基类,这样,如果派生类不覆盖它,则与具有空委托属性相同。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2021-12-07
                • 1970-01-01
                • 1970-01-01
                • 2010-11-18
                • 1970-01-01
                • 2013-06-07
                • 2012-05-29
                • 1970-01-01
                相关资源
                最近更新 更多