использование абстрактного метода против регулярных методов


Я хотел бы знать разницу между двумя соглашениями:

  1. Создание абстрактного базового класса с помощью абстрактного метода который будет реализован позже на производных классах.
  2. Создание абстрактного базового класса без абстрактных методов
    но добавление соответствующего метода позже на уровне производных классов.

В чем разница?

6 4

6 ответов:

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

public abstract class LoggerBase
{
  public abstract void Write(object item);

  protected virtual object FormatObject(object item)
  {
    return item;
  }
}

В этом действительно базовом примере выше я, по существу, сделал две вещи:

  1. определил контракт, которому будут соответствовать мои производные типы.
  2. предоставляет некоторые функции по умолчанию, которые могут быть переопределены, если требуемый.

Учитывая, что я знаю, что любой производный тип LoggerBase будет иметь метод Write, я могу его вызвать. Эквивалентом вышесказанного в качестве интерфейса может быть:

public interface ILogger
{
  void Write(object item);
}

Как абстрактный класс, я могу предоставить дополнительную услугу FormatObject, которую можно опционально переопределить, скажем, если я писал ConsoleLogger, например:

public class ConsoleLogger : LoggerBase
{
  public override void Write(object item)
  {
    Console.WriteLine(FormatObject(item));
  }
}

Помечая метод FormatObject как виртуальный, это означает, что я могу предоставить общую реализацию. Я также могу переопределить его:

public class ConsoleLogger : LoggerBase
{
  public override void Write(object item)
  {
    Console.WriteLine(FormatObject(item));
  }

  protected override object FormatObject(object item)
  {
    return item.ToString().ToUpper();
  }
}

Итак, ключ части:

  1. Классы должны быть унаследованы.
  2. Методы abstract должны быть реализованы в производных типах.
  3. virtual методы могут быть переопределены в производных типах.

Во втором случае, поскольку вы не добавляете функциональность к абстрактному базовому классу, вы не можете вызвать этот метод при работе с экземпляром базового класса напрямую. Например, если бы я реализовал ConsoleLogger.WriteSomethingElse, я не мог бы вызвать его из LoggerBase.WriteSomethingElse.

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

abstract class Sorter {
    public abstract Array sort(Array arr);
}
Затем вы можете реализовать различные алгоритмы, такие как quicksort, mergesort, heapsort в подклассах.
class QuickSorter {
    public Array sort(Array arr) { ... }
}

class MergeSorter {
    public Array sort(Array arr) { ... }
}

Вы создаете объект сортировки, выбрав алгоритм,

Sorter sorter = QuickSorter();

Теперь вы можете пройти sorter вокруг, не выставляя напоказ тот факт, что под капотом у него плывун. Чтобы отсортировать массив, вы говорите

Array sortedArray = sorter.sort(someArray);

Таким образом, детали реализации (какой алгоритм вы используете) отделяются от интерфейса к объекту (тот факт, что он сортирует массив).

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

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

Смысл абстрактных классов обычно заключается в определении некоторого контракта на базовом классе, который затем реализуется дервиированными классами (и в этом контексте важно признать, что интерфейсы являются своего рода "чистыми абстрактными классами").

Хм, Ну, разница в том, что базовый класс будет знать о первом, а не о втором.

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

Очевидно, если базовый класс не имеет этих методов... ты не можешь им позвонить...

Абстрактная функция не может иметь никакой функциональности. Вы в основном говорите, что любой дочерний класс должен дать свою собственную версию этого метода, однако он слишком общий, чтобы даже пытаться реализовать в родительском классе. Виртуальная функция, по сути, говорит: Смотрите, вот функциональность, которая может быть или не быть достаточно хороша для дочернего класса. Поэтому, если он достаточно хорош, используйте этот метод, если нет, то переопределите меня и предоставьте свою собственную функциональность...

И, конечно, если вы переопределяете виртуальный метод, вы всегда можете обратиться к родительскому методу, вызвав base.myVirtualMethod()

Хорошо, когда вы видите такой метод:

A.Foo();
То, что у вас действительно есть (за кулисами), - это подпись, подобная этой.
Foo(A x);

И когда вы вызываете A.Foo(), вы действительно вызываете Foo(this), где this - ссылка на объект типа A.

Теперь, иногда вы хотели бы иметь Foo(A|B|C|D...), где Foo - это метод, который может принимать или тип A, или B, или C, или D. Но вы не хотите беспокоиться о том, какой тип вы передаете, вы просто хотите, чтобы он делал что-то другое на основе о типе, который был передан. Абстрактные методы позволяют вам это делать, это их единственная цель.