Подтвердить что ты не робот

Каковы преимущества и недостатки использования интерфейсов в Delphi?

Я уже давно использовал классы Delphi, но никогда не использовал интерфейсы. Я уже немного прочитал о них, но хочу узнать больше.

Я хотел бы узнать, какие плюсы и минусы вы столкнулись при использовании интерфейсов в Delphi в отношении кодирования, производительности, ремонтопригодности, четкости кода, разделения слоев и, вообще говоря, любых соображений, о которых вы можете думать.

Спасибо и наилучшие пожелания

4b9b3361

Ответ 1

Все, о чем я могу думать сейчас:

Плюсы:

  • Четкое разделение между интерфейсом и реализацией
  • Сокращенные зависимые единицы
  • Множественное наследование
  • Подсчет ссылок (при желании, может быть отключен)

Минусы:

  • Ссылки на класс и интерфейс нельзя смешивать (по крайней мере, с подсчетом ссылок)
  • Функции Getter и setter, необходимые для всех свойств
  • Подсчет ссылок не работает с круговыми ссылками
  • Отладочные трудности (спасибо габру и Уоррену за то, что указали это)

Ответ 2

Добавление к ответам нескольких преимуществ:

  • Используйте интерфейсы для представления поведения, и каждая реализация поведения будет реализовывать интерфейс.
  • Публикация API: интерфейсы отлично подходят для публикации API. Вы можете опубликовать интерфейс, не выдавая фактической реализации. Таким образом, вы можете делать внутренние структурные изменения без каких-либо проблем с клиентами.

Ответ 3

Все, что я говорю, это то, что интерфейсы БЕЗ подсчета ссылок ОЧЕНЬ ВЫСОКИЙ в моем списке желаний для delphi!!!

- > Реальное использование интерфейсов - это объявление интерфейса. Не возможность подсчета ссылок!

Ответ 4

Есть некоторые SUBTLE минусы для интерфейсов, которые я не знаю, если люди при использовании их считают:

  • Отладка становится сложнее. В отладчике я видел много странных трудностей, вступающих в сопряженные вызовы методов.

  • Интерфейсы в Delphi поставляются с семантикой IUnknown, если вам это нравится или нет, вы зацикливаетесь на том, что подсчет ссылок является поддерживаемым интерфейсом. И, таким образом, с любыми интерфейсами, созданными в мире Delphi, вы должны быть уверены, что правильно обрабатываете подсчет ссылок, а если нет, вы получите утечки. Если вы хотите избежать подсчета ссылок, ваш единственный выбор - переопределить addref/decf и фактически ничего не освобождать, но это не лишено собственных проблем. Я обнаружил, что более загруженные в интерфейсах кодовые базы имеют некоторые из самых трудных для поиска нарушений доступа и утечек памяти, и это, я думаю, потому что очень сложно объединить семантику refcount и семантику delphi по умолчанию (владелец освобождает объекты, а никто другой не делает, и большинство объектов живут на всю жизнь своих родителей.)

  • Плохо сделанные реализации с использованием интерфейсов могут внести некоторые неприятные запахи кода. Например, интерфейсы, определенные в той же единице, которая определяет начальную конкретную реализацию класса, добавляют весь вес интерфейсов, не обеспечивая при этом надлежащего разделения между пользователями интерфейсов и разработчиками. Я знаю, что это не проблема с самими интерфейсами, а скорее из-за того, кто пишет код на основе интерфейса. Пожалуйста, разместите свои объявления интерфейса в единицах, в которых есть только те декларации интерфейса, и избегайте аджания адгезии от единицы к блоку, вызванные замалчиванием объявлений вашего интерфейса в те же единицы, что и классы-разработчики.

Ответ 5

Интерфейсы решают определенные проблемы. Основная функция заключается в... ну,... определении интерфейсов. Чтобы различать определение и реализацию.

Если вы хотите указать или проверить, поддерживает ли класс набор методов - используйте интерфейсы.

Вы не можете сделать это каким-либо другим способом.

(Если все классы наследуются от одного и того же базового класса, тогда абстрактный класс определит интерфейс. Но когда вы имеете дело с разными иерархиями классов, вам нужны интерфейсы для определения методов, которые у вас есть вместе...)

Ответ 6

В основном я использую интерфейсы, когда хочу, чтобы объекты с разными родословными предлагали общую службу. Лучший пример, который я могу представить из собственного опыта, - это интерфейс под названием IClipboard:

IClipboard = interface
  function CopyAvailable: Boolean;
  function PasteAvailable(const Value: string): Boolean;
  function CutAvailable: Boolean;
  function SelectAllAvailable: Boolean;
  procedure Copy;
  procedure Paste(const Value: string);
  procedure Cut;
  procedure SelectAll;
end;

У меня есть куча пользовательских элементов управления, полученных из стандартных элементов управления VCL. Каждый из них реализует этот интерфейс. Когда операция буфера обмена достигает одной из моих форм, она смотрит, поддерживает ли активный элемент управления этот интерфейс и, если да, отправляет соответствующий метод.

Для очень простого интерфейса вы можете сделать это с помощью обработчика событий of object, но как только он становится достаточно сложным, интерфейс работает хорошо. На самом деле я считаю, что это очень хороший аналог. Используйте интерфейс, в котором одно событие of object не будет соответствовать функциям.

Ответ 7

Дополнительная заметка о Минусы: производительность

Я думаю, что многие люди слишком беспечно отказываются от производительности интерфейсов. (Не то, чтобы мне не нравились и не использовали интерфейсы, но вы должны знать, что вы получаете). Интерфейсы могут быть дорогими не только для _AddRef/_Release hit (даже если вы просто возвращаете -1), но также и для того, чтобы эти свойства были НЕОБХОДИМО иметь метод Get. По моему опыту, большинство свойств в классе имеют прямой доступ для аксессуаров чтения (например, свойство Prop1: Integer, чтение FProp1, запись SetProp1). Изменение этого прямого, никакого штрафного доступа к вызову функции может быть значительным ударом по вашей скорости (особенно когда вы начинаете добавлять 10s вызовов свойств внутри цикла.

Например, простой цикл с использованием класса

for i := 0 to 99 do
begin
  j := (MyClass.Prop1 + MyClass.Prop2 + MyClass.Prop3) / MyClass.Prop4;
  MyClass.Update;
  // do something with j
end;

переходит от 0 вызовов функций к 400 вызовам функций, когда класс становится интерфейсом. Добавьте больше свойств в этот цикл, и он быстро становится хуже.

Предел _AddRef/_Release, который вы можете улучшить с помощью некоторых советов (я уверен, что есть другие советы. Это не так):

  • Используйте WITH или назначайте временную переменную, чтобы нести только штраф за _AddRef/_Release за блок кода
  • Всегда передавать интерфейсы с использованием ключевого слова const в функцию (в противном случае вы получаете дополнительный _AddRef/_Release каждый раз, когда вызывается эта функция.

Ответ 8

Единственный случай, когда нам приходилось использовать интерфейсы (помимо данных COM/ActiveX), было то, что нам нужно было множественное наследование, и интерфейсы были единственным способом его получить. В нескольких других случаях, когда мы пытались использовать интерфейсы, у нас были различные проблемы, в основном с подсчетом ссылок (когда к объекту обращались как экземпляр класса, так и через интерфейс).

Поэтому мой совет будет заключаться в том, чтобы использовать их только тогда, когда вы знаете, что они вам нужны, а не когда вы думаете, что это может облегчить вашу жизнь в каком-то аспекте.

Обновление: как напомнил Дэвид, с интерфейсами вы получаете множественное наследование только интерфейсов, а не реализации. Но это было хорошо для наших нужд.

Ответ 9

Помимо того, что другие уже перечислены, большой про интерфейс - это способность их агрегирования.

Я написал сообщение в блоге по этой теме некоторое время назад, которое можно найти здесь: http://www.nexusdb.com/support/index.php?q=intf-aggregation (tl; dr: вы можете иметь несколько объектов, каждый из которых реализует интерфейс, а затем собирает их в совокупность, которая во внешнем мире выглядит как один объект, реализующий все эти интерфейсы).

Вы также можете посмотреть ссылки на "Основы интерфейса" и "Расширенное взаимодействие с интерфейсом и шаблонами", размещенные там.