Использование абстрактного метода против обычных методов
Я хотел бы знать разницу между двумя соглашениями:
- Создание абстрактного базового класса с абстрактным методом, который будет реализован позже на производных классах.
- Создание абстрактного базового класса без абстрактных методов
но добавив соответствующий метод позже на уровне производных классов.
В чем разница?
6 ответов
Как и интерфейсы, абстрактные классы предназначены для выражения набора известных операций для ваших типов. Однако, в отличие от интерфейсов, абстрактные классы позволяют реализовать общие / общие функциональные возможности, которые могут использоваться любым производным типом. Например:
public abstract class LoggerBase
{
public abstract void Write(object item);
protected virtual object FormatObject(object item)
{
return item;
}
}
В этом действительно простом примере выше я, по сути, сделал две вещи:
- Определен контракт, которому будут соответствовать мои производные типы.
- Предоставляет некоторые функциональные возможности по умолчанию, которые могут быть переопределены при необходимости.
Учитывая, что я знаю, что любой производный тип 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();
}
}
Итак, ключевые части:
abstract
классы должны быть унаследованы.abstract
методы должны быть реализованы в производных типах.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. Но вы не хотите беспокоиться о том, какой тип вы передаете, вы просто хотите, чтобы он делал что-то другое в зависимости от типа, который был переданы. Абстрактные методы позволяют вам делать это, это их единственная цель.