Привет! У меня несколько вопросов по ООП в Delphi: 1) Итак, есть у нас следующая ситуация: Code: type a = class private { Private declarations } FNumber: Integer; public { Public declarations } end; b = class private { Private declarations } my_class: a; public { Public declarations } procedure SetNumber(N: Integer); end; implementation { b } procedure b.SetNumber(N: Integer); begin my_class.FNumber := N; end; Здесь ясно видно, что наша процедура SetNumber присваивает новое значение приватному полю объекта класса A. В принципе все норм. Но данная конструкция Code: procedure b.SetNumber(N: Integer); begin my_class.FNumber := N; end; работает, если класс A и B объявлены в одном юните. Как можно добиться того же эффекта, что и процедура SetNumber, но если классы находятся в разных модулях? Переменная FNumber должна оставаться в разделе private. 2) Объясните мне, пожалуйста, директиву virtual (некоторые трудности в понимании ее назначения). 3) В каких случаях вы посоветуете создавать наследники для своих классов? Когда это удобно? Заранее благодарен за ответы по теме.
1) Варианты: 1.а Сделать свойство Number ссылающееся на FNumber (в классе а) 2.б объявить FNumber как protected-член а класс b наследником класса a 2) virtual - директива указывающая что метод виртуальный и может переопределяться наследниками например, в delphi 2010 (может и раньше) у базового класса TObject есть такой виртуальный метод: PHP: function ToString: string; virtual; создаем наследника и переопределяем его: PHP: type TMyClass = class public function ToString: string; override; end; function TMyClass.ToString: string; begin Result := 'ololo'; end; и используем: PHP: procedure TForm1.Button1Click(Sender: TObject); var obj: TObject; begin obj := TObject.Create; ShowMessage(obj.ToString); // тут показывает "TObject" obj.Free; obj := TMyClass.Create; ShowMessage(obj.ToString);// а тут уже "ololo" obj.Free; end; несмотря на то что obj обявлен как TObject на самом деле obj.ToString дает разные результаты внутри одной подпрограммы из-за того что мы применили разные конструкторы и метод виртуальный 3) Например: я пишу сетовое приложение. Для выполнения действий на сайте я создаю некий базовый класс-клиент именно под сайт реализующий общую функциональность (например авторизацию) и определяющий абстрактный виртуальный метод DoWork. А от него наследуют конкретные реализации для спама, инвайта, флуда, чека акков и т.д. реализующие каждый по-своему метод DoWork. При этом клиенты объявляются как client: TBaseClass а конструктор подставляется уже "конкретного" класса. Таким образом чтобы добавить или изменить функционал мне достаточно создать нового наследника и переопределить методы, не боясь что при этом что-т осломается в проекте. Архитетура проста и понятна. Профит! ЗЫ Вопрос не тривиален, если останутся вопросы стучи в аську. ЗЫЫ Добро пожаловать в ооп =)
Спасибо большое! Все понятно. Я просто пишу некоторый класс сейчас и хотел узнать эти аспекты, чтобы потом ничего не нужно было переписывать. Еще один заключительных вопрос по методах с директивой virtual и без: 1) PHP: type TObject = class public function ToString: string; virtual; end; TMyClass = class public function ToString: string; override; end; 2) PHP: type TObject = class public function ToString: string; virtual; end; TMyClass = class public function ToString: string; end; 3) PHP: type TObject = class public function ToString: string; end; TMyClass = class public function ToString: string; end; Где здесь будет переопределена функция ToString класса TMyClass?
В первом варианте. Во втором случае полиморфизм не работает. Какой именно метод будет вызван зависит от того как объявлена переменная. В примере 2 поста если убрать override то будет вызван метод TObject.ToString и вернет "TMyClass" вместо "ololo". Объявляя в наследнике метод с одинаковой сигнатурой виртуального метода базового класса ты просто прерываешь цепочку переопределения ToString, а сделав как в первом варианте ты можешь наследовать от TMyClass и переопределять ToString далее. В третьем случае метод вообще статический и опять же тут будет зависеть от того как объявлена переменная-экземпляр. Вообще, отличный пример наследования и полиморфизма - класс TThread Имеется абстрактный виртуальный метод Execute который вызывается после запуска потока несмотря на то что он не реализован в RTL, а реализует его уже программист в наследниках TThread. Например, можно делать так: PHP: var thread: TThread; ..... thread := TMythread.Create(False); // в классе TMythread реализован Execute
Спасибо, все понятно. Но в процессе работы еще один вопрос появился. В моем классе храниться массив некоторых структур, в которых часть информации должна быть доступна для пользователя, а часть - только для самого класса. У меня два варианта реализации есть, какой из них лучше? 1) Массив классов с приват и паблик полями. 2) 2 массива записей (record), первый - с паблик данными и будет он находиться в секции public, а второй - с приват данными в секции private. Естественно, каждому элементу одного массива должен соответствовать элемент с таким же номером с другого массива. Лично я склоняюсь больше к первому варианту, так он более удобнее. Но в этом случае можно взять любой элемент из массива и выполнить ему метод Free из программы, которая использует класс, тогда данные потеряются. По идеи я этого делать не буду, но все равно хотелось бы узнать, как избежать этой проблемы.
Самый лучший вариант - использовать современные версии Дельфи, где: 1) Записи могут хранить любые члены как и классы: свойства(с регулируемым доступом read и write), методы 2) Все модификаторы доступа(strict private, private, public и т.д.) работают также как и в классах Если сделать так - ты решишь проблему первым вариантом, но без классов, и следовательно риска потерять данные. Если нет такой возможности то есть способ скрыть деструктор. Вот допустим этот класс: PHP: type TMyClass = class public destructor Destroy; overload; override; private destructor Destroy(Owner: TObject); overload; end; { TMyClass } destructor TMyClass.Destroy; begin raise Exception.Create('Not supported operation'); // если вызван дефольный деструктор то возбуждаем исключение end; destructor TMyClass.Destroy(Owner: TObject); begin if Owner is TStringList then // если владелец нужного нам типа то освобождаем объект иначе исключение inherited Destroy else raise Exception.Create('Not supported operation'); end; Таким образом вызов Free для объекта будет возбуждать исключение, а "настоящий" деструктор скрыт в секции private и виден внутри одного модуля, принимает аргумент объект-владелец (в данном случаем TStringList для примера) PHP: procedure TForm1.Button1Click(Sender: TObject); var obj: TMyClass; begin obj := TMyClass.Create; obj.Free; // исключение obj.Destroy(TStringList.Create); // освобождение. класс TMyClass объявлен в этом же модуле по-этому настоящий деструктор виден end; Но это уже напоминает костыли. Самое лучшее решения имхо - использовать продвинутые записи. Второй вариант вообще не рассмативался ибо некрасиво как-то.