Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Improving this Generic Request Response Pattern

Lately I seem to be working with lots of request/response stuff and I thought of creating something generic.

I have the following but I am not happy about creating multiple ifs statements and I would like to avoid them.

The idea is this:

Regardless of the request /response process them. How can I remove these if statement within my generic request Handler?

Code

class Program
{
    private static void Main()
    {
        IRequestResponseFactory factory = new RequestResponseFactory();

        var customerRequest = new CustomerRequest { Name = "Joe", Surname = "Bloggs" };
        var customerResponse = factory.ProcessRequest<CustomerRequest, CustomerResponse>(customerRequest);

        var billRequest = new BillRequest() { Amount = 100m };
        var billResponse = factory.ProcessRequest<BillRequest, BillResponse>(billRequest);

        Console.WriteLine(billResponse.Success);
        Console.WriteLine(customerResponse.Success);
        Console.ReadKey();

    }
}


public class CustomerRequest : IRequestData<CustomerResponse>
{
    public string Name { get; set; }
    public string Surname { get; set; }
}

public class CustomerResponse
{
    public bool Success { get; set; }
}
public class BillRequest : IRequestData<BillResponse>
{
    public decimal Amount { get; set; }
}
public class BillResponse
{
    public bool Success { get; set; }
}
public interface IRequestData<TResponse>
{
}

public interface IRequestHandler<TRequest, TResponse> where TRequest : IRequestData<TResponse>
{
    TResponse ProcessRequest(TRequest request);
}

public interface IRequestResponseFactory
{
    TResponse ProcessRequest<TRequest, TResponse>(TRequest request) where TRequest : IRequestData<TResponse>;
}
class RequestResponseFactory : IRequestResponseFactory
{
    public TResponse ProcessRequest<TRequest, TResponse>(TRequest request) where TRequest : IRequestData<TResponse>
    {
        var handler = new GenericRequestHandler<TRequest, TResponse>();
        TResponse response = handler.ProcessRequest(request);
        return response;
    }
}
public class GenericRequestHandler<TRequest, TResponse> : IRequestHandler<TRequest, TResponse> where TRequest : IRequestData<TResponse>
{
    public TResponse ProcessRequest(TRequest request)
    {
        var response = default(TResponse);

        //How do I avoid this if statements????

        if (request is IRequestData<CustomerResponse>)
        {
            var tempResponse = new CustomerResponse { Success = true };
            response = (TResponse)Convert.ChangeType(tempResponse, typeof(TResponse));
            return response;
        }
        if (request is IRequestData<BillResponse>)
        {
            var tempResponse = new BillResponse { Success = false };
            response = (TResponse)Convert.ChangeType(tempResponse, typeof(TResponse));
            return response;
        }
        return response;
    }
}
like image 954
user9969 Avatar asked Nov 04 '12 11:11

user9969


1 Answers

You can replace the GenericRequestHandler<TRequest, TResponse> class with specialized IRequestHandlers and have the factory keep track of all available such handlers via reflection.

Concretely, these could be the handlers:

public class CustomerRequestHandler : IRequestHandler<CustomerRequest, CustomerResponse>
{
    public CustomerResponse ProcessRequest(CustomerRequest request)
    {
        return new CustomerResponse { Success = true };
    }
}

public class BillRequestHandler : IRequestHandler<BillRequest, BillResponse>
{
    public BillResponse ProcessRequest(BillRequest request)
    {
        return new BillResponse { Success = false };
    }
}

Now, the factory, instead of just forwarding the call to the generic handler that contains the if ugliness, will do the following:

  • initialize itself by scanning the current assembly (and possibly other assemblies as well) for types that implement IRequestHandler<TRequest, TResponse>
  • will build a dictionary of handler types, where the key is the TRequest type and the value is the handler type
  • on any incoming request it checks the handler list to see if that particular type can be handled and:
    • delegates the work to the available handler
    • throws an exception if no suitable handler is found

This is a possible implementation for the factory class:

class RequestResponseFactory : IRequestResponseFactory
{
    private readonly Dictionary<Type, Type> _requestHandlerTypes;

    public RequestResponseFactory()
    {
        _requestHandlerTypes =
            typeof(RequestResponseFactory).Assembly.GetTypes()
                .Where(t => !t.IsAbstract)
                .Select(t => new
                {
                    HandlerType = t,
                    RequestType = GetHandledRequestType(t)
                })
                .Where(x => x.RequestType != null)
                .ToDictionary(
                    x => x.RequestType,
                    x => x.HandlerType
                );
    }

    private static Type GetHandledRequestType(Type type)
    {
        var handlerInterface = type.GetInterfaces()
            .FirstOrDefault(i =>
                i.IsGenericType &&
                i.GetGenericTypeDefinition() == typeof(IRequestHandler<,>));

        return handlerInterface == null ? null : handlerInterface.GetGenericArguments()[0];
    }

    public TResponse ProcessRequest<TRequest, TResponse>(TRequest request) where TRequest : IRequestData<TResponse>
    {
        if (!_requestHandlerTypes.ContainsKey(typeof(TRequest)))
            throw new ApplicationException("No handler registered for type: " + typeof(TRequest).FullName);

        var handlerType = _requestHandlerTypes[typeof(TRequest)];

        var handler = (IRequestHandler<TRequest, TResponse>)Activator.CreateInstance(handlerType);

        return handler.ProcessRequest(request);
    }
}

The complete program with working sample is available at http://ideone.com/TxRnEi.

The output is the same as the output of the original program, but now the program allows you to add new types of requests and new types of handlers and will be able to dynamically use them at run time, without you having to write the long if/case statements to specify when they should be used.

like image 123
Cristian Lupascu Avatar answered Nov 04 '22 04:11

Cristian Lupascu