Ключом к пониманию этого является осознание того, что речь идет не только о определении против реализации . Это разные способы описания одного и того же существительного:
- Наследование классов отвечает на вопрос: «Что это за объект?»
- Реализация интерфейса отвечает на вопрос: «Что я могу сделать с этим объектом?»
Допустим, вы моделируете кухню. (Заранее извиняюсь за следующие аналогии с едой, я только что вернулся с обеда ...) У вас есть три основных типа посуды - вилки, ножи и ложки. Все они подпадают под категорию посуда , поэтому мы смоделируем это (я опускаю некоторые скучные вещи, такие как вспомогательные поля):
type
TMaterial = (mtPlastic, mtSteel, mtSilver);
TUtensil = class
public
function GetWeight : Integer; virtual; abstract;
procedure Wash; virtual; // Yes, it's self-cleaning
published
property Material : TMaterial read FMaterial write FMaterial;
end;
Все это описывает данные и функциональные возможности, общие для любой посуды - из чего она состоит, что она весит (зависит от конкретного типа) и т. Д. Но вы заметите, что абстрактный класс на самом деле не делает что угодно. У TFork
и TKnife
на самом деле не так много общего, что вы можете поместить в базовый класс. Технически вы можете Cut
с TFork
, но TSpoon
может быть растяжкой, так как отразить тот факт, что только некоторые посуда могут делать определенные вещи?
Хорошо, мы можем начать расширять иерархию, но она становится грязной:
type
TSharpUtensil = class
public
procedure Cut(food : TFood); virtual; abstract;
end;
Это относится к острым, но что, если мы хотим вместо этого группировать таким образом?
type
TLiftingUtensil = class
public
procedure Lift(food : TFood); virtual; abstract;
end;
TFork
и TKnife
подходят под TSharpUtensil
, но TKnife
довольно паршиво для того, чтобы поднять кусок курицы. В итоге мы либо выбираем одну из этих иерархий, либо просто внедряем всю эту функциональность в общий TUtensil
, и производные классы просто отказываются реализовывать методы, которые не имеют смысла. С точки зрения дизайна, мы не хотим застрять в такой ситуации.
Конечно, реальная проблема заключается в том, что мы используем наследование, чтобы описать, что объект делает , а не то, чем является . Для первых у нас есть интерфейсы. Мы можем очистить этот дизайн много:
type
IPointy = interface
procedure Pierce(food : TFood);
end;
IScoop = interface
procedure Scoop(food : TFood);
end;
Теперь мы можем разобраться, что делают конкретные типы:
type
TFork = class(TUtensil, IPointy, IScoop)
...
end;
TKnife = class(TUtensil, IPointy)
...
end;
TSpoon = class(TUtensil, IScoop)
...
end;
TSkewer = class(TStick, IPointy)
...
end;
TShovel = class(TGardenTool, IScoop)
...
end;
Я думаю, что все понимают. Смысл (без каламбура) в том, что мы имеем очень детальный контроль над всем процессом, и нам не нужно делать никаких компромиссов. Здесь мы используем интерфейсы наследования и , варианты не являются взаимоисключающими, просто мы включаем функциональность только в абстрактный класс, который действительно, общий для всех производных типов.
Независимо от того, решите ли вы использовать абстрактный класс или один или несколько интерфейсов нисходящего потока, зависит от того, что вам нужно с ним делать:
* * 1068
Это имеет смысл, потому что в посудомоечную машину входит только посуда, по крайней мере, на нашей очень ограниченной кухне, в которую не входят такие предметы роскоши, как посуда или чашки. TSkewer
и TShovel
, вероятно, не идут туда, хотя они могут технически участвовать в процессе приема пищи.
С другой стороны:
type
THungryMan = class
procedure EatChicken(food : TFood; utensil : TUtensil);
end;
Это может быть не так хорошо. Он не может есть только с TKnife
(ну, не легко). И требовать как TFork
, так и TKnife
тоже не имеет смысла; что если это куриное крылышко?
В этом гораздо больше смысла:
type
THungryMan = class
procedure EatPudding(food : TFood; scoop : IScoop);
end;
Теперь мы можем дать ему или TFork
, TSpoon
, или TShovel
, и он счастлив, но не TKnife
, который до сих пор является посудой, но на самом деле здесь не помогает.
Вы также заметите, что вторая версия менее чувствительна к изменениям в иерархии классов. Если мы решим изменить TFork
для наследования от TWeapon
, наш человек все равно будет счастлив, пока он реализует IScoop
.
Я также как бы замалчиваю здесь проблему подсчета ссылок, и я думаю, @Deltics сказал это лучше всего; просто то, что у вас есть AddRef
, не означает, что вам нужно делать с ним то же самое, что и TInterfacedObject
. Подсчет ссылок на интерфейсы является своего рода случайной функцией, это полезный инструмент для тех случаев, когда вам это нужно, но если вы собираетесь смешивать интерфейс с семантикой классов (а это часто случается), это не всегда приводит к смысл использовать функцию подсчета ссылок как форму управления памятью.
На самом деле, я бы даже сказал, что большую часть времени вам, вероятно, не нужна семантика подсчета ссылок . Да, там я это сказал. Я всегда чувствовал, что весь подсчет ссылок состоит только в том, чтобы помочь в поддержке автоматизации OLE и тому подобного (IDispatch
). Если у вас нет веских причин для автоматического уничтожения вашего интерфейса, просто забудьте об этом, вообще не используйте TInterfacedObject
. Вы всегда можете изменить его, когда вам это нужно - это смысл использования интерфейса! Подумайте об интерфейсах с точки зрения проектирования высокого уровня, а не с точки зрения управления памятью и временем жизни.
Итак, мораль этой истории:
Когда вам требуется, чтобы объект поддерживал некоторые конкретные функции , попробуйте использовать интерфейс.
Когда объекты принадлежат одному семейству и вы хотите, чтобы они имели общие характеристики , наследовали от общего базового класса.
И если применимы обе ситуации, используйте обе!