Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / Delphi Новый топик    Ответить
Топик располагается на нескольких страницах: [1] 2 3 4 5   вперед  Ctrl      все
 Дженерики: передача процедуры в качестве параметра  [new]
X11
Member

Откуда: Kharkiv, Ukraine
Сообщений: 12484
Я пытаюсь написать бота. Больше нужна идея, нежели код и пример, но и примеры тоже будут полезны.
Важно, чтобы в итоге код получился легко масштабируемым, чтобы можно было легко добавлять обработчики (процедуры) новых команд.

Я так понимаю, что нужно создать какой-то список, где будет ключ: команда боту /start, /find, /найти, /Ещё что-то) и значение: название, соответствующей этому ключевому слову процедуре. Не писать же в процедуре входящих команд 100500 IF`ов?

Дженерики почти никогда не использовал. Использовал что-то простое для строк и объектов или чисел.
Буду благодарен за примеры и идеи.
Хоть как это правильно объявить, с чего начать?
28 окт 18, 12:33    [21717171]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
X11
Member

Откуда: Kharkiv, Ukraine
Сообщений: 12484
что-то типа
Var
  DicCmds: TDictionary<String, а здесь что>;

чтобы можно было потом подставить название процедуры в качестве параметра
и как это потом использовать?
28 окт 18, 12:45    [21717178]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
asviridenkov
Member

Откуда:
Сообщений: 3827
X11
что-то типа
Var
  DicCmds: TDictionary<String, а здесь что>;

чтобы можно было потом подставить название процедуры в качестве параметра
и как это потом использовать?


type
TMyProc = function(const Command: string): string;
28 окт 18, 12:47    [21717179]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
X11
Member

Откуда: Kharkiv, Ukraine
Сообщений: 12484
Внутри секции ptivate пишу
...
...
Type
    Type botStart = procedure;
    Type botFindByID = procedure (id: integer);
    Type botFindByPhone= procedure(const sPhone, sType: string);

...
...
Var
  DicCmds: TDictionary<String, TProcedure>;


так?
даже если разное количество параметров у процедур будет?
28 окт 18, 13:07    [21717185]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
X11
Member

Откуда: Kharkiv, Ukraine
Сообщений: 12484
X11
Type botStart = procedure;

в итоге ругается компилятор
Incompatible types: 'regular procedure and method pointer'



Так правильно?
Type
  TcmdStart = procedure of object;
28 окт 18, 13:18    [21717191]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
ziv-2014
Member

Откуда:
Сообщений: 147
X11,
TMethod и Invoke на вскидку.
28 окт 18, 13:24    [21717197]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
X11
Member

Откуда: Kharkiv, Ukraine
Сообщений: 12484
ziv-2014,
???
28 окт 18, 13:48    [21717221]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
JaDi
Member

Откуда: Сызрань, Россия
Сообщений: 3862
Лучше уж добавлять обработку команд через о дельный метод:

procedure registerCommand(const ACommand: string; const AWorker: procedure of object);
28 окт 18, 13:54    [21717228]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
X11
Member

Откуда: Kharkiv, Ukraine
Сообщений: 12484
JaDi,

а дальше?
пока не понял идеи
28 окт 18, 13:58    [21717231]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
JaDi
Member

Откуда: Сызрань, Россия
Сообщений: 3862
и вообще, в обработчик ведь надо параметры разные передавать. Поэтому сначала надо определиться с форматом этих параметров. Например, в виде json-строки.

Type TBotWorker = procedure(const AParams: string);

После чего получится
procedure registerCommand(const ACommand: string; const AWorker: TBotWorker);

...

registerCommand('start', this.ProcessStart);
registerCommand('find_by_id', this.ProcessFindByID);
registerCommand('find_by_name', this.ProcessFindByName);
28 окт 18, 14:01    [21717233]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
X11
Member

Откуда: Kharkiv, Ukraine
Сообщений: 12484
JaDi, я пока не знаю, какие будут параметры и какого типа.
Мало того, заранее неизвестно, что напишет пользователь боту. Это может быть не только команда, но и простой текст, которые придётся распарсить и понять - какую процедуру обработки вызвать.
28 окт 18, 14:03    [21717235]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
X-Cite
Member

Откуда: Минск
Сообщений: 1164
X11
Внутри секции ptivate пишу
...
...
Type
    Type botStart = procedure;
    Type botFindByID = procedure (id: integer);
    Type botFindByPhone= procedure(const sPhone, sType: string);

...
...
Var
  DicCmds: TDictionary<String, TProcedure>;


так?
даже если разное количество параметров у процедур будет?


Уже давно пора писать
botStart = reference to procedure;

И там неважно что будет тогда, метод, процедура или анонимная процедура
28 окт 18, 14:28    [21717247]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
X11
Member

Откуда: Kharkiv, Ukraine
Сообщений: 12484
X-Cite
reference to procedure;


Тогда как объявить словарь?

Вот сделал так:
    Type TBotProcRef  = reference to procedure;
    Type TBotProcRef1 = reference to procedure (id: integer);
    Type TBotProcRef2 = reference to procedure(const sPhone, sType: string);

    Var      
      DicCmdRows: TDictionary<String, TProcedure>;
      cmdStart:  TBotProcRef;
      cmdDateTime: TBotProcRef;
      cmdFindByID: TBotProcRef1;



Получаю ошибку при компиляции:
Incompatible types: 'TProcedure' and 'TfmXXX.TBotProcRef'

procedure TfmMainTelegramBot.FillDicProcs;
begin
  if Not Assigned(DicCmdRows) then
    DicCmdRows := TDictionary<string, TProcedure>.Create
  else
    DicCmdRows.Clear;

  DicCmdRows.Add('/Start', cmdStart);
//  DicCmdRows.Add('/время', cmdDateTime);

end;
28 окт 18, 15:27    [21717273]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
X11
Member

Откуда: Kharkiv, Ukraine
Сообщений: 12484
А если словарь объявлять так:
    Type TBotProcRef  = reference to procedure;
    Type TBotProcRef1 = reference to procedure (id: integer);
    Type TBotProcRef2 = reference to procedure(const sPhone, sType: string);

   Var
    DicCmdRows: TDictionary<String, TBotProcRef>;

То тогда придётся объявлять несколько разных словарей
28 окт 18, 15:35    [21717277]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
defecator
Member

Откуда:
Сообщений: 38865
Навскидку

type
    { один параметр }
    TParam = record
      ParamName  : string[64] ;
      ParamValue : Variant ;
    end ;

    TParams = array of TParam ;

    TBotProcRef = procedure(const aParams : TParams) ;
28 окт 18, 15:47    [21717288]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
X11
Member

Откуда: Kharkiv, Ukraine
Сообщений: 12484
Я пока думаю, что всегда будет один параметр, т.е. в качестве этого одного параметра будет входящее написанное пользователем сообщение....
28 окт 18, 15:51    [21717296]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
X11
Member

Откуда: Kharkiv, Ukraine
Сообщений: 12484
X11
А если словарь объявлять так:
    Type TBotProcRef  = reference to procedure;
    Type TBotProcRef1 = reference to procedure (id: integer);
    Type TBotProcRef2 = reference to procedure(const sPhone, sType: string);

   Var
    DicCmdRows: TDictionary<String, TBotProcRef>;


То тогда придётся объявлять несколько разных словарей


Ладно, продолжаю тормозить

rcvMessage - метод ресивера, который срабатывает при входящем сообщении
procedure TfmMainXXX.rcvMessage(ASender: TObject; AMessage: ITgMessage);
begin
  if DicCmdRows.ContainsKey(AMessage.Text) then
    cmdStart := DicCmdRows[AMessage.Text];



И как запустить на выполнение cmdStart?


Сейчас объявлено так:

    procedure PcmdStart;
    procedure FillDicProcs;

    Type TBotProcRef  = reference to procedure;
    Type TBotProcRef1 = reference to procedure (id: integer);
    Type TBotProcRef2 = reference to procedure(const sPhone, sType: string);



    Var
      sUserDocsKvxTelegramBotPath: string;
      DicCmdRows: TDictionary<String, TBotProcRef>;
      cmdStart:  TBotProcRef;
      cmdDateTime: TBotProcRef;
      cmdFindByID: TBotProcRef1;


...
...
...

// наполняем словарь ключами и ссылками на процедуры-обработчики команд
procedure TfmMainXXX.FillDicProcs;
begin
  if Not Assigned(DicCmdRows) then
    DicCmdRows := TDictionary<string, TBotProcRef>.Create
  else
    DicCmdRows.Clear;

  DicCmdRows.Add('/Start', cmdStart);
  DicCmdRows.Add('/время', cmdDateTime);

end;

// в итоге должен сработать метод PcmdStart, когда пользователь прислал /start
procedure TfmMainXXX.PcmdStart;
begin
// обработка команды /Start
  ShowMessage('/Start');
end;
28 окт 18, 15:57    [21717298]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
X11
Member

Откуда: Kharkiv, Ukraine
Сообщений: 12484
X11
 DicCmdRows.Add('/Start', cmdStart);
  DicCmdRows.Add('/время', cmdDateTime);


здесь я ошибся, нужно подставлять имена реальных процедур

procedure TfmMainXXX.PcmdStart;
begin
// обработка команды /Start
  ShowMessage('/Start');
end;


procedure TfmMainXXX.PcmdShowTime;
begin
// обработка команды /время
  ShowMessage('/время');
end;

// заполняем словарь
procedure TfmMainTelegramBot.FillDicProcs;
begin
  if Not Assigned(DicCmdRows) then
    DicCmdRows := TDictionary<string, TBotProcRef>.Create
  else
    DicCmdRows.Clear;

  DicCmdRows.Add('/Start', PcmdStart);
  DicCmdRows.Add('/время', PcmdShowTime);

end;


procedure TfmMainXXX.rcvMessage(ASender: TObject; AMessage: ITgMessage);
begin
  if DicCmdRows.ContainsKey(AMessage.Text) then
    BotProcRef := DicCmdRows[AMessage.Text];

// просто написать, так правильно?
 BotProcRef;


и как теперь выполнить cmdStart, в котором живёт
28 окт 18, 16:04    [21717302]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
X11
Member

Откуда: Kharkiv, Ukraine
Сообщений: 12484
Да, работает, бот получил эти 2 команды и запустил нужные процедуры.
28 окт 18, 16:06    [21717303]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
JaDi
Member

Откуда: Сызрань, Россия
Сообщений: 3862
Пример реализации: менеджер ботов, два боты со своими командами, обработка и вывод результата. Можно дальше дорабатывать, например, добавить пользовательские сессии в менеджере и хранить там состояние о об активном боте и введенных данных.

type
  // обработчик одной команды
  TBotWorker = procedure(const AParams: string; var AResults: string) of object;

  TBotsManager = class;
  TBot = class
  public
    procedure RegisterCommands(const ABotsManager: TBotsManager); virtual; abstract;
  end;

  // менеджер для управления ботами, командами и обработки
  TBotsManager = class
  private
    BotsList: TObjectList<TBot>;
    CommandsList: TDictionary<string, TBotWorker>;
    procedure ProcessCommand(const ACommandLine: string);
  public
    constructor Create; overload;
    destructor Destroy; override;

    procedure RegisterBot(ABot: TBot);
    procedure RegisterCommand(const ACommand: string; const AWorker: TBotWorker);
  end;

  // бот 1
  TBotDialogHello = class(TBot)
  private
    procedure ProcessHello(const AParams: string; var AResults: string);
  public
    procedure RegisterCommands(const ABotsManager: TBotsManager); override;
  end;

  // бот 2
  TBotDialogSearch = class(TBot)
  private
    procedure ProcessStart(const AParams: string; var AResults: string);
    procedure ProcessFindByName(const AParams: string; var AResults: string);
    procedure ProcessFindByID(const AParams: string; var AResults: string);

    procedure ProcessFind(const AParams: string; var AResults: string);
  public
    procedure RegisterCommands(const ABotsManager: TBotsManager); override;
  end;



Полностью реализация:
+
unit Unit14;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
  System.Generics.Collections;

type
  TForm14 = class(TForm)
    Button_Send: TButton;
    edit_Command: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure Button_SendClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
  public
  end;

type
  // обработчик одной команды
  TBotWorker = procedure(const AParams: string; var AResults: string) of object;

  TBotsManager = class;
  TBot = class
  public
    procedure RegisterCommands(const ABotsManager: TBotsManager); virtual; abstract;
  end;

  // менеджер для управления ботами, коммандами и обработки
  TBotsManager = class
  private
    BotsList: TObjectList<TBot>;
    CommandsList: TDictionary<string, TBotWorker>;
    procedure ProcessCommand(const ACommandLine: string);
  public
    constructor Create; overload;
    destructor Destroy; override;

    procedure RegisterBot(ABot: TBot);
    procedure RegisterCommand(const ACommand: string; const AWorker: TBotWorker);
  end;

  // бот 1
  TBotDialogHello = class(TBot)
  private
    procedure ProcessHello(const AParams: string; var AResults: string);
  public
    procedure RegisterCommands(const ABotsManager: TBotsManager); override;
  end;

  // бот 2
  TBotDialogSearch = class(TBot)
  private
    procedure ProcessStart(const AParams: string; var AResults: string);
    procedure ProcessFindByName(const AParams: string; var AResults: string);
    procedure ProcessFindByID(const AParams: string; var AResults: string);

    procedure ProcessFind(const AParams: string; var AResults: string);
  public
    procedure RegisterCommands(const ABotsManager: TBotsManager); override;
  end;

var
  Form14: TForm14;

implementation

{$R *.dfm}

var
  BotsManager: TBotsManager;

{ TBotManager }

constructor TBotsManager.Create;
begin
  inherited;

  Self.BotsList := TObjectList<TBot>.Create;
  Self.CommandsList := TDictionary<string, TBotWorker>.Create();
end;

destructor TBotsManager.Destroy;
begin
  FreeAndNil(Self.CommandsList);
  FreeAndNil(Self.BotsList);

  inherited;
end;

procedure TBotsManager.ProcessCommand(const ACommandLine: string);
var
  commandName: string;
  commandParams: string;
  foundedWorker: TBotWorker;
  s: string;
  res: string;
begin
  s := Trim(ACommandLine);
  if s.IndexOf(' ') >= 0 then
  begin
    commandName := AnsiLowerCase(trim(s.Substring(0, s.IndexOf(' '))));
    commandParams := trim(s.Substring(commandName.Length + 1));
  end
  else
  begin
    commandName := AnsiLowerCase(s);
    commandParams := '';
  end;

  if Self.CommandsList.TryGetValue(commandName, foundedWorker) then
  begin
    res := '';
    foundedWorker(commandParams, res);
    ShowMessage(res);
  end
  else
  begin
    ShowMessage('unknown command: ' + commandName);
  end;
end;

procedure TBotsManager.RegisterBot(ABot: TBot);
begin
  Self.BotsList.Add(ABot);

  ABot.RegisterCommands(Self);
end;

procedure TBotsManager.RegisterCommand(const ACommand: string; const AWorker: TBotWorker);
begin
  Self.CommandsList.Add(ACommand, AWorker);
end;

procedure TForm14.Button_SendClick(Sender: TObject);
begin
  BotsManager.ProcessCommand(edit_Command.Text);
end;

procedure TForm14.FormCreate(Sender: TObject);
begin
  BotsManager := TBotsManager.Create;
  BotsManager.RegisterBot(TBotDialogHello.Create);
  BotsManager.RegisterBot(TBotDialogSearch.Create);
end;

procedure TForm14.FormDestroy(Sender: TObject);
begin
  FreeAndNil(BotsManager);
end;

procedure TBotDialogSearch.ProcessFind(const AParams: string; var AResults: string);
var
  id: Integer;
begin
  id := StrToIntDef(AParams, -1);
  if id <> -1 then
  begin
    Self.ProcessFindByID(AParams, AResults);
  end
  else
  begin
    Self.ProcessFindByName(AParams, AResults);
  end;
end;

procedure TBotDialogSearch.ProcessFindByID(const AParams: string; var AResults: string);
var
  id: Integer;
begin
  id := StrToIntDef(AParams, -1);
  if id <> -1 then
  begin
    AResults := 'searching by id = ' + id.ToString;
  end
  else
  begin
    AResults := 'unknown search params';
  end;
end;

procedure TBotDialogSearch.ProcessFindByName(const AParams: string; var AResults: string);
begin
  if AParams <> '' then
  begin
    AResults := 'searching by name = ' + AParams;
  end
  else
  begin
    AResults := 'unknown search params';
  end;
end;

procedure TBotDialogSearch.ProcessStart(const AParams: string; var AResults: string);
begin
  AResults := 'started';
end;

procedure TBotDialogSearch.RegisterCommands(const ABotsManager: TBotsManager);
begin
  ABotsManager.RegisterCommand('start', Self.ProcessStart);
  ABotsManager.RegisterCommand('find by name', Self.ProcessFindByName);
  ABotsManager.RegisterCommand('find by id', Self.ProcessFindByID);
  ABotsManager.RegisterCommand('find', Self.ProcessFind);
end;

procedure TBotDialogHello.ProcessHello(const AParams: string; var AResults: string);
begin
  AResults := 'Hello, username!';
end;

procedure TBotDialogHello.RegisterCommands(const ABotsManager: TBotsManager);
begin
  ABotsManager.RegisterCommand('hello', Self.ProcessHello);
end;

end.
28 окт 18, 16:20    [21717308]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
X11
Member

Откуда: Kharkiv, Ukraine
Сообщений: 12484
JaDi, это тя прямо сейчас написал?
28 окт 18, 16:23    [21717310]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
JaDi
Member

Откуда: Сызрань, Россия
Сообщений: 3862
X11,

ога... дфм форму не прикладываю, она там простая - кнопка и поле для ввода.

К сообщению приложен файл. Размер - 6Kb
28 окт 18, 16:25    [21717313]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
X11
Member

Откуда: Kharkiv, Ukraine
Сообщений: 12484
Ага, спасибо.
Значит, я шёл в нужном направлении.
28 окт 18, 16:31    [21717318]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
X11
Member

Откуда: Kharkiv, Ukraine
Сообщений: 12484
Не смог найти.
Поэтому вопрос.
А есть ли у Delphi встроенный способ создать словарь с регистронезависимым (case-insensitive) поиском по ключам?

DicCmdRows := TDictionary<string, TBotProcRef>.Create(что здесь указать);


Или нужно самому пилить?
30 окт 18, 16:40    [21719217]     Ответить | Цитировать Сообщить модератору
 Re: Дженерики: передача процедуры в качестве параметра  [new]
zinpub
Member

Откуда:
Сообщений: 273
X11,

Надо TCustomEqualityComparer.GetHashCode и Equals - переопределить и вуаля
30 окт 18, 16:47    [21719229]     Ответить | Цитировать Сообщить модератору
Топик располагается на нескольких страницах: [1] 2 3 4 5   вперед  Ctrl      все
Все форумы / Delphi Ответить