Безопасное преобразование типов
Класс TDog имеет метод Eat, остутствующий в классе TAnimal. Если myAnimal ссылается на экземпляр класса TDog, требуется возможность вызвать эту функцию.
1) прямое приведение типов
Если его сделать а переменная вдруг содержит экземплр другого типа, то можно получить ошибку времени выполнения или непредсказуемое изменение содержимого в памяти, т.к. компилятор не может определить является ли тип объекта конкретным и существует ли в действительности вызываемый метод.
В данном случае можно воспользоваться механизмом RTTI, каждый объект знает свой тип а также класс своего родителя. И можно обратиться к этой информации, используя оператор is или метод InheritsFrom класса TObject.
Парметрами операции is является объект и тип класса, а результат bool.
///
If myAnimal is TDog then…
////
Результат истина если переменная myAnimal ссылается в данный момент на экземпляр класса TDog или его потомка!!!
После того как мы убедились как переменная myAnimal совместима с типом TDog можно выполнить прямое приведение типов
If myAnimal is TDog then
Begin mydog:=TDog(myAnimal);
LbEat.Caption:=myDog.Eat; //LbEat:=TDog(myAnimal).Eat
Два действия (is и прямое приведение типов) можно объединить на одну операцию as, которая делает то же самое
Mydog:=myAnimal as TDog;
LbEat.Caption:=myDog.Eat;
LbEat.Caption:=(myAnimal as TDog).Eat; //осущесвтялет проверку типа и потом выполняет преобразование типа, но если не тот экземпляр то все плохо
……
If myAnimal is TDog then
(myAnimal as TDog).Eat; // так очень длинно
Вывод: сначало проверим, а потом вручную прямое приведение типов!
Разница между традиционным преобразованием типов и использованием оператора as заключается в том, что последний вариант вызовет исключающую ситуацию, если тип объекта не совпадает с типом класса, к которому его пытаются привезти. Чтобы избежать этой ситации лучше всего использовать операцию is а потом прямое приведение типов(чтобы не выполнять is 2 раза).
Обе операции is и as часто применяются в Дельфи, когда возникает необходимость в написании кода универсального для нескольких компонентов одного типа или даже разных типов, когда компонент передается как параметр методу, обрабатывающему событие.
Пример
Procedure Tform1.Btn1Click(Sendet:Tobject);
Begin
If sender is //button///TEdit//// then…..
End;
Это стандартный прием в Дельфи, некая конструкция программирование, но несмотря на этой следует ограничить применение этих операций лишь несколькими специальными случаями.
В случае, когда есть выбор между полиморфизмом и RTTI предпочтение следует отдавать полиморфизму.
Использование механизма RTTI вместо полиморфизма является плохим стилем программирования, т.к. приводит к снижению производительности программы.
Проверка корректности преобразования типов требует прохождения по всей иерархии классов во время выполнения программы, а для обращения к виртуальному методу требуется только прочитать адрес этого метода из памяти.
Интерфейсы
Определяя абстрактный класс, являющийся базовым классом иерархии можно обнаружить, что разрабатываемый класс является настолько абстрактным что в его состав входят лишь виртуальные функции, не одна из которых не реализована в рамках этого класса. Т.е. класс содержит лишь объявления функций и не содержит не одного определения функций.
Такой, что абстрактный класс можно определить с использованием ключевого слова interface и подобные классы называют интерфейсами.
С формальной точки зрения интерфейс не является классом, хотя может быть похожим на него. Интерфейсы рассматриваются как отдельный элемент …..,обладающий след. особенностями:
1) при работе с объектами интерфейсного типа осуществляется автоматический подсчет ссылок на эти объекты.
Когда ни одной ссылки на такой объект не остается – происходит автоматическое уничтожение этого объекта (подобным образом контролируется в Дельфи использовании длинных строк, управление памятью осуществляется почти автоматически)
2) класс потомок может обладать единственным классом предком, но при этом он может обладать несколькими интерфейсами (реализовывать несколько интерфейсов).
3)Все классы являются потомками класса Tobject’a. Все интерфейсы являются потомками IInterface, при этом формируется современно отдельная иерархия.
До 6 версии Дельфи был IUnnoun. Потом ввели IInterface, для того чтобы подчеркнуть разницу между Microsoft COM и IInterface.
Com – Component Object Library – это технология, определяющая стандартный способ взаимодействия модулей клиента и сервера через специальный интерфейс. Здесь “модуль” обозначает приложение или библиотеку dll, два модуля могут выполняться на одном и том же компьютере или на разных компьютерах локальной сети. Существует множество различных интерфейсов в зависимости от задач, выполняемых клиентом и сервером.
Интерфейсы реализуются на объектах серверах. COM сервер обычно реализует более одного интерфейса. Все серверы обладают рядом object возможностей, т.к. все они должны реализовать интерфейс Iunnoun. Этот интерфейс продублирован в виде IInterface для работы с интрефейсами без COM, это базовый интерфейс, от него наследуется любой интерфейс в Дельфи, в дельфи существует несколько различных классов с готовой реализацией интерфейсов Iunnoun. IInterface, среди которых IInterfacedObject (!) и TComObject.
Интерфейсы поддерживают несколько иную объектно-ориентированную модель чем классы. Объекты, содержащие в своем составе интерфейсы подчиняются полиморфизму для каждого интерфейса, который они поддерживают. Основанная на интерфейсах программная модель мощная, интерфейсы благоприятствуют инкапсуляции и обеспечивают более свободные связи чем наследование. Все современные ООП языки поддерживают средство реализации интерфейсов.
Интерфейсы как классы могут быть только объявлены глобально в программе или модуле.
Type
Имя интер-са = interface (предок)
[‘{GUID}’]
Список членов
End;
GUID – глобальный …идентификатор. Уникальный цифровой идентификатор, формируемый в соответствии с определенными правилами, и назначаемый каждому из зарегистрированных интерфейсов. Чтобы его сгенерировать достаточно нажать комбинацию клавиш ctr+shift+ G.
Type ICanFly = interface
[‘{ }’]
Function Fly : string;
End;
Объявив интерфейс необходимо определить класс, его реализующий.
TAirPlane = class(TInterfacedObject, ICanFly) // чтобы наш класс имел все из //IInterface…
Function Fly:string; virtual;
В дельфи имеется несколько базовых классов, реализующих базовое поведение интерфейса IInterface. Самый простой из них – TInterfacedObject.
Методы, входящие в состав интерфейса нужно реализовать либо как статические, либо как виртуальные, виртуальные можно переопределить в дочерних классах с помощью директивы override.
Если не использовать виртуальные методы, все равно можно включить в состав дочернего класса новую реализацию метода, для этого необходимо заново указать интерфейсный тип в подклассе и заново связать методы интерфейса с новыми версиями статических методов.
Оба подхода использования статических и виртуальных методов одинаково удобные и гибкие, однако, все – таки лучше использовать статические методы. Это связано с особенностями реализации вызова функции интерфейса. Чтобы настроить точки входа интерфейсных функций компилятор вставляет в исполняемый код подпрограммы-заглушки, каждая из которых обеспечивает коррекцию указателя self, и передачу управления соответствующего класса, реализующего интерфейс. Заглушка для статического метода обладает простой структурой, а для виртуального метода устроена сложнее и занимает гораздо больше памяти (до 30 байт) + еще увеличивается таблица VMT этого класса и дочерних классов.
Вывод: Поэтому использование статических методов делает код меньшим по размеру при той же степени полиморфизма
После того как класс TAirPlane будет реализован, приведем пример кода, который будет его использовать.
1 Вариант
var Airplane1:TAirplane;
Begin
Airplane1:=TAirplane.Create;
Airplane1.Fly;
Airplane1.Free;
End;
2 Вариант
Var Flyer1:TCanFly;
Begin
Flyer1:=TAirplane.Create;
Flyer1.Fly;
End;
Как только переменной интерфейсного типа присваивается объект, дельфи автоматически проверяет реализует ли объект данного типа этот интерфейс. Для осуществления такой проверки используется специальная версия операции as. Операцию as можно использовать и явно.
Flyer1:=Tairplane.Create as ICanFly;
//если класс реализует больше 2 интерфейсов.
Операция as для интерфейсов отличается от операции as для классов
Исполняемый код, генерируемый компилятором для операции as при использовании этой операции совместно с интерфейсами отличается от кода, который генерируется компилятором в случае, если операция as используется в отношении класса.
В отношении классов в исполняемый код добавляется осуществляемая в процессе исполнения программы проверка того, что объект принадлежит классу, совместимому с данным.
Если операция as используется в отношении интерфейсов на этапе компиляции проверяется, существует ли возможность извлечь необходимый интерфейс из имеющегося класса.
В обоих случаях, и при явном присваивании, и при использовании операции as, выполняется одна дополнительная операция: происходит обращение к методу _AddRef, определенному в рамках IInterface и реализованному в TInterfacedObject. В рез – е увеличивается счетчик ссылок этого объекта. Как только переменная Flyer1 выйдет из области видимости, вызывается метод _Release, который тоже является частью IInterface и тоже реализован в TInterfacedObject. Он уменьшает счетчик ссылок и проверяет не равен ли он нулю, и если необходимо уничтожает объект.
Отсутствует необходимость удалять объект самостоятельно.
Замечание: если мы работаем с объектами, основанными на интерфейсах, рекомендуется, чтобы доступ к таким объектам осуществлялся либо только при помощи переменных, ссылающихся на обычные объекты, либо только при помощи переменных интерфейсного типа. Смешивая подходы можно разрушить механизм подсчета ссылок.
механизм подсчета ссылок
Дата добавления: 2016-07-27; просмотров: 1846;