【问题标题】:Protected "stub" methods used only for overriding purposes considered good practice or not?仅用于覆盖目的的受保护的“存根”方法是否被认为是好的做法?
【发布时间】:2011-05-26 19:07:01
【问题描述】:

有时当我扩展我自己的一个类时,我想(为了子类的目的)在超类中的方法中间“注入”一两行代码。

在这些情况下,我有时会添加对空保护方法的调用以供子类覆盖。

public void superClassMethod() {

    // some fairly long snippet of code

    doSubclassSpecificStuff();

    // some other fairly long snippet of code

}

// dummy method used for overriding purposes only!
protected void doSubclassSpecificStuff() {
}

当我在同一个班级多次这样做时,我必须说它看起来很尴尬/丑陋,所以我的问题:

  1. 这种为子类“开放”以在被认为是良好做法的方法中间“注入”代码的方式是否合适?
  2. 模式(反模式?)叫什么?
  3. 是否已在任何知名的 API/库中使用过? (请注意,我说的是非抽象类。)
  4. 还有更好的选择吗?

我能想出的唯一选择是使用类似命令模式的东西并有一个setMiddleOfMethodHandler(SomeRunnableHandler),然后调用handler.doSubclassSpecificStuff() 而不是虚拟方法。但在我看来,它有一些缺点,例如无法访问受保护的数据。

【问题讨论】:

  • 我一直这样做,特别是对于“框架”,超级类负责处理大量的家务/管道任务。我认为这是一件好事,所以我很好奇你会得到什么回应。

标签: java inheritance oop


【解决方案1】:

您刚刚发现了Template method 设计模式。请注意,通常构成各个步骤的方法是抽象的(而不是空的和受保护的),因此子类必须覆盖它们。

【讨论】:

  • 对。我说的是非抽象类,但我想它仍然算作“模板方法”!
  • 是的。重点是超类实现了通用算法,子类实现了具体细节。
【解决方案2】:

Template method pattern。那里的想法是大部分工作都是常见的,除了少数位,它们由子类实现的方法处理。

【讨论】:

    【解决方案3】:

    是的,这是一种合法的做事方式;我自己用过。

    我能看到的唯一问题不是具体的技术,而是您使用的是具体(阅读:非抽象)类的子类这一事实。子类化具体类有很多微妙的问题,所以我建议完全避免它。参见例如http://en.wikipedia.org/wiki/Liskov_substitution_principle 解释你必须做什么才能正确子类化一个类,以及所涉及的问题。此外,在“Effective Java”块中推荐使用组合(Item 16)。

    另一种方法(避免子类化)是使用Dependency Injection。您的方法将接受实现接口ISpecificStuff 的类型的参数,该接口指定方法doSubclassSpecificStuff()

    public void superClassMethod(ISpecificStuff specificStuff) {
      ....
      specificStuff.doSubclassSpecificStuff();
      ....
    }
    

    这样,任何调用者都可以决定该方法应该做什么。这避免了子类化的需要。当然,如果你在多个方法中需要它,你可以通过构造函数注入。

    【讨论】:

      【解决方案4】:

      对我来说这看起来很可疑。我认为您最终不得不这样做的原因是设计缺陷。您需要“拆分”的方法可能做得太多。解决方案是将其分步分解,并为“doSubclassSpecificStuff”步骤赋予特定含义。

      例如:

      void Live()
      {
        BeBorn();
        DoCrazyStuff(); // this can be made protected virtual
        Die();
      }
      

      【讨论】:

      • 当然,尽管如此,doCrazyStuff 在超类中仍然是空的,这看起来很尴尬。 (顺便说一句,Java 中的所有方法都是虚拟的 :)
      【解决方案5】:

      是的,完全没问题。这是模板方法模式的一个示例,您可以在其中使用继承来定义维护已知“骨架”但可以具有自定义逻辑的方法。

      public abstract class Ancestor
      {
         protected virtual void CanOverrideThisStep(){...}
      
         protected abstract void MustDefineThisStep();
      
         protected sealed void MustDoExactlyThis(){...}
      
         private void HideThisStepFromEveryone(){...}
      
         public sealed void TemplateMethod()
         {
            ...
            CanOverrideThisStep();
      
            ...
            MustDoExactlyThis();
      
            ...
            MustDefineThisStep();
      
            ...
            HideThisStepFromEveryone();
         }
      }
      

      上述祖先的继承者必须为 MustDefineThisStep() 定义一个主体,并且可以选择覆盖 CanOverrideThisStep(),但不能触及 MustDoExactlyThis()、HideThisStepFromEveryone 或 TemplateMethod 驱动函数本身。但是,除了 HideThisStepFromEveryone 之外,所有子方法都可用于子类,因此子类可以在 MustDefineThisStep() 的实现中使用 MustDoExactlyThis()。

      这很常见;这样的结构是面向对象语言有这样的访问修饰符的原因。该模式对于工作流、文件处理和其他通常相同但实现细节略有不同的任务非常有用。

      【讨论】:

        【解决方案6】:

        我经常使用这种技术来处理特殊情况。我会这样写:

        public void foo()
        {
          theData=getTheData();
          preprocessDataHook(theData);
          putTheData(theData);
        }
        protected void preprocessDataHook(SomeObject theData)
        {
          // Nop. Available for subclasses to override.
        }
        

        不需要预处理数据的子类可以不覆盖这个函数。确实需要预处理的子类可以覆盖该函数。

        如果我们预计所有或大多数子类都需要预处理,那么这应该是一个抽象函数,迫使程序员实现它,或者有意识地决定什么都不做。但如果只是偶尔需要在这里做点什么的子类,我认为这是一个完全有效的方法。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-09-09
          • 2013-07-01
          • 1970-01-01
          • 2011-08-04
          • 2015-10-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多