受保护的“存根”方法仅用于重写目的,是否为良好做法?

时间:2010-12-16 20:41:20

标签: java inheritance oop

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

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

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. 还有更好的选择吗?

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

6 个答案:

答案 0 :(得分:9)

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

答案 1 :(得分:4)

Template method pattern。这个想法是很多工作是常见的,除了一些位,由子类实现的方法处理。

答案 2 :(得分:2)

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

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

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

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

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

答案 3 :(得分:1)

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

例如:

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

答案 4 :(得分:1)

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

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()。

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

答案 5 :(得分:1)

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

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

不需要预处理数据的子类就可以不覆盖此函数。需要预处理的子类可以覆盖该函数。

如果我们期望所有或大多数子类都需要预处理,那么这应该是一个抽象函数来强制程序员实现它,或者做出有意识的决定什么都不做。但如果它只是一个需要在这里做某事的偶然子类,我认为这是一种非常有效的方法。