Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Прочее";
Текущий архив: 2006.09.03;
Скачать: [xml.tar.bz2];

Вниз

Ошибка проектирования.. как исправить...   Найти похожие ветки 

 
Kolan ©   (2006-08-04 00:36) [0]

Здравствуйте,
 Осознал почему так мучаюсь при необходимости ввода изменений в программу :)

Вот как я пследнее время организую программы:
 Задача проста - составить и отослать команду(куда не важно, в итоге комманда - массив байт). В общем виде делаю это так:
1. Есть класс непосредственно посылающий массив.

2. Есть класс который "знает" протокол. Он составляет массив байт по параметрам.

3. Есть менеджер, который "содержит" в себе экземпляры классов (1) и (2). Его задача такая получая неполный набор параметров, он подставляет недостающие(например номер комманды) использую класс (2) получает массив и отсылает его с пом класса (1).

4. И есть еще главный менеджер который запрявляет другими объектами в том числе и экземпляром (3). У него есть теже ф-ции, что и в (3), но параметров надо еще меньше.

Какие тут плюсы:
Кокда я описал функции ими оч. легко управлять. Например, чтобы послать
запрос "Включить устройство №1" надо написать:
FHeadManager.Switch(1);
Далее будет вызвана соотв. вункция экземпляра класса (3), он "Знает" что номер этой коммады "1". Далее он все это подставит в экземпляр класса (2) и отправит.

Думаю что в плане пользования - оч. удобно.

Если бы не надо было менять существующие и добавлять новые ф-ции :).

Так при добавлении новай ф-ции мне надо добавить соотв ф-цию в класс (2). Затем в класс (3) и наконец в (4). Причем не забыть ничего короче .....

Как можно исправить так, чтобы для добавления новой ф-ции её нужно было бы добавлять только в одном месте? Но при этом На верхних уровнях(класс (4)) вызовы ф-ций остались бы такими же протыми.

Как ие использовать паттерны? Что еще?


 
jack128 ©   (2006-08-04 00:58) [1]

ничего не понял.
почему бы так не сделать:

type
// 1)
 TCommandSender = class
 public
   procedure SendCommand(Data: string);
 end;
// 2 + 3)
 TDevice = class
 public
   property CommandSender: TCommandSender;
   procedure Switch;
 end;

procedure TDevice.Switch;
begin
 CommandSender.SendCommand(#1);
end;

4) - я так понял, просто Owner для 1) и 2+3)


 
Германн ©   (2006-08-04 01:06) [2]

Да. Тут действительно ошибка в проектировании! Имхо.
Не понял. 3 и 4 те, которые менеджеры, тоже классы?

Я бы в классе 2 заранее бы продумал функцию "преобразования данных в соответствии с текущим протоколом", которая бы принимала вышеуказанные данные по-максимуму в произвольном виде. И более того. Эту функцию я бы поместил в DLL.

Это если я правильно интерпретировал сабж.


 
Kolan ©   (2006-08-04 01:12) [3]


> почему бы так не сделать:

Да коммады сложные ... :

Сообщение канального уровня содержит следующие поля:
1. адрес назначения (1 байт);
2. адрес отправителя (1 байт);
3. порядковый номер сообщения (2 байта);
4. длина поля пользователя  (2 байта);
5. контрольная сумма заголовка (1 байт);
6. поле пользователя.
Сообщение пользователя инкапсулируется в поле пользователя сообщения канального уровня. Сообщение пользователя имеет следующие поля:
1. тип пакета (1 байт); в данной версии всегда равен 1
2. команда (1 байт);
3. обязательные параметры команды (длина зависит от команды);
4. не обязательные параметры (длина зависит от обязательных параметров);
5. контрольная сумма (2 байта);



> ничего не понял.

Так я изясняюсь... :) Вoт пример, теперь пойду от верхнего к нижниму(Это простейшая запись коэффициента):

Пользовательнажимает на кнопку:

procedure TMainForm.BitBtn1Click(Sender: TObject);
var
 Value1: Double;
begin
   FHeadManager.WriteAmplifierOutputKoeff(1, Value1);
end;


FHeadManager = класс 4.

procedure THeadManager.WriteAmplifierOutputKoeff(Address: Byte; Koeff: Double);
begin
 FDeviceManager.WriteAmplifierOutputKoeff(Address, Round(Koeff * $7fffffff));
end;


FDeviceManager = класс 3.

procedure TDeviceManager.WriteAmplifierOutputKoeff(Address: Byte;
 Koeff: Cardinal);
var
 Package: TByteArray;
 StaffedPackage: TByteArray;
begin
 {Если порт открыт.}
 if FComPort.IsPortOpened then
 begin

   {Составляем пакет.}
   FPackageComposer.ComposeAmplifierOutputKoeffWriteRequest(Address, Koeff,
     Package, FSendCommandsCount);
   {Добавляем стаффинг.}
   FSendStaffer.StaffByteArray(Package, StaffedPackage);
   {Пишем это дело в порт.}
   FComPort.Write(StaffedPackage, Length(StaffedPackage));
   {Увеличиваем счетчик посланных комманд.}
   FSendCommandsCount := FSendCommandsCount + 1;
   {Последня команда - запрос FFT.}
   FLastCommand := ctAmplifierOutputKoeffWriteRequest;
   {Пишем в протокол.}
   ProtocolManager.WriteString("Запись коэффициента: " +IntToStr(Koeff));
 end;
end;


FPackageComposer = Класс 2.

procedure TPackageComposer.ComposeAmplifierOutputKoeffWriteRequest(
 Address: Byte; AmplifierKoeff: Cardinal; var Answer: TByteArray;
 PackageCount: Word);
var
 UserData: TByteArray;
 B: array[0..3] of Byte;
 TempValue: Cardinal absolute B;
begin
 TempValue := AmplifierKoeff;
 {Составляем поле пользоватея.}
 MakeUserData(ptUsual, ctAmplifierOutputKoeffWriteRequest, [Address, B[0], B[1],
   B[2], B[3]], [], UserData);
 {Добовляем заголовок.}
 AddHeader(Address, UserData, Answer, PackageCount);
end;


Ну и шлем.

Вот такое для каждой комманды :(


 
Kolan ©   (2006-08-04 01:21) [4]


> "преобразования данных в соответствии с текущим протоколом"

Те 1 ф-ция, которая все подготавливает, для любой комманды? Как же её написать...


 
jack128 ©   (2006-08-04 01:47) [5]

Kolan ©   (04.08.06 1:12) [3]
Да коммады сложные ... :

и что здесь сложного???
ну если тя так напрягает, можно конечно написать мелкий классик

var
 CommandBuilder: TCommandBuilder;
begin
 CommandBuilder := TCommandBuilder.Create;
 try
   CommandBuilder.Dest := 12;
   CommandBuilder.Source := 5;
   CommandBuilder.UserData := "Данные пользователя";
   CommandSender.SendCommand(CommandBuilder.BuildCommand())
 finally
   CommandSender.Free;
 end;
end;

но извне им не кто пользоваться не будет.  Или у тя одно и тоже устройство умеет по размым протоколам общаться? ;-)


 
SPeller ©   (2006-08-04 02:35) [6]

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

procedure TMainForm.BitBtn1Click(Sender: TObject);
var
Value1: Double;
begin
  FDeviceManager.SendCommand(1, Round(Value1 * $7fffffff), BuildAmplifierOutputKoeffPacketProc);
end;

procedure TDeviceManager.SendCommand(Address: Byte;
Koeff: Cardinal; BuildProc: TMyProcedure);
var
Package: TByteArray;
StaffedPackage: TByteArray;
begin
{Если порт открыт.}
if FComPort.IsPortOpened then
begin

  {Составляем пакет.}
     if (@MyProc <> nil)
     then
         MyProc(StaffedPackage, LogMessage, ...); // Процедура сбора конкретного пакета
     else
         // Какая-то стандартная процедура сборки пакета

  FComPort.Write(StaffedPackage, Length(StaffedPackage));
  {Увеличиваем счетчик посланных комманд.}
  FSendCommandsCount := FSendCommandsCount + 1;
  {Последня команда - запрос FFT.}
  FLastCommand := ctAmplifierOutputKoeffWriteRequest;
  {Пишем в протокол.}
  ProtocolManager.WriteString(LogMessage);
end;
end;

procedure BuildAmplifierOutputKoeffPacketProc(...)
begin
 // сборка пакета и прочие специфические операторы
end;


 
SPeller ©   (2006-08-04 02:37) [7]

Итого для каждой команды надо будет написать одну процедуру сборки пакета и вставить ее в новый вызов FDeviceManager.SendCommand()


 
jack128 ©   (2006-08-04 03:11) [8]

SPeller ©   (04.08.06 2:37) [7]
все это будет работать, только если BuildAmplifierOutputKoeffPacketProc , BuildSameOtherCommandProc и все остальные будут иметь одинаковую сигнатуру. А это - маловероятно.


 
SPeller ©   (2006-08-04 03:16) [9]

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


 
07BB   (2006-08-04 08:11) [10]

Kolan ©   (04.08.06 00:36)  
А попробывать реализовать что нибудь на теории конечных автоматов?
Данная теория может использоваться не только для оиска совпадений в тексте но и для реализации подобных задач!


 
atruhin ©   (2006-08-04 10:04) [11]

Например:
Любое устройство поддерживает определенное кол-во комманд. В классе протокола(2) реализуем процедуру:
Procedure SendCommand(Command : integer; Fields : array of string; Values : array of variant);
Она принимает номер комманды и задываемые поля. Остальные заполняет по дефолту.
Класс №3 выкидываем. В классе №4 (обертке) вызываем:

procedure THeadManager.WriteAmplifierOutputKoeff(Address: Byte; Koeff: Double);
begin
FDeviceManager.SendCommand(cmAmplifier, ["address","koef"],[Address, Round(Koeff * $7fffffff)]);
end;
И все.


 
Kolan ©   (2006-08-04 10:50) [12]


> все это будет работать, только если BuildAmplifierOutputKoeffPacketProc
> , BuildSameOtherCommandProc и все остальные будут иметь
> одинаковую сигнатуру. А это - маловероятно.

Разная конечно...

> 07BB   (04.08.06 08:11) [10]

Немного я конечно знаю про автоматы, но как они здесь могут помочь
... пример можно?


> Любое устройство поддерживает определенное кол-во комманд.

Честно нео чень понял как надо сделать, и как это поможет..
Это не так. Завтра прийдут и скажут что мы тут подумали и ввели еще 2 комманды....


> Или у тя одно и тоже устройство умеет по размым протоколам
> общаться? ;-)

Нет не умеет.


 
Slym ©   (2006-08-04 11:48) [13]

type
 TCommandProc=function(const Args: array of const;out Command:pointer):integer;

var
 FCommandList:TStrings=nil;

procedure RegisterCommand(const Cmd:string;CommandProc:TCommandProc);
begin
 if not assigned(FCommandList) then
   FCommandList:=TStringList.Create;
 FCommandList.AddObject(Cmd,@CommandProc);
end;

function MakeCommand(const Cmd:string;const Args: array of const;out Command:pointer):integer;
var i:integer;
begin
 i:=FCommandList.IndexOf(Cmd);
 if i<0 then raise Exception.Create("Unsupported command");
 result:=TCommandProc(FCommandList.Objects[i])(Args,Command);
end;

function WriteAmplifierOutputKoeff(const Args: array of const;out Command:pointer):integer;
var
UserData: TByteArray;
Long:LongRec;
AmplifierKoeff:TVarRec;
begin
 Application.MessageBox(PChar(IntToStr(Args[0].vinteger)),"");
 Long:=LongRec(Args[0].vinteger);
 result:=4;
 GetMem(Command,result);//скока надо столько выделяем;
 try
   PByteArray(Command)[0]:=10;//заполняем
 except
   FreeMem(Command);
   result:=0;
   raise;
 end;
end;

procedure SendCommand(Address: Byte;const Cmd:string;const Args: array of const);
var
 command,packet:pointer;
 CmdSize:integer;
 cmdcrc:word;
begin
 CmdSize:=MakeCommand("WriteAmplifierOutputKoeff",[1],command);
 try
   {cmdcrc:=MakeCRC(command,CmdSize);
   packet:=MakePacket(Address);
   MakeCRC(packet,packetsize);
   Send(packet,packetsize);}
 finally
   FreeMem(command);
 end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
 SendCommand(10,"WriteAmplifierOutputKoeff",[1]);
end;

initialization
RegisterCommand("WriteAmplifierOutputKoeff",WriteAmplifierOutputKoeff);

finalization
 if assigned(FCommandList) then
   FreeAndNil(FCommandList);


 
Slym ©   (2006-08-04 11:50) [14]

Функции добавлять проще некуда: RegisterCommand("Name",Name);


 
Slym ©   (2006-08-04 12:06) [15]

можно plugавую схему забацать:
TProc=function(Buf:pointer;BufSize:integer;Args:array of pointers):integer;stdcall;

cmd:="WriteAmplifierOutputKoeff.dll";
arg:integer=100;

LoadLib(cmd) else raise NotSupportedException;
proc:=GetProcAddress(hlib,"Proc");
TProc(Proc)(buf,size,[@arg,@arg2,@strarg]);
FreeLib;


 
Slym ©   (2006-08-04 12:09) [16]

основное направление мысли - открытый массив параметров (array of)


 
Kolan ©   (2006-08-04 15:35) [17]


> Slym ©   (04.08.06 12:09) [16]

Буду разбираться, пока не понятно ничего... :)


 
GrayFace ©   (2006-08-04 16:06) [18]

Германн ©   (04.08.06 1:06) [2]
Я бы в классе 2 заранее бы продумал функцию "преобразования данных в соответствии с текущим протоколом", которая бы принимала вышеуказанные данные по-максимуму в произвольном виде. И более того. Эту функцию я бы поместил в DLL.

В сабже вроде не было ни слова про dll. А наворачивать dll на пустом месте - глупость абсолютная.

Kolan ©   (04.08.06 1:12) [3]
Вот такое для каждой комманды :(

Если для всех команд такая же кухня и параметры сходные, то просто сделай общие процедуры, которые будут принимать в качестве параметра конечную процедуру (как procedure of object). Если параметры у функций разные, но есть несколько общих для всех параметров, то специфические можно засунуть в record, в промежуточной(ых) ф-ции сделать var Params без типа, в конечный var Params:TТакой-тоRecord.

07BB   (04.08.06 8:11) [10]
А попробывать реализовать что нибудь на теории конечных автоматов?
Данная теория может использоваться не только для оиска совпадений в тексте но и для реализации подобных задач!

Слабо представляю ее использование для поиска совпадений и тем более для данной задачи.


 
Slym ©   (2006-08-07 09:15) [19]

Kolan ©   (04.08.06 15:35) [17]
Буду разбираться, пока не понятно ничего... :)

Что не понятно?
1. Список имен функций с указателями на них
2. Все функции одного типа с открытым массивом параметров.
3. Расширение функционала реализуется добавлением имени функции и ее указателя в список.
Единственное неудобство, контроля типов параметров нет, функция сама должна все проверить.


 
GrayFace ©   (2006-08-08 11:35) [20]

С record-ами контроль типов параметров будет и накладных расходов меньше.



Страницы: 1 вся ветка

Форум: "Прочее";
Текущий архив: 2006.09.03;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.52 MB
Время: 0.041 c
2-1155750524
pvi
2006-08-16 21:48
2006.09.03
CreateRemoteComObject


4-1147107989
Destroyer
2006-05-08 21:06
2006.09.03
Удаление занятого файла


15-1154787455
ZeroDivide
2006-08-05 18:17
2006.09.03
Женился


3-1151188021
wardoc
2006-06-25 02:27
2006.09.03
транзакции в распределенной БД


6-1145269349
vcj
2006-04-17 14:22
2006.09.03
Delphi 7 + PHP 4-5





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский