I want to implement a RPC module. Different requests are encoded as JSON objects. They will be decoded and then be handled by a request handler. At last a corresponding response will be returned. The demo code looks as follows:
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;
According to the code, the handling of different request types are very similar. If I have 100 different request types, I have to copy and paste the above code block 100 times. This is not clever. I am looking for a better way to do the same logic. My imagination is as follows:
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)
开发者_C百科 Request.Free;
end;
end
else
Result := CreateErrorResponse('Unknown method: ' + Json.ToString);
end;
I do not know, if I can write an universal way to handle a great number of different request types. Development environment Delphi 2010.
Any hint is appreciated.
Your second attempt is very close. You're only missing a couple of details.
Where you've used the made-up type TClassInfo
, you need to define a metaclass to represent your request classes. I assume TAaaRequest
and TBbbRequest
(and the 100 other request classes) all descend from some base TRequest
class. Define TRequestClass
like this:
type
TRequestClass = class of TRequest;
The FromJSON
method does something different for each class, right? If that's the case, then it should be virtual. (If the method does the same thing in each class, then it doesn't have to be virtual, despite what others may tell you.) You don't have to type-cast the result of the constructor; simply declare Info
as a TRequest
instead of a TObject
.
The biggest change you'll need to make is to your IRequestHandler
interface. Since every one of your objects is a TRequest
, it will be clumsy to dispatch to the right interface method without having a giant if
-else
ladder to check each possible class.
Instead, use virtual dispatch again. Give each TRequest
object a virtual Handle
method, so the class declaration will look like this:
type
TRequest = class
public
constructor FromJSON(const json: string);
function Handle: TResponse; virtual; abstract;
end;
Implement Handle
for each descendant, and you're done. Ultimately, the IRequestHandler
interface can go away. You've already written the handling ability into each of the request classes. You don't need one class to represent the request and another class to handle it.
If you want to have a separate handling class, then you could either go with what you have already, where you'll have a big conditional to decide which IRequestHandler
method you'll call, or you have lots of request-handler objects all implement the same interface, and you decide which one to create the same way you decide which request class to create. Then you give the request object to the request-handler object and let them work together.
For example, define your handler interface:
type
IRequestHandler = interface
function Handle(request: TRequest): TResponse;
end;
Register handlers like you register requests:
// Use the same names as the requests, but a different dictionary
FRequestHandlers.Add(TAaaRequest.ClassName, TAaaHandler);
FRequestHandlers.Add(TBbbRequest.ClassName, TBbbHandler);
Instantiate handlers like you do requests:
HandlerType := FRequestHandlers[Method];
HandlerObject := HandlerType.Create;
if not Supports(HandlerObject, IRequestHandler, Handler) then
exit;
Then pass the request to the handler:
Result := Handler.Handle(Request);
精彩评论