Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2006.09.03;
Скачать: CL | DM;

Вниз

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

 
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;
Скачать: CL | DM;

Наверх




Память: 0.54 MB
Время: 0.032 c
6-1144955552
qazwsx
2006-04-13 23:12
2006.09.03
base64_encode(pack("H*", sha1(utf8_encode($_GET[ pwd ])))))


15-1155297927
ArtemESC
2006-08-11 16:05
2006.09.03
Пытался установить 4 диска Брокгаузъ и Ефронъ...


3-1150965531
Megabyte
2006-06-22 12:38
2006.09.03
Использование параметров в кач-ве имени таблицы


15-1154714785
Eraser
2006-08-04 22:06
2006.09.03
Иконка 24x24, символизирующая просмотр видео.


2-1155207767
DelphiMax
2006-08-10 15:02
2006.09.03
Отловить запуск программ