Текущий архив: 2007.09.23;
Скачать: CL | DM;
ВнизИспользование объектов Найти похожие ветки
← →
Виктор007 (2007-08-21 18:06) [0]Решил потренироваться в написании программ используя объекты. Всегда пользовался простыми функциями...
Помогите составить объекты для программы-планировщика.
Задания в планировщике могут быть нескольких типов:
1. Один раз. Параметры: DateTime : TSystemTime (время срабатывания)
2. Ежечасно. Параметры: minute: Int; (минута каждого часа, от 0..59)
3. Ежедневно. Параметры: Время: TSystemTime; (напр 21:49) isПонедельник, isВторник, isСреда и т.п. : Boolean; (по каким дням)
4. Еженедельни и т.п.
Вот собственно я ломаю голову - как построить объектную модель программы (или как это называется).
Подскажите пожалуйста, у меня сложность - слишком много варинтов... я думал сделать обращения к заданиям в таком виде:
Jobs.Items[i].GetNextAlarm... но итемы все получаются разные. В общем даже не знаю как объяснить т.к. но думаю меня поняли. Хочется селать правильно, а не кучей функций и ifов, а используя красивые наглядные объекты...
← →
Юрий Зотов © (2007-08-21 18:26) [1]Один из вариантов (неполный, только схематично):
type
TJobItemType = (jiSingle, jiHourly, jiDaily, jiWeekly, ...);
TJobItem = class (TCollectionItem)
private
FJobItemType: TJobItemType;
FNextAlarmTime: TDateTime;
public
procedure Execute;
// jiSingle - самоуничтожение, остальные - пересчет FNextAlarmTime.
property JobItemType: TJobItemType
read FJobItemType write FJobItemType default jiSingle;
property NextAlarmTime: TDateTime
read FNextAlarmTime write FNextAlarmTime;
end;
TJobs = class(TCollection)
...
end;
Конкретная реализация зависит от особенностей задачи. Например - кто должен следить за временем, что должно произойти при срабатывании, нужна ли возможность редактирования в design-time и т.п.
← →
Виктор007 (2007-08-21 21:36) [2]Если я правильно понял JobItemType - это тип задания. А меня интересует как реализовать для каждого из типов различные свойства, и чтобы все задания различных
Например для jiSingle: DateTime : TSystemTime
для jiHourly: minute : Integer;
для jiDaily: DateTime : TSystemTime; Monday, Tuesday, Wednesday... : Boolean;
и т.п. По какому пути лучше пойти в таком случае?
← →
Сергей М. © (2007-08-22 09:47) [3]
> как реализовать для каждого из типов различные свойства
Это учебная задача ?
← →
Виктор007 (2007-08-22 10:22) [4]Нет, для себя. Лежу уже 2 месяца в больнице, лежать еще 3 месяца - делать нечего. Сейчас пришел немного в себя, привезли ноутбук. Делать катострофически нечего - ничего серьезного делать(думать) нельзя, вот и решил поднатаскаться в основах ООП. Лежу с менингитом. Врач сказал каждый час делать перерывы на 10 минут от компа. Вот и решил написать планировщик, а заодно и освоить ООП.
Задания (напоминания) сделать типа таких: зделать зарядку, на уколы, в столовую, зделать перерыв.
Т.к. сижу в интернету через GPRS, пользоваться поиском и качать книжки довольно дорого :( поэтому обратился на форум.
← →
Сергей М. © (2007-08-22 10:28) [5]Ну тогда можно поступить так:
1. Базовый класс TCustomJobItem реализует метод Execute
2. Производные от него классы TSingleJobItem, THourlyJobItem, TDaylyJobItem, TWeeklyJobItem имеют каждый свои собственные свойства требуемых типов.
← →
Виктор007 (2007-08-22 18:21) [6]В общем пока начал делать так:
unit Jobs;
interface
uses Windows;
type
PSingleJob = ^TSingleJob;
TSingleJob = packed record
Time: TSystemTime;
end;
PHourlyJob = ^THourlyJob;
THourlyJob = packed record
Minute: Integer;
end;
TJobItemType = (jiSingle, jiHourly);
TJobItem = class
private
FJobItemType: TJobItemType;
FCaption: string;
FEnabled: boolean;
FNextAlarmTime: TSystemTime;
FSettings: Pointer;
public
constructor Create;
procedure SetJobItemType(const Value: TJobItemType);
property JobItemType: TJobItemType
read FJobItemType;
property Caption: string
read FCaption write FCaption;
property Enabled: boolean
read FEnabled write FEnabled;
property NextAlarmTime: TSystemTime
read FNextAlarmTime write FNextAlarmTime;
property Settings: Pointer
read FSettings write FSettings;
end;
implementation
constructor TJobItem.Create;
begin
FJobItemType := jiSingle;
GetMem(FSettings, SizeOf(TSingleJob));
end;
procedure TJobItem.SetJobItemType(const Value: TJobItemType);
begin
FreeMem(FSettings);
FJobItemType := Value;
case Value of
jiSingle: GetMem(FSettings, SizeOf(TSingleJob));
jiHourly: GetMem(FSettings, SizeOf(THourlyJob));
end;
end;
var i: TJobItem;
initialization
i := TJobItem.Create;
i.Caption := "Сделать зарядку";
i.SetJobItemType( jiSingle );
case i.JobItemType of
jiSingle: GetLocalTime(PSingleJob(i.Settings).Time);
jiHourly: PHourlyJob(i.Settings).Minute := 10;
end;
end.
Cкажите, насколько это правильный подход?
← →
Юрий Зотов © (2007-08-22 23:25) [7]1. Утечки памяти из-за:
property Settings: Pointer read FSettings write FSettings;
А вот так их не будет:
property Settings: Pointer read FSettings write SetSettings;
И в SetSettings надо сначала вызвать FreeMem.
2. Установка Settings "снаружи" не нужна, поэтому так еще лучше:
property Settings: Pointer read FSettings;
Свойство будет доступно только для чтения. В данном случае так и нужно.
3. Надо перекрыть деструктор и в нем тоже вызвать FreeMem, иначе снова утечки памяти.
4. Нет смысла делать лишние операции (тем более, без нужды фрагментировать память), поэтому:
procedure TJobItem.SetJobItemType(const Value: TJobItemType);
begin
if FJobItemType <> Value then
begin
...
end
end;
5. Так будет лучше:
property JobItemType: TJobItemType
read FJobItemType write SetJobItemType;
И метод SetJobItemType убираем в private.
6. Секция initialization не нужна. Только утечка памяти из-за нее.
7. Пользоваться классом неудобно, потому что требуется приведение типа указателя, да еще и в зависимости от типа задания. Нужно добавить какие-то методы в сам класс, и весь служебный код вынести в них. Идеально, если программист - пользователь класса не должен знать, как этот класс реализован. Он просто вызывает метод класса и получает все, что хочет.
← →
Сергей М. © (2007-08-23 09:34) [8]
> насколько это правильный подход?
Судя по задействованию рекордов вместо иерархии классов подход вряд ли соответствует желанию "освоить ООП".
← →
Юрий Зотов © (2007-08-23 09:55) [9]> Сергей М. © (23.08.07 09:34) [8]
Если б не было желания, не было бы этой ветки. А для перестройки мышления нужно время. Придет оно, никуда не денется.
> Виктор007 (22.08.07 18:21) [6]
А по сути Сергей прав - рекорды тут смотрятся весьма криво. Из-за них и п.7 в [7]. Неплохо бы поискать способ от них избавиться.
← →
Юрий Зотов © (2007-08-23 10:18) [10]> > Виктор007 (22.08.07 18:21) [6]
А вообще, если хотите освоить ООП, Delphi и VCL, то настоятельно рекомендую капитально проштудировать и понять вот эту книгу:
http://mirknig.com/2006/02/19/sozdanie_originalnykh_komponent_v_srede_Delphi.html
Шедевр. Написана еще в эпоху Delphi 1, но актуальности не потеряла. Тем, кто уже знает Паскаль и уже умеет программировать, ставит понимание объектного подхода буквально за несколько часов. А далее - практика.
← →
Виктор007 (2007-08-23 11:33) [11]Всем большое спасибо за объяснения.
>> Юрий Зотов © (23.08.07 10:18) [10]
Эта книга как раз ко мне сучайно попала года 4 назад. До сих пор лежит в шкафу нечитаная, т.к. думал что устаревшая. Теперь обязательно прочитаю - благо в больнице много свободного времеми. Еще раз спасибо, планировщик пока заброшу - повешу таймер на форму :). Если будут остальные вопросы - то уже по ходу чтения книги.
← →
Виктор007 (2007-08-23 11:53) [12]P.S. Вот первый класс который я написал в этом же планировщике.
unit Driver;
interface
uses Windows;
type
TDriver = class
private
FDriverPath: string;
FDriverName: string;
fhDriver: THandle;
procedure SetDriverPath(const fname: string);
procedure SetDriverName(const Value: string);
procedure InstallDriver(const fname: string);
procedure UninstallDriver();
public
procedure OpenDriver;
procedure FreeDriver;
published
property hDriver : THandle read fhDriver;
property DriverPath : string read FDriverPath write SetDriverPath;
property DriverName : string read FDriverName write SetDriverName;
end;
implementation
type
NTStatus = cardinal;
PClientID = ^TClientID;
TClientID = packed record
UniqueProcess:cardinal;
UniqueThread:cardinal;
end;
PUnicodeString = ^TUnicodeString;
TUnicodeString = packed record
Length: Word;
MaximumLength: Word;
Buffer: PWideChar;
end;
// Прототипы функций
TRtlInitUnicodeString = procedure(DestinationString: PUnicodeString; SourceString: PWideChar); stdcall;
TZwLoadDriver = function(DriverServiceName: PUnicodeString): NTStatus; stdcall;
TZwUnloadDriver = function(DriverServiceName: PUnicodeString): NTStatus; stdcall;
var
RtlInitUnicodeString :TRtlInitUnicodeString = nil;
ZwLoadDriver :TZwLoadDriver = nil;
ZwUnloadDriver :TZwUnloadDriver = nil;
// Проверка платформы (true - платформа NT)
function ISNT : boolean;
var
Ver : TOSVersionInfo;
begin
Result := False;
Ver.dwOSVersionInfoSize := SizeOf(TOSVersionInfo);
GetVersionEx(Ver);
case Ver.dwPlatformId of
VER_PLATFORM_WIN32_NT : Result := True; //Windows NT - подходит
VER_PLATFORM_WIN32_WINDOWS : Result := False; //Windows 9x-Me - подходит
VER_PLATFORM_WIN32s : Result := False; //Windows 3.x - не подходит
end;
end;
Procedure TDriver.InstallDriver(const fname: string);
var
Key, Key2: HKEY;
Pth: PChar;
dType: dword;
Image: array [0..MAX_PATH] of Char;
begin
lstrcpy(Image, "\??\");
GetFullPathName(PChar(fname), MAX_PATH, PChar(dword(@Image) + 4), Pth);
dType := 1;
RegOpenKey(HKEY_LOCAL_MACHINE, "system\CurrentControlSet\Services", Key);
RegCreateKey(Key, PChar(FDriverName), Key2);
RegSetValueEx(Key2, "ImagePath", 0, REG_SZ, @Image, lstrlen(Image));
RegSetValueEx(Key2, "Type", 0, REG_DWORD, @dType, SizeOf(dword));
RegCloseKey(Key2);
RegCloseKey(Key);
end;
procedure TDriver.UninstallDriver();
var
Key: HKEY;
begin
RegOpenKey(HKEY_LOCAL_MACHINE, "system\CurrentControlSet\Services", Key);
RegDeleteKey(Key, PChar(FDriverName));
RegCloseKey(Key);
end;
procedure TDriver.SetDriverPath(const fname: string);
begin
FDriverPath := fname;
end;
procedure TDriver.SetDriverName(const Value: string);
begin
FDriverName := Value;
end;
procedure TDriver.OpenDriver;
var
Image: TUnicodeString;
s: WideString;
begin
InstallDriver(FDriverPath);
RtlInitUnicodeString(@Image, PWChar(WideString("\registry\machine\system\CurrentControlSet\Services\"+ fDriverName)));
ZwLoadDriver(@Image);
fhDriver := CreateFile(PChar("\\.\"+DriverName), GENERIC_READ + GENERIC_WRITE, 0,
nil, OPEN_EXISTING, 0, 0);
end;
Procedure TDriver.FreeDriver();
var
Image: TUnicodeString;
begin
if fhDriver = 0 then exit;
CloseHandle(hDriver);
RtlInitUnicodeString(@Image, PWChar(WideString("\registry\machine\system\CurrentControlSet\Services\"+ fDriverName)));
ZwUnloadDriver(@Image);
UninstallDriver();
fhDriver := 0;
end;
var
Lib: THandle;
initialization
Lib := GetModuleHandle("ntdll.dll");
if Lib<>0 then
begin
ZwLoadDriver := GetProcAddress(Lib, "ZwLoadDriver");
ZwUnloadDriver := GetProcAddress(Lib, "ZwUnloadDriver");
RtlInitUnicodeString := GetProcAddress(Lib, "RtlInitUnicodeString");
end;
end.
и собственно сам наследникunit LKbdDrv;
interface
uses Windows, Driver, KOL;
type
TKbdDriver = class(TDriver)
public
procedure EnableKbd;
procedure DisableKbd;
constructor Create;
constructor Destroy;
end;
implementation
const
IOCTL_LOCK_KBD = $0022E014;
IOCTL_UNLOCK_KBD = $0022E018;
procedure TKbdDriver.EnableKbd;
var
BytesReturned: dword;
begin
DeviceIoControl(hDriver, IOCTL_UNLOCK_KBD, nil, 0, nil, 0, BytesReturned, nil);
end;
procedure TKbdDriver.DisableKbd;
var
BytesReturned: dword;
begin
DeviceIoControl(hDriver, IOCTL_LOCK_KBD, nil, 0, nil, 0,BytesReturned, nil );
end;
constructor TKbdDriver.Create;
begin
DriverPath := GetStartDir + "lockkbd.sys";
DriverName := "lockkbd";
OpenDriver;
end;
constructor TKbdDriver.Destroy;
begin
FreeDriver;
end;
end.
Единственное - при вызове метода Free экземпляра класса TKbdDriver не вызывается деструктор Destroy
← →
Сергей М. © (2007-08-23 11:59) [13]
> constructor Destroy;
???
должно быть
destructor Destroy; override;
← →
Виктор007 (2007-08-23 12:13) [14]ммм... точно. Спасибо.
← →
Юрий Зотов © (2007-08-23 13:02) [15]1. Класс TDriver отнаследован от TObject, поэтому секция published в нем не имеет смысла. Если сохранение свойств этого класса в DFM не требуется, то используйте public, если требуется - наследуйте его от TPersistent.
2. Методы SetDriverPath и SetDriverName в ТАКОЙ реализации смысла не имеют, то же самое (но проще) дает прямой доступ к полям на запись:property DriverPath : string read FDriverPath write FDriverPath;
property DriverName : string read FDriverName write FDriverName;
3. Не стал разбираться, проверьте сами: что произойдет, если вызвать метод OpenDriver несколько раз подряд? (Только не говорите, что такого не будет - юзер класса ОБЯЗАТЕЛЬНО это сделает, можно не сомневаться).
4. Про деструктор Сергей уже сказал. И вызов Inherited в его конце обязателен (надо освободить ресурсы, захваченные предками).
5. Переменные ZwLoadDriver, ZwUnloadDriver и RtlInitUnicodeString (и все, что к ним относится) имеют смысл, если все это уже не сделано в VCL. Я не проверял (проверьте сами), но есть подозрение, что в VCL эти функции уже объявлены - а если так, то вся эта возня не нужна, можно просто вызвать функцию. Если же в VCL этого нет, то появляется смысл в том, чтобы вытащить всю низкоуровневую работу в отдельный юнит (типа SysUtils или Windows) и потом использовать этот юнит в других проектах. Такой юнит будет представлять ценность уже и сам по себе.
6. По функционалу - не вникал, проверьте сами вот что.
а). Создаю несколько экземпляров класса TKbdDriver. Что будет?
б). Создаю экземпляр класса TKbdDriver, дизаблю клаву, завершаю программу. Что будет?
7. Главное. Непонятно, зачем в планировщике заданий потребовалось лочить клаву? Складывается ощущение, что написание кода опережает осмысление функционала. То есть, что еще нет четкой продуманности будущей библиотеки классов, что она еще не спроектирована. И если это действительно так, то писать код реализации рано, надо сначала продумать всю будущую библиотеку и написать объявления всех ее классов. Хоть на бумаге, неважно.
← →
Виктор007 (2007-08-23 16:03) [16]Юрий, по всем пунктам понял недочеты. Единственное -
п.5: - модуль предполагается использовать без VCL, т.к. планировщик будет висеть в памяти - то таскать за собой в такой простой программе его не хочется. А динамическое связывание для того чтобы в Win9x не получать непонятной ошибки а просто выводить сообщение о невозможности работы программы в данной среде.
п.6: не пробовал, пишу же класс для себя. Но намек понял, подумаю над этим что-нибудь
п.7: лочить клаву и мышь опять же для себя. Понимаете, если просто выскочит сообщение то я его закрою и про себя подумаю "щас, еще пять минут и пойду выпью таблетки" и в итоге про них забываю, у меня уже пол ящика этих забытых таблеток. Пусть комп просто блокируется на 10 минут чтобы даже Ctrl-Alt-Del не работали - тогда я хочу или не хочу, но придется заняться делами.
> нет четкой продуманности будущей библиотеки классов, что она еще не спроектирована
В этом-то для меня и самая сложность. В институте мы ООП проходили в виде трех страниц определений и все. А сейчас после него хочется со всем разобраться но нет опыта...
← →
Юрий Зотов © (2007-08-23 16:50) [17]> Виктор007 (23.08.07 16:03) [16]
> п.5.
Тогда тем более есть смысл вытащить работу с ntdll в отдельный юнит, чтобы его можно было использовать и без VCL, и вообще без всяких классов.
Динамическое связывание для диагностики - хороший ход, спору нет.
> п.6. и п. 7.
"Для себя" - это обычная ошибка, когда с разработки программ человек переходит на разработку классов. Мышление должно перестроиться, потому что класс (и даже библиотека классов) - это не программа, это кирпичик для множества программ. В этих словах "кирпичик" и "множество" весь фокус и есть. Писать надо не "для себя", а "для кого угодно". Раньше у Вас был один юзер (бухгалтер Лидия Петровна), и Вы думали только о нем. Теперь же у Вас стало два юзера - программист Вася Пупкин и бухгалтер Лидия Петровна. И думать надо уже о них обоих, и писать надо уже для них обоих (причем в первую очередь как раз для Васи).
> В этом-то для меня и самая сложность
Обычная вещь. Читайте Конопку, не пожалеете. Слова класс, экземпляр класса, инкапсуляция, наследование, полиморфизм, поле, свойство, событие, метод, область видимости, статический метод, динамический, метод, виртуальный метод, абстрактный метод, классовый метод, метод диспетчеризации, метод доступа, обработчик сообщения, перекрытие и перегрузка методов, RTTI, метакласс, виртуальный конструктор, сериализация и пр. должны стать такими же родными и понятными, как "папа" и "мама". А линейку наследования TObject - TPersistent - TComponent - TControl - TWinControl/TGraphicControl надо знать чуть ли не наизусть (во всяком случае, понимать, зачем в этой линейке нужен каждый класс, что и как он в этой линейке делает).
Без четкого понимания этих вещей сделать хорошую библиотеку классов довольно проблематично. Так что - читайте Конопку, не пожалеете. Там все это есть (кроме перегрузки методов, но это неважно) и все прекрасно расписано. Еще и перевод очень хороший.
← →
@!!ex © (2007-08-23 17:46) [18]Юрий, а не подскажите книжечку по ООП, но не компонентам?
Считал, что знаю прелести ООП достаточно, но почитав эту ветку, понял, что хоть и пишу на ООП, использую лишь малую часть его функионала.
← →
Юрий Зотов © (2007-08-23 18:21) [19]> @!!ex © (23.08.07 17:46) [18]
По ООП вообще - Гради Буч, видимо.
По ООП конкретно Дельфишному - тот же Конопка. Компоненты - это ведь тоже ООП, да еще какой!
← →
Виктор007 (2007-08-30 17:31) [20]> Юрий Зотов © (23.08.07 18:21) [19]
А у вас нет случаем дискетки от книги Рэя Конопки? А то у меня она утерянна :(
Страницы: 1 вся ветка
Текущий архив: 2007.09.23;
Скачать: CL | DM;
Память: 0.54 MB
Время: 0.047 c