Home > database >  Which design pattern to use for using different subclasses based on input
Which design pattern to use for using different subclasses based on input

Time:07-07

There is an interface called Processor, which has two implementations SimpleProcessor and ComplexProcessor.

Now I have a process, which consumes an input, and then using that input decides whether it should use SimpleProcessor or ComplexProcessor.

Current solution : I was thinking to use Abstract Factory, which will generate the instance on the basis of the input.

But the issue is that I don't want new instances. I want to use already instantiated objects. That is, I want to re-use the instances.

That means, Abstract factory is absolutely the wrong pattern to use here, as it is for generating objects on the basis of type.

Another thing, that our team normally does is to create a map from input to the corresponding processor instance. And at runtime, we can use that map to get the correct instance on the basis of input.

This feels like a adhoc solution.

I want this to be extendable : new input types can be mapped to new processor types.

Is there some standard way to solve this?

CodePudding user response:

You can use a variation of the Chain of Responsibility pattern.
It will scale far better than using a Map (or hash table in general).

This variation will support dependency injection and is very easy to extend (without breaking any code or violating the Open-Closed principle).
Opposed to the classic version, handlers do not need to be explicitly chained. The classic version scales very bad.

The pattern uses polymorphism to enable extensibility and is therefore targeting an object oriented language.

The pattern is as follows:

  1. The client API is a container class, that manages a collection of input handlers (for example SimnpleProcessor and ComplexProcessor).
  2. Each handler is only known to the container by a common interface and unknown to the client.
  3. The collection of handlers is passed to the container via the constructor (to enable optional dependency injection).
  4. The container accepts the predicate (input) and passes it on to the anonymous handlers by iterating over the handler collection.
  5. Each handler now decides based on the input if it can handle it (return true) or not (return false).
  6. If a handler returns true (to signal that the input was successfully handled), the container will break further input processing by other handlers (alternatively, use a different criteria e.g., to allow multiple handlers to handle the input).

In the following very basic example implementation, the order of handler execution is simply defined by their position in their container (collection).
If this isn't sufficient, you can simply implement a priority algorithm.

Implementation (C#)

Below is the container. It manages the individual handler implementation using polymorphism. Since handler implementation are only known by their common interface, the container scales extremely well: simply add/inject an additional handler implementation.
The container is actually used directly by the client (whereas the handlers are hidden from the client, while anonymous to the container).

interface IInputProcessor
{
  void Process(object input);
}
class InputProcessor : IInputProcessor
{
  private IEnumerable<IInputHandler> InputHandlers { get; }

  // Constructor.
  // Optionally use an IoC container to inject the dependency (a collection of input handlers).
  public InputProcessor(IEnumerable<IInputHandler> inputHandlers)
  {
    this.InputHandlers = inputHandlers;
  }

  // Method to handle the input.
  // The input is then delegated to the input handlers.
  public void Process(object input)
  {
    foreach (IInputHandler inputHandler in this.InputHandlers)
    {
      if (inputHandler.TryHandle(input))
      {
        return;
      }
    }
  }
}

Below are the input handlers.
To add new handlers i.e. to extend input handling, simply implement the IInputHandler interface and add it to a collection which is passed/injected to the container (IInputProcessor):

interface IInputHandler
{
  bool TryHandle(object input);
}
class SimpleProcessor : IInputHandler
{
  public bool TryHandle(object input)
  {
    if (input == 1)
    {
      //TODO::Handle input

      return true;
    }

    return false;
  }
}

class ComplexProcessor : IInputHandler
{
  public bool TryHandle(object input)
  {
    if (input == 3)
    {
      //TODO::Handle input

      return true;
    }

    return false;
  }
}

Usage Example

public class Program
{
  public static void Main()
  {
    /* Setup Chain of Responsibility. 
    /* Preferably configure an IoC container. */
    var inputHandlers = new List<IInputHandlers> 
    {
      new SimpleProcessor(),
      new ComplexProcessor()
    };
    
    IInputProcessor inputProcessor = new InputProcessor(inputHandlers);
    
    
    /* Use the handler chain */
    int input = 3;
    inputProcessor.Pocess(input); // Will execute the ComplexProcessor

    input = 1;
    inputProcessor.Pocess(input); // Will execute the SimpleProcessor
  }
}

CodePudding user response:

It is possible to use Strategy pattern with combination of Factory pattern. Factory objects can be cached to have reusable objects without recreating them when objects are necessary. As an alternative to caching, it is possible to use singleton pattern. In ASP.NET Core it is pretty simple. And if you have DI container, just make sure that you've set settings of creation instance to singleton

Let's start with the first example. We need some enum of ProcessorType:

public enum ProcessorType 
{
    Simple, Complex
}

Then this is our abstraction of processors:

public interface IProcessor
{
    DateTime DateCreated { get; }
}

And its concrete implemetations:

public class SimpleProcessor : IProcessor
{
    public DateTime DateCreated { get; } = DateTime.Now;
}

public class ComplexProcessor : IProcessor
{
    public DateTime DateCreated { get; } = DateTime.Now;
}   

Then we need a factory with cached values:

public class ProcessorFactory
{
    private static readonly IDictionary<ProcessorType, IProcessor> _cache 
        = new Dictionary<ProcessorType, IProcessor>()
    {
        { ProcessorType.Simple,  new SimpleProcessor() },
        { ProcessorType.Complex, new ComplexProcessor() }
    };

    public IProcessor GetInstance(ProcessorType processorType) 
    {
       return _cache[processorType];
    }
}

And code can be run like this:

ProcessorFactory processorFactory = new ProcessorFactory();
Thread.Sleep(3000);
var simpleProcessor = processorFactory.GetInstance(ProcessorType.Simple);
Console.WriteLine(simpleProcessor.DateCreated); // OUTPUT: 2022-07-07 8:00:01

ProcessorFactory processorFactory_1 = new ProcessorFactory();
Thread.Sleep(3000);
var complexProcessor = processorFactory_1.GetInstance(ProcessorType.Complex);
Console.WriteLine(complexProcessor.DateCreated); // OUTPUT: 2022-07-07 8:00:01  

The second way

The second way is to use DI container. So we need to modify our factory to get instances from dependency injection container:

public class ProcessorFactoryByDI
{
    private readonly IDictionary<ProcessorType, IProcessor> _cache;

    public ProcessorFactoryByDI(
        SimpleProcessor simpleProcessor,
        ComplexProcessor complexProcessor)
    {
        _cache = new Dictionary<ProcessorType, IProcessor>()
        {
            { ProcessorType.Simple, simpleProcessor },
            { ProcessorType.Complex, complexProcessor }
        };
    }

    public IProcessor GetInstance(ProcessorType processorType)
    {
        return _cache[processorType];
    }
}

And if you use ASP.NET Core, then you can declare your objects as singleton like this:

services.AddSingleton<SimpleProcessor>();
services.AddSingleton<ComplexProcessor>();

Read more about lifetime of an object

  • Related