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

28

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

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

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

  • 0
    +1, этим вопросом должно быть сообщество вики.
Теги:
interface

9 ответов

26
Лучший ответ

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

Плюсы:

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

Минусы:

  • Ссылки на класс и интерфейс нельзя смешивать (по крайней мере, с подсчетом ссылок)
  • Функции Getter и setter, необходимые для всех свойств
  • Подсчет ссылок не работает с круговыми ссылками
  • Отладочные трудности (спасибо габру и Уоррену за то, что указали это)
  • 3
    Хороший список. Я бы добавил только к минусам - проблемы отладки интерфейса (ссылки не уменьшаются / уменьшаются слишком рано - или при переводе - память не освобождается / память освобождается слишком рано) могут быть довольно грязными делами.
  • 3
    Возможно добавление к минусам (небольшое) снижение производительности использования интерфейса в некоторых случаях. Но в целом реализация собственных интерфейсов Delphi (не COM) очень быстрая и оптимизированная (так же быстро, как виртуальный метод плюс подсчет ссылок).
Показать ещё 9 комментариев
11

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

  • Используйте интерфейсы для представления поведения, и каждая реализация поведения будет реализовывать интерфейс.
  • Публикация API: интерфейсы отлично подходят для публикации API. Вы можете опубликовать интерфейс, не выдавая фактической реализации. Таким образом, вы можете делать внутренние структурные изменения без каких-либо проблем с клиентами.
  • 4
    +1 за публикацию API
  • 1
    Как производители компонентов могли бы жить без интерфейсов ... Но я еще не видел, чтобы кто-то раскрывал API через интерфейсы.
Показать ещё 17 комментариев
9

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

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

  • 5
    Прежде чем кто-нибудь скажет мне о возврате -1 в _AddRef / _Release: нет, это не то, что я имею в виду. Я имею в виду интерфейс, где Delphi даже не называет эти методы!
  • 0
    @Daniel, @Smasher упомянул в своем ответе, что подсчет ссылок можно отключить (я думаю, возвращая -1 в _AddRef / _Release). Почему бы этого не было достаточно? Есть что-нибудь еще, чтобы рассмотреть? Спасибо
Показать ещё 6 комментариев
7

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

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

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

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

  • 0
    +1 за добавление некоторых минусов. Один из недостатков, который мне действительно не нравится, это то, что вы не можете откатить ссылку на интерфейс на ссылку на экземпляр объекта (что присуще, поскольку интерфейс может быть реализован чем-то вроде COM, который не основан на Delphi TObject).
  • 0
    Я полагаю, что вы можете написать «IS check», и если входящий интерфейс IS TMyImplementation, вы можете восстановить TMyImplementation через as cast.
Показать ещё 2 комментария
4

В основном я использую интерфейсы, когда хочу, чтобы объекты с разными родословными предлагали общую службу. Лучший пример, который я могу представить из собственного опыта, - это интерфейс под названием 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 не будет соответствовать функциям.

  • 0
    Это отличная техника, и я только хочу, чтобы среда TAction VCL была основана на интерфейсе. (Если вы не хотите настраивать все свои элементы управления, вы можете вместо этого использовать шаблон Model-GUI-Mediator и использовать посредники для реализации необходимых интерфейсов.)
4

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

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

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

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

  • 1
    Йорн, вы можете проверить, реализует ли класс метод с использованием RTTI, вы также можете вызвать его с помощью RTTI: интерфейсы - не единственный способ. Они более элегантный способ.
  • 2
    @ Космин не так. RTTI долгое время был доступен только для опубликованных методов (не для публики и ниже), поэтому обобщение «можно» неверно.
Показать ещё 1 комментарий
3

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

Я думаю, что многие люди слишком беспечно отказываются от производительности интерфейсов. (Не то, чтобы мне не нравились и не использовали интерфейсы, но вы должны знать, что вы получаете). Интерфейсы могут быть дорогими не только для _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 каждый раз, когда вызывается эта функция.
  • 0
    Цикл выборки - это запах кода, указывающий на то, что ответственность за выполнение расчета находится не в том месте. Например, в этом случае замените j := ... на j := MyClass.CalculateJ; , СОВЕТ: Если профилирование идентифицирует повторные вызовы через интерфейс как проблему с производительностью, рассмотрите возможность рефакторинга, чтобы переместить ответственность в более подходящее место.
1

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

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

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

  • 1
    Хорошие статьи, спасибо за ссылки!
1

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

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

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

  • 0
    Конечно, интерфейсы дают вам только множественное наследование интерфейсов, а не множественное наследование реализаций. Иногда это может означать, что эта техника становится грязной.
  • 0
    Это правильно. Интерфейсы не дают вам множественного наследования.
Показать ещё 13 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню