Pipeline
Processes a request through handlers that progressively apply transformations or enrichments.
You need to apply filters to an image.
Image → (black and white) → (blurred background) → (crop) → (resize) → Resulting image
If the steps are tightly coupled, you cannot easily reorder or reuse them, and the processing method will quickly become large and difficult to maintain. As more filters are added, it becomes increasingly important to keep each part of the process isolated. In some domains, you may also need a pipeline to behave as a step itself in order to support nested sub-processes.
Handlers do not know about each other. They only know what work they must perform, what data they operate on, and when they should execute.
#C# Example
public record ImageContext(ExpandoObject Image, HashSet<Type> RequestedFilters);
public interface IStep<TContext>
{
bool CanExecute(TContext context);
void Execute(TContext context);
}
public class BackAndWhiteFilter : IStep<ImageContext>
{
public bool CanExecute(ImageContext context)
{
return context.RequestedFilters.Contains(typeof(BackAndWhiteFilter));
}
public void Execute(ImageContext context)
{
dynamic image = context.Image;
image.IsBlackAndWhite = true;
}
}
public class ResizeFilter : IStep<ImageContext>
{
public bool CanExecute(ImageContext context)
{
return context.RequestedFilters.Contains(typeof(ResizeFilter));
}
public void Execute(ImageContext context)
{
dynamic image = context.Image;
image.IsResized = true;
}
}
public class BlurFilter : IStep<ImageContext>
{
public bool CanExecute(ImageContext context)
{
return context.RequestedFilters.Contains(typeof(BlurFilter));
}
public void Execute(ImageContext context)
{
dynamic image = context.Image;
image.IsBlurred = true;
}
}
public interface IPipeline<TContext> : IStep<TContext>
{
IPipeline<TContext> AddStep(IStep<TContext> context);
}
public class Pipeline<TContext> : IPipeline<TContext>
{
private readonly List<IStep<TContext>> _steps = new();
public IPipeline<TContext> AddStep(IStep<TContext> context)
{
_steps.Add(context);
return this;
}
public bool CanExecute(TContext context) => true;
public void Execute(TContext context)
{
foreach (var step in _steps)
{
if (step.CanExecute(context))
step.Execute(context);
}
}
}When sequential processing is not required, or when it is not possible to identify clear and independent steps that progressively complement, transform, or enrich the context.
| Pros | Cons |
|---|---|
| Steps can be reordered and reused | It can be difficult to determine which step processed the context |
#behavioral #chain