предложение по дизайну для декодера сообщений в Delphi - PullRequest
1 голос
/ 06 апреля 2010

Я хочу реализовать модуль RPC. Различные запросы кодируются как объекты JSON. Они будут декодированы, а затем обработаны обработчиком запроса. Наконец соответствующий ответ будет возвращен. Демо-код выглядит следующим образом:

type
  IRequestHandler = interface
    function Handle(const Request: TAaaRequest): TResponse;
    function Handle(const Request: TBbbRequest): TResponse;
  end;

  TDecoder = class
    class function Decode(const Json: TJsonObject; const RequestHandler: IRequestHandler): TResponse;
  end;

class function TDecoder.Decode(const Json: TJsonObject; const RequestHandler: IRequestHandler): TResponse;
var
  Method: string;
  Request: TObject;
begin
  Method := Json['method'].AsString;
  if (Method = TAaaRequest.ClassName) then
  begin
    Request := TAaaRequest.FromJSON(Json); // Casted as TObject
    if Request <> nil then
    begin
      Result := RequestHandler.Handle(TAaaRequest(Request));
      Request.Free;
    end;
  end
  else if (Method = TBbbRequest.ClassName) then
  begin
    Request := TBbbRequest.FromJSON(Json); // Casted as TObject
    if Request <> nil then
    begin
      Result := RequestHandler.Handle(TBbbRequest(Request));
      Request.Free;
    end;
  end
  else
    Result := CreateErrorResponse('Unknown method: ' + Json.ToString);
end;

Согласно коду, обработка разных типов запросов очень похожа. Если у меня есть 100 различных типов запросов, я должен скопировать и вставить вышеуказанный блок кода 100 раз. Это не умно. Я ищу лучший способ сделать ту же логику. Мое воображение выглядит следующим образом:

TDecoder = class
private
  FRequestTypes: TDictionary<string, TClassInfo>; // Does this work?
public
  constructor Create;
  destructor Destroy; override;
  function Decode(const Json: TJsonObject; const RequestHandler: IRequestHandler): TResponse;
end;

constructor TDecoder.Create;
begin
  FRequestTypes := TDictionary<string, TClassInfo>.Create;
  FRequestTypes.Add(TAaaRequest.ClassName, TAaaRequest); // Does this work?
  FRequestTypes.Add(TBbbRequest.ClassName, TBbbRequest); 
end;

destructor TDecoder.Destroy;
begin
  FRequestTypes.Free;
  inherited;
end;

function TDecoder.Decode(const Json: TJsonObject; const RequestHandler: IRequestHandler): TResponse;
var
  Method: string;
  Info: TClassInfo;
  Request: TObject;
begin
  Method := Json['method'].AsString;
  if FRequestTypes.ContainsKey(Method) then
  begin
    // An universal way
    Info := FRequestTypes[Method];
    Request := Info.FromJSON(Json); // Casted as TObject
    if Request <> nil then
    begin
      Result := RequestHandler.Handle(Info(Request)); // Casted to corresponding class type (e.g. TAaaRequest or TBbbRequest)
      Request.Free;
    end;
  end
  else
    Result := CreateErrorResponse('Unknown method: ' + Json.ToString);
end;

Не знаю, смогу ли я написать универсальный способ обработки большого количества различных типов запросов. Среда разработки Delphi 2010.

Любая подсказка приветствуется.

1 Ответ

1 голос
/ 06 апреля 2010

Ваша вторая попытка очень близка. Вам не хватает только нескольких деталей.

Если вы использовали составной тип TClassInfo, вам нужно определить метакласс для представления классов вашего запроса. Я предполагаю, что TAaaRequest и TBbbRequest (и 100 других классов запросов) все происходят от некоторого базового TRequest класса. Определите TRequestClass как это:

type
  TRequestClass = class of TRequest;

Метод FromJSON делает что-то свое для каждого класса, верно? Если это так, то это должно быть виртуальным. (Если метод делает то же самое в каждом классе, тогда он не может быть виртуальным, несмотря на то, что другие могут вам сказать.) Вам не нужно приводить тип результата к конструктору ; просто объявите Info как TRequest вместо TObject.

Самое большое изменение, которое вам нужно сделать, - это интерфейс IRequestHandler. Поскольку каждый из ваших объектов является TRequest, будет неуклюже отправлять правильный интерфейсный метод без гигантской if - else лестницы для проверки каждого возможного класса.

Вместо этого снова используйте виртуальную диспетчеризацию. Присвойте каждому TRequest объекту виртуальный Handle метод, чтобы объявление класса выглядело так:

type
  TRequest = class
  public
    constructor FromJSON(const json: string);
    function Handle: TResponse; virtual; abstract;
  end;

Реализация Handle для каждого потомка, и все готово. В конечном итоге, интерфейс IRequestHandler может исчезнуть. Вы уже написали возможность обработки в каждом из классов запросов. Вам не нужен один класс для представления запроса и другой класс для его обработки.

Если вы хотите иметь отдельный класс обработки, то вы можете либо пойти с тем, что у вас уже есть, где у вас будет большое условное решение, какой метод IRequestHandler вы будете вызывать, или у вас много запросов Все объекты -handler реализуют один и тот же интерфейс, и вы решаете, какой из них создать таким же образом, как вы решаете, какой класс запроса создать. Затем вы передаете объект request объекту request-handler и позволяете им работать вместе.

Например, определите интерфейс вашего обработчика:

type
  IRequestHandler = interface
    function Handle(request: TRequest): TResponse;
  end;

Регистрируйте обработчики так же, как вы регистрируете запросы:

// Use the same names as the requests, but a different dictionary
FRequestHandlers.Add(TAaaRequest.ClassName, TAaaHandler);
FRequestHandlers.Add(TBbbRequest.ClassName, TBbbHandler);

Создайте обработчики, как вы делаете запросы:

HandlerType := FRequestHandlers[Method];
HandlerObject := HandlerType.Create;
if not Supports(HandlerObject, IRequestHandler, Handler) then
  exit;

Затем передайте запрос обработчику:

Result := Handler.Handle(Request);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...