lundi 6 juillet 2020

chain of responsibility and generics

I have a chain of responsibility that applies filters to a collection. I am trying to make a factory to build that chain of responsibility from a configuration. My concrete types for the chain arent generic but their abstraction are, and the genericity makes me struggle to put them in a collection for a mapping between config and correct chain node implementation.

Here is the implementation of the chain :

public interface IFilter<T> where T : IFilterable
{
    IFilter<T> SetNext(IFilter<T> next);
    IEnumerable<T> Filter(IEnumerable<T> data);
}

public class BaseFilter<T> : IFilter<T> where T : IFilterable
{
    protected IFilter<T> Next { get; set; }

    public IFilter<T> SetNext(IFilter<T> next)
    {
        Next = next;
        return Next;
    }

    public virtual IEnumerable<T> Filter(IEnumerable<T> data)
    {
        return Next == null ? data : Next.Filter(data);
    }
}

Here is an example of concrete implementation of the nodes of the chain :

public interface IFilterable {}

public interface ICanFly: IFilterable
{
    bool CanFly { get; }
}

public interface ITransport : IFilterable
{
    int Passengers { get; }
}

public class Duck : ICanFly
{
    public bool CanFly => true;
}

public class Plane : ICanFly, ITransport
{
    public bool CanFly => true;
    public int Passengers => 5;
}

public class FlyerFilter : BaseFilter<ICanFly>
{
    public override IEnumerable<ICanFly> Filter(IEnumerable<ICanFly> data)
    {
        return base.Filter(data.Where(x => x.CanFly));
    }
}

public class SmallTransportFilter : BaseFilter<ITransport>
{
    public override IEnumerable<ITransport> Filter(IEnumerable<ITransport> data)
    {
        return base.Filter(data.Where(x => x.Passengers < 8));
    }
}

My problems start when I want to make a factory that map the configuration to my concrete types (FlyerFilter and SmallTransportFilter in my example)

public interface IFilterChainBuilder<T> where T : IFilterable
{
    IFilter<T> GenerateFilterResponsabilityChain(IEnumerable<string> filtersParam);
}

public class FilterChainBuilder<T> :  IFilterChainBuilder<T> where T : IFilterable
{


     private readonly Dictionary<string, IFilter<T>> _paramToFiltersMap;

     public FilterChainBuilder()
     {
         _paramToFiltersMap = new Dictionary<string, IFilter<T>>(StringComparer.OrdinalIgnoreCase)
         {
            {"Flyers",  new FlyerFilter()},                  // Compile error, cannot convert from FlyerFilter to IFilter<T>
            {"SmallTransport",  new SmallTransportFilter()}  // Compile error, cannot convert from SmallTransportFilter to IFilter<T>
         };
     }

     public IFilter<T> GenerateFilterResponsabilityChain(IEnumerable<string> filtersParam)
     {
         IFilter<T> filterResponsabilityChain = null;

         foreach (var parameter in filtersParam)
             if (_paramToFiltersMap.TryGetValue(parameter, out var filter))
             {
                  if (filterResponsabilityChain == null)
                      filterResponsabilityChain = filter;
                  else
                     filterResponsabilityChain.SetNext(filter);
            }
            else
            {
                 throw new ArgumentException(
                     $"config parameter {parameter} has no associated IFilter");
            }

         return filterResponsabilityChain ?? new BaseFilter<T>();
     }
}

I can understand why it doesnt compile. Since FlyerFilter is a BaseFilter<ICanFly> (so a IFilter<ICanFly>), it would be bad if I declared a new FilterChainBuilder<PlaceholderType>. And actually since SmallTransportFilter inherit from a different T type, the only possible IFilterable implementation would have to implement both ITransport and ICanFly.

I tried to remove the generic T type entirely but the consummer of this chain of responsability relies on that IEnumerable<T> Filter(IEnumerable<T> data) signature and wants an enumeration of concrete types rather than IFilterable.

I am not sure how could I fix this problem, I am currently stuck here.

Aucun commentaire:

Enregistrer un commentaire