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

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

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

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

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. abstract классы должны быть унаследованы.
  2. abstract методы должны быть реализованы в производных типах.
  3. virtual методы могут быть переопределены в производных типах.

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

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

abstract class Sorter {
    public abstract Array sort(Array arr);
}

Затем вы можете реализовать различные алгоритмы, такие как быстрая сортировка, слияние, сортировка в подклассах.

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 является ссылкой на объект типа А.

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

Другие вопросы по тегам