Защищенные методы-заглушки, используемые только в целях переопределения, считаются хорошей практикой или нет? - PullRequest
7 голосов
/ 16 декабря 2010

Иногда, когда я расширяю один из моих собственных классов, я хочу (для целей подкласса) «внедрить» одну или две строки кода в середину метода в суперклассе.

Inв этих случаях я иногда добавляю вызов к пустому защищенному методу для переопределения подкласса.

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() вместо dummy-метода.Хотя у него есть несколько недостатков, таких как, например, невозможность прикоснуться к защищенным данным.

Ответы [ 6 ]

9 голосов
/ 16 декабря 2010

Вы только что обнаружили Шаблонный метод шаблон проектирования. Обратите внимание, что обычно методы, составляющие отдельные шаги, являются абстрактными (а не пустыми и защищенными), поэтому подклассы должны их переопределять.

4 голосов
/ 16 декабря 2010

Существует шаблон метода Template .Идея в том, что большая часть работы является обычной, за исключением нескольких битов, которые обрабатываются с помощью метода, реализованного в подклассе.

2 голосов
/ 16 декабря 2010

Да, это законный способ делать вещи; Я использовал это сам.

Единственная проблема, которую я вижу, это не конкретная техника, а тот факт, что вы используете подклассы конкретных (читай: не абстрактных) классов вообще. У подклассов конкретных классов есть много тонких проблем, поэтому я бы рекомендовал вообще их избегать. Смотрите, например http://en.wikipedia.org/wiki/Liskov_substitution_principle для объяснения того, что вы должны сделать, чтобы правильно создать подкласс класса, и проблем, связанных с ним. Также в «Эффективной Java» Блок рекомендует использовать композицию (Элемент 16).

Другим подходом (который позволяет избежать подклассов) будет использование Внедрение зависимостей . Ваш метод будет принимать параметр типа, который реализует интерфейс ISpecificStuff, который определяет метод doSubclassSpecificStuff():

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

Таким образом, любой вызывающий абонент может решить, что должен делать метод. Это устраняет необходимость в создании подклассов. Конечно, вы могли бы вводить с помощью конструктора, если вам это нужно в нескольких методах.

1 голос
/ 17 декабря 2010

Я обычно использую эту технику как способ обработки особых случаев.Я напишу что-то вроде этого:

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

Подкласс, которому не требуется предварительная обработка данных, может просто не перекрывать эту функцию.Подкласс, для которого требуется предварительная обработка, может переопределить функцию.

Если мы ожидали, что все или большинство подклассов потребуется для предварительной обработки, то это должна быть абстрактная функция, чтобы заставить программиста реализовать ее или сделать сознательныйрешение ничего не делать.Но если это просто случайный подкласс, который должен что-то здесь делать, я думаю, что это совершенно правильный подход.

1 голос
/ 16 декабря 2010

Да, это прекрасно. Это пример шаблона Template Method, где вы используете наследование для определения метода, который поддерживает известный «скелет», но может иметь собственную логику.

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

Приведенные выше наследники Ancestor должны определять тело для MustDefineThisStep () и могут по своему выбору переопределять CanOverrideThisStep (), но не могут касаться MustDoExactlyThis (), HideThisStepFromEveryone или самой движущей функции TemplateMethod. Однако, за исключением HideThisStepFromEveryone, все подметоды доступны дочерним классам, поэтому дочерний объект может использовать MustDoExactlyThis () при реализации MustDefineThisStep ().

Это очень распространено; такие конструкции являются причиной того, что языки OO имеют в своем распоряжении такие модификаторы доступа, подобные этим. Шаблон очень полезен для рабочих процессов, обработки файлов и других задач, которые обычно одинаковы, но имеют немного отличающиеся детали реализации.

1 голос
/ 16 декабря 2010

Это выглядит подозрительно для меня. Я думаю, что причина, по которой вам приходится это делать, заключается в недостатке дизайна. Ваш метод, который нужно «разделить», вероятно, делает слишком много. Решение состоит в том, чтобы разбить его на шаги и придать этому шагу doSubclassSpecificStuff особое значение.

Например:

void Live()
{
  BeBorn();
  DoCrazyStuff(); // this can be made protected virtual
  Die();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...