Ключом к пониманию этого является осознание того, что речь идет не только о Определение vs.внедрение. Речь идет о различных способах описания же существительного:
- наследование класса отвечает на вопрос: «Какой объект является это»
- Внедрение интерфейса отвечает на вопрос: «Что я могу сделать с этим объектом?»
Допустим, вы моделирования кухни. (Извиняюсь заранее за следующие пищевые аналогии, я только что вернулся с обеда ...) У вас есть три основных типа посуды - вилки, ножи и ложки. Они все подходят под посуды категории, поэтому мы будем моделировать, что (я опускаю некоторые скучные вещи, как подпирая полей):
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;
это все описывает данные и функциональные возможности, общие для любой посуды - что он сделан, что он весит (который зависит от конкретного типа) и т. д. Но вы заметите, что абстрактный класс действительно не do ничего. A 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;
Я думаю, что каждый получает идею. Точка (не предназначенная для каламбура) заключается в том, что мы имеем очень мелкомасштабный контроль над всем процессом, и нам не нужно делать никаких компромиссов. Мы используем как наследование , так и интерфейсов здесь, выбор не является взаимоисключающим, просто мы включаем только функциональность в абстрактный класс, который действительно, действительно общий для всех производных типов.
ли или не вы решите использовать абстрактный класс или один или несколько интерфейсов вниз действительно зависит от того, что вам нужно сделать с ним:
type
TDishwasher = class
procedure Wash(utensils : Array of TUtensil);
end;
Это имеет смысл, потому что только посуда идут в посудомоечной машиной, по крайней мере, на нашей очень ограниченной кухне, которая не включает такие предметы роскоши, как блюда или чашки. 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
. Вы всегда можете изменить его, когда вам это нужно - вот в чем смысл использования интерфейса! Подумайте о интерфейсах с высокоуровневой проектной точки зрения, а не с точки зрения управления памятью и временем жизни.
Таким образом, мораль этой истории такова:
Когда требуется объект поддержки некоторых особенностей функциональности, попробуйте использовать интерфейс.
Когда объекты того же семейства и вы хотите их имеют общие черты, наследуют от общего базового класса.
И если применимы обе ситуации, используйте оба варианта!
+1, очень хороший вопрос. – mghie
Связанный вопрос: http://stackoverflow.com/questions/918380/abstract-classes-vs-interfaces-vs-mixins. Конкретный вопрос для Delphi кажется оправданным. – mghie
Я знаю разницу, но на самом деле я чувствую, что чего-то не хватает :) Может быть, компромисс между ними? Что же касается нового объявления: TBase = abstract class ... end; Действует как интерфейс, но без подсчета ссылок. –