На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! В разделе обсуждаются следующие темы:
1) Процесс разработки программного обеспечения.
2) Определение требований к программному обеспечению.
3) Составные части и процесс проектирования (см. Шаблоны проектирования).
4) Документирование программного продукта(проекта).
5) Руководство разработкой программного обеспечения.
6) Проектирование пользовательского интерфейса.
7) Контроль версий проекта (см. Управление версиями в Subversion, Стратегии использования svn).
Модераторы: ElcnU
  
> Полиморфизм в Delphi , статья для начинающих разработчиков
    Часто в коде начинающих программистов можно встретить громоздкие конструкции, которые с легкостью могли бы быть упрощены, используй автор всю мощь ООП. Особенно часто на начальном этапе возникают проблемы с полиморфизмом. Слово это, уверен, знакомо каждому, а что за ним кроется, понимают не все. Возможно причина в том, что во многих источниках при попытке объяснить этот термин, большой упор делается на раскрытии принципов позднего связывания, его отличиях от раннего связывания, и при этом практически забывается суть – для чего это нужно. В этой небольшой статье я попытаюсь раскрыть смысл понятия “полиморфизм” для тех, кто понимает его не достаточно хорошо.

    Итак, прежде чем начать, введем несколько понятий, которые будут использоваться далее:
    • Базовый класс. Допустим, у нас есть несколько родственных классов с общим предком. Например, это могут быть классы TFileStream, TMemoryStream, TStringStream с общим предком – TStream. Так вот, класс TStream и будем называть базовым для классов, являющихся его потомками.
    • Клиентским кодом будем называть код, использующий тот или иной класс, т.е. вызывающий его методы, обращающийся к свойствам. Другими словами, сам класс как сервер предоставляет клиентам набор определенных функций, клиенты могут их использовать.
    • Интерфейсом класса будем называть совокупность его членов (методов, свойств), объявленных в классе и доступных для клиентского кода. Понятие интерфейса неразрывно связано с понятием инкапсуляции. Воздействовать на объект клиент может только используя объявленный в классе интерфейс. При наследовании классы получают интерфейс своего предка и могут дополнить его собственными членами.

    Примечание: в Delphi, да и во многих других языках программирования, словом “интерфейс” так же называется тип данных, представляющий собой совокупность методов и свойств без их реализации, который может быть реализован в классе. В Delphi такой тип данных объявляется с помощью ключевого слова interface. Дабы не возникало путаницы, везде ниже под словом “интерфейс” мы будем подразумевать именно контракт класса – набор объявленных им членов, доступный клиенту, а не одноименный тип данных.

    Теперь вернемся к полиморфизму и попробуем понять, что это такое и какую функцию он выполняет. Если попытаться дать понятию определение, то можно сказать, что полиморфизм – это возможность одного и того же клиентского кода вести себя по-разному в зависимости от того, с экземпляром какого класса он имеет дело. Для этого клиентский код оперирует только лишь интерфейсом базового класса, а его потомки, по-разному реализуя методы этого интерфейса, обеспечивают и различное поведение. Расшифруем на примере:

    ExpandedWrap disabled
      procedure SaveToStream(Stream: TStream);
      var
        I: Integer;
      begin
        Stream.WriteBuffer(Count, SizeOf(Count));
        for I := 0 to Count – 1 do
          Stream.WriteBuffer(Data[i], SizeOf(Data[i]));
      end;


    Процедура SaveToStream – это полиморфный клиентский код. Он принимает ссылку на поток в качестве параметра и вызывает его методы для записи каких-либо данных. Ссылка на поток передается под видом базового класса TStream, хотя реально при вызове SaveToStream мы можем подставить любого его потомка. Если мы подставим экземпляр TMemoryStream, данные будут записаны в память, если TFileStream – в файл. Наш код может вести себя по-разному в зависимости от того, с каким классом реально имеет дело. В то же время, он нигде не проверяет классы, а просто использует общий для всех классов данной иерархии интерфейс.
    Из этого примера должно быть видно основное назначение полиморфизма – возможность писать обобщенный клиентский код, а всю конкретику переложить на сами классы. Благодаря этому мы избавлены от необходимости писать сложные длинные проверки, построенные на условных операторах, код становится более коротким, понятным и, что не менее важно – расширяемым (в будущем мы можем написать новые потомки от TStream, клиентский код переделывать не придется, т.е. наш код работает даже с классами которых еще нет, но которые обязуются поддерживать заложенный в базовом классе интерфейс).
    Если с практической точки зрения все понятно, то вернемся к теории. Как это работает? Полиморфизм, прежде всего, основан на двух вещах:
    • Наследование интерфейсов. Выше мы говорили, что при наследовании класс получает целиком интерфейс своего предка. Без этого полиморфизм невозможен. Чтобы писать обобщенный код, нужно обращаться к объектам разных типов через их общий интерфейс.
    • Методы позднего связывания. Под ними чаще всего подразумеваются виртуальные методы, объявляемые в Delphi с помощью ключевого слова virtual. Их особенность в том, что наследуя интерфейс с этими методами, класс-потомок может подменить их реализацию. В итоге у нас и получается одинаковый интерфейс при разной реализации, что и означает полиморфное поведение.

    Примечание: для того, чтобы переопределить реализацию виртуального метода, нужно в потомке переобъявить этот метод с ключевым словом “override”. Если забыть указать “override”, то мы не переопределим реализацию существующего в интерфейсе предка метода, а введем в интерфейсе потомка новый метод с тем же именем. В результате, мы не сможем вызвать этот метод через общий интерфейс, любая попытка будет вызывать непереопределенный метод базового класса. Новый же метод будет доступен в итоге только через интерфейс нашего потомка, где мы этот метод ввели, а для полиморфизма обязательным условием является возможность вызова именно через базовый интерфейс. Вот почему важно не забывать указывать “override”, хотя если вы забудете это сделать – среда предупредит вас соответствующим warning-ом.
    Примерно то же самое будет, если не указать ни “virtual”, ни “override”. Без директивы “virtual“ мы вводим в классе статический (не виртуальный) метод, чью реализацию подменить в потомках невозможно. Единственное что можно сделать – объявить в потомке одноименный метод, но это будет именно другой метод (а не тот же метод с другой реализацией) другого интерфейса со всеми вытекающими последствиями. Эту вещь важно понимать.

    Примечание: под наследованием в ООП обычно подразумевают две вещи: наследование интерфейса и наследование реализации. Первое означает, что потомок получает интерфейс своего предка и может дополнить его своими членами. Второе – что реализация методов от предка также переходит потомку, если ничего не предпринять для того, чтобы это изменить. Для осуществления полиморфного поведения достаточно только чтобы потомок унаследовал интерфейс, а реализация в базовом классе иногда не нужна, главное – это возможность ее подменить своей. Для того чтобы не писать реализацию в базовом классе, можно использовать абстрактные методы, объявляемые с помощью ключевого слова “abstract”.


    Последний вопрос, на который стоит ответить – как найти то место в клиентском коде, в котором целесообразно использовать полиморфизм? Выше уже частично был дан ответ на этот вопрос: в тех местах, где вам хочется применить условную логику, заключающуюся в проверке типа класса, и в зависимости от этого реализовать по-разному одно и то же действие. Встретив такое место в коде, подумайте, а не лучше ли в данном случае перейти от условной логики к полиморфизму? Хорошим признаком является наличие оператора “is”. Если ваш код построен на проверках типа с помощью “is” (особенно если они многочисленны), то почти наверняка он нуждается в том, чтобы переделать его с использованием полиморфизма. Хотя не обязательно в условии должен быть именно “is”, вы можете и по другим косвенным признакам определять тип объекта.

    Примеры использования полиморфизма не составляет труда найти почти в любом учебнике по ООП, здесь их приводить нет смысла. Надеюсь, что если после прочтения учебных материалов и разбора примеров из них у вас остались вопросы, эта статья помогла на них ответить.
    Сообщение отредактировано: --Ins-- -
    0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
    0 пользователей:


    Рейтинг@Mail.ru
    [ Script execution time: 0,0196 ]   [ 16 queries used ]   [ Generated: 23.04.24, 23:51 GMT ]