Execução da variação da cadeia de responsabilidade

Eu tenho um pipeline de tarefas que basicamente é uma variação do padrão de cadeia de responsabilidade.

Uma tarefa no meu pipeline parece abaixo

internal interface IPTask
{
    bool CanExecute(T instance);
    T Process(T instance);
}

..e meu processador parece

internal interface IProcessor
{
    T Execute(T instance);
}

e minha implementação concreta é assim:

public class Processor : IProcessor
{
    private readonly ITasks tasks;

    public Processor(ITasks tasks)
    {
        this.tasks= tasks;
    }

    public T Execute(T instance)
    {
         var taskstoExecute = tasks.GetTasks()
                                   .Where(task => task.CanExecute(instance));

         taskstoExecute.ToList().ForEach(task=>task.Process(instance));

         return T;
    }
}

..e minhas tarefas são assim:

internal interface ITasks
{
    IEnumerable> GetTasks();
}

T poderia ser instâncias diferentes, mas vinculadas por um contrato genérico. Uma das tarefas é mapear o objeto recebido em um objeto completamente diferente e encaminhar essa instância a partir daí.

Agora, como você veria, estou executando todas as tarefas no pipeline, gostaria de modificar isso para o seguinte:

  • A entrada para o método Executar para a próxima tarefa deve ser da tarefa executada anteriormente.
  • Se o CanExecute falhar em uma tarefa, o pipeline deverá parar de processar as tarefas.

Você pode por favor me ajudar a conseguir isso. Você também esperaria que o código fosse estruturado de maneira diferente para esse propósito?

0
adicionado editado
Visualizações: 1

2 Respostas

Que tal agora:

public T Execute(T instance)
{
     T result = instance;
     foreach(var individual in tasks.GetTasks())
     {
         if(!individual.CanExecute()) break;

         result = individual.Process(result);
     }

     return result;
}

Como você tem atualmente, é muito mais como um padrão composto do que uma cadeia de responsabilidade. Essa mudança torna um pouco mais CoR-ish. Mas é mais importante observar se ela atende às suas necessidades do que usar o jargão de padrão de design correto. :)

0
adicionado

Com essa implementação, o CanProcess é realmente usado para acionar uma exceção que interromperá o processo.

Eu adicionei uma segunda implementação, chamada ExecuteWithPartial , que lida com a exceção, caso seja esse o comportamento que você está esperando. Ele processa mas, se houver um erro, retorna o resultado parcial até esse ponto.

public class Processor : IProcessor
{
    //... rest of your code

    public T Execute(T instance)
    {
        return this._tasks.GetTasks().Aggregate(instance, (current, task) => InternalExecute(task, current));
    }

    public T ExecuteWithPartial(T instance)
    {
        var target = instance;
        try
        {
            foreach (var task in this._tasks.GetTasks())
            {
                target = InternalExecute(task, target);
            }
            return target;
        }
        catch (CantExecuteException)
        {
            return target;
        }
    }


    private static T InternalExecute(IPTask task, T instance)
    {
        if (!task.CanExecute(instance))
            throw new CantExecuteException();
        return task.Process(instance);
    }
}

E a nova classe de exceção é:

public class CantExecuteException : Exception
{
}
0
adicionado