简体   繁体   中英

What creational design pattern would suit these requirements?

I have a scenario where I pass a data 'request' object into a service and the service itself must create a number of different 'processors' depending on the data in the request.

Each processor can itself be one of a number of different types. So for example, a crude ugly implementation might look like:

public Collection<IProcessor> UglyCreationalMethod(Request request)
{
    var processors = new Collection<IProcessor>();

    if(request.Type == RequestType.SomeVal)
    {
        if(request.Id > 1000)
        {
            processors.Add(new ProcessLargeRequest(request));
        }
        else
        {
            processors.Add(new ProcessSmallRequest(request));
        }
    }
    else (request.Type == RequestType.SomeOtherVal)
    {
        if(request.Source == RequestSource.User)
        {
            processors.Add(new ProcessUserRequest(request));
        }
        else
        {
            processors.Add(new ProcessCorpRequest(request));
        }
    }

    if(request.SomeProp == "blah")
        processors.Add(new ProcessBlahRequest(request));

    // ... etc ad infinitum :)

    return processors;
}

I'm looking for a pattern that's extensible and hides the nasty logic that determines the types of processors the service needs to create, so it's a bit cleaner and more maintainable than the above ugly code.

I know about factory methods, but these alone will not suffice.

Suggestions appreciated.

One pattern that springs to mind is Chain of responsibility (perhaps not a creation pattern)

Firstly, you need RequestHandlers

public interface IRequestHandler
    {
        bool CanHandle(Request req);

        void Handle(Request req);
    }

    public class LargeRequestHandler : IRequestHandler
    {
        public bool CanHandle(Request req)
        {
            return (req.Type == RequestType.SomeVal && req.id > 1000);
        }

        public void Handle(Request req)
        {
            processors.Add(new ProcessLargeRequest(request));
        }
    }

    public class SmallRequestHandler : IRequestHandler
    {
        public bool CanHandle(Request req)
        {
            return (req.Type == RequestType.SomeVal && req.id < 1000);
        }

        public void Handle(Request req)
        {
            processors.Add(new SmallLargeRequest(request));
        }
    }

... similarly keep adding classes for more handlers as you need.

Then create a Chain of these handlers like

public class RequestChain
    {
        IRequestHandler[] handlers;

        public RequestChain()
        {
            handlers = new[] { new LargeRequestHandler(), new SmallRequestHandler() };
        }

        public void ProcessRequest(Request req)
        {
            foreach (var handler in handlers)
            {
                if (handler.CanHandle(req))
                {
                    handler.Handle(req);
                }
            }
        }
    }

Hope this helps. Cheers.

A factory is probably the right way to go, but you need a bit more behind it, namely, configuration.

For example, you may want a config file that looks something like

<processor type="typename">
  <rules>
    <rule type="requestIdThresholdRule">
      <configuration evaluationType="ExceedsThreshold" threshold="1000"/>
    </rule>
  </rules>
</processor>
<processor type="othertypename">
  <rules>
    <rule type="yadda">
       <configuration evaluationType="DoesNotMeetThreshold" threshold="1000"/>
    </rule>
  </rules>

This allows you a lot of flexibility for defining which types get created based on the run-time evaluation of the context. You don't have a load of code sitting in the factory method itself, but within a few rules which are mainly driven by configuration values. A lot less code, a lot more flexible.

Then you just call it like:

 List<ISomething> items = ISomethingFactory.CreateISomethingsForContext(context);

What you want to do is to create a Factory, the main question is how do you want to configure it. I like to follow an approach where choosing which method should be created resides within responsibilities of Factory not in classes that are being created - it leads to better configurability and it's easier to manage.

I would create something like this:

    public struct ProcessorCreationSettings
    {
        public Predicate<Request> Predicate;
        public Func<Request, IProcessor> Creator;
    }

    public class ProcessorFactory
    {
        protected static List<ProcessorCreationSettings> settings = new List<ProcessorCreationSettings>();

        static ProcessorFactory()
        {
            settings.Add(new ProcessorCreationSettings
            {
                Predicate = r => r.Type == RequestType.SomeOther && r.Id > 1000,
                Creator = r => new ProcessLargeRequest(r)
            });
            settings.Add(new ProcessorCreationSettings
            {
                Predicate = r => r.Type == RequestType.SomeOther && r.Id <= 1000,
                Creator = r => new ProcessSmallRequest(r)
            });
        }

        public List<IProcessor> Create(Request request)
        {
            return settings
                .Where(s => s.Predicate(request))
                .Select(s => s.Creator(request))
                .ToList();
        }
    }

The configuration part is done by static list, however you can use an IoC container for this as well, if it has such feature.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM