использование абстрактного метода против регулярных методов
Я хотел бы знать разницу между двумя соглашениями:
- Создание абстрактного базового класса с помощью абстрактного метода который будет реализован позже на производных классах.
- Создание абстрактного базового класса без абстрактных методов
но добавление соответствующего метода позже на уровне производных классов.
В чем разница?
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
должны быть реализованы в производных типах.virtual
методы могут быть переопределены в производных типах.Во втором случае, поскольку вы не добавляете функциональность к абстрактному базовому классу, вы не можете вызвать этот метод при работе с экземпляром базового класса напрямую. Например, если бы я реализовал
ConsoleLogger.WriteSomethingElse
, я не мог бы вызвать его изLoggerBase.WriteSomethingElse
.
Идея помещения абстрактных методов в базовый класс и последующей реализации их в подклассах состоит в том, что вы можете использовать родительский тип вместо любого конкретного подкласса. Например, вы хотите отсортировать массив. Вы можете определить базовый класс как что-то вроде
Затем вы можете реализовать различные алгоритмы, такие как quicksort, mergesort, heapsort в подклассах.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
- ссылка на объект типа A.Foo(A|B|C|D...)
, гдеFoo
- это метод, который может принимать или тип A, или B, или C, или D. Но вы не хотите беспокоиться о том, какой тип вы передаете, вы просто хотите, чтобы он делал что-то другое на основе о типе, который был передан. Абстрактные методы позволяют вам это делать, это их единственная цель.