Выполнение варианта цепочки ответственности

У меня есть конвейер задач, который в основном представляет собой вариант схемы цепочки ответственности.

Задача в моем конвейере выглядит так:

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

..и мой процессор выглядит так

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

и моя конкретная реализация выглядит так:

public class Processor<T> : IProcessor<T>
{
    private readonly ITasks<T> tasks;

    public Processor(ITasks<T> 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;
    }
}

..и мои задачи выглядят так:

internal interface ITasks<T>
{
    IEnumerable<IPTask<T>> GetTasks();
}

T могут быть разными экземплярами, но связанными общим контрактом. Одна из задач заключается в отображении входящего объекта в совершенно другой объект и пересылке этого экземпляра оттуда.

Теперь, как вы видите, я выполняю все задачи в конвейере, я хотел бы изменить это следующим образом:

  • Входные данные для метода Execute для следующей задачи должны поступать из ранее выполненной задачи.
  • Если CanExecute не удается выполнить задачу, конвейер должен прекратить обработку задач.

Не могли бы вы помочь мне в получении этого. Также вы ожидаете, что код будет структурирован по-другому для этой цели?


person Mike    schedule 21.06.2012    source источник


Ответы (2)


Как насчет этого:

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

         result = individual.Process(result);
     }

     return result;
}

В том виде, в каком он у вас есть сейчас, он больше похож на составной шаблон, чем на цепочку ответственности. Это изменение делает его немного более похожим на CoR. Но гораздо важнее отметить, соответствует ли он вашим потребностям, чем использовать правильный жаргон шаблонов проектирования. :)

person tallseth    schedule 22.06.2012

В этой реализации CanProcess фактически используется для запуска исключения, которое остановит процесс.

Я добавил вторую реализацию, названную ExecuteWithPartial, которая обрабатывает исключение, если вы ожидаете такого поведения. Он обрабатывает, но если есть ошибка, он возвращает частичный результат до этой точки.

public class Processor<T> : IProcessor<T>
{
    //... 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<T> task, T instance)
    {
        if (!task.CanExecute(instance))
            throw new CantExecuteException();
        return task.Process(instance);
    }
}

И новый класс Exception:

public class CantExecuteException : Exception
{
}
person Pablo Romeo    schedule 21.06.2012