我建议换个稍微不同的设计。这个想法是基于装饰图案的。
首先,我会
Context
一个非泛型类,并删除输入和输出值。在我的设计中,上下文只包含上下文信息(如处理时间和消息):
public class Context
{
public Context()
{
UniqueToken = new Guid();
Logs = new List<string>();
}
public Guid UniqueToken { get; }
public DateTime ProcessStartedAt { get; set; }
public DateTime ProcessEndedAt { get; set; }
public long ProcessTimeInMilliseconds
{
get
{
return (long)ProcessEndedAt.Subtract(ProcessStartedAt).TotalMilliseconds;
}
}
public List<string> Logs { get; set; }
}
然后,我将使处理器接口通用:
public interface IProcessor<InputType, OutputType>
{
OutputType Process(InputType input, Context context);
}
然后我把你的
Container
变成一个
Pipeline
使用泛型类型参数:
public interface IPipeline<InputType, OutputType>
{
OutputType Execute(InputType input, out Context context);
OutputType ExecuteSubPipeline(InputType input, Context context);
}
这两个函数的区别在于前者初始化上下文,后者只使用上下文。如果你不想让你的客户访问,你可以把它分成一个公共接口和一个内部接口
ExecuteSubPipeline()
.
AppendProcessor()
这将创建一个添加了给定处理器的新管道:
public abstract class PipelineBase<InputType, ProcessorInputType, OutputType> : IPipeline<InputType, OutputType>
{
protected IProcessor<ProcessorInputType, OutputType> currentProcessor;
public PipelineBase(IProcessor<ProcessorInputType, OutputType> processor)
{
currentProcessor = processor;
}
public IPipeline<InputType, ProcessorOutputType> AppendProcessor<ProcessorOutputType>(IProcessor<OutputType, ProcessorOutputType> processor)
{
return new Pipeline<InputType, OutputType, ProcessorOutputType>(processor, this);
}
public OutputType Execute(InputType input, out Context context)
{
context = new Context();
context.ProcessStartedAt = DateTime.Now;
var result = ExecuteSubPipeline(input, context);
context.ProcessEndedAt = DateTime.Now;
return result;
}
public abstract OutputType ExecuteSubPipeline(InputType input, Context context);
}
public class TerminalPipeline<InputType, OutputType> : PipelineBase<InputType, InputType, OutputType>
{
public TerminalPipeline(IProcessor<InputType, OutputType> processor)
:base(processor)
{ }
public override OutputType ExecuteSubPipeline(InputType input, Context context)
{
return currentProcessor.Process(input, context);
}
}
public class Pipeline<InputType, ProcessorInputType, OutputType> : PipelineBase<InputType, ProcessorInputType, OutputType>
{
IPipeline<InputType, ProcessorInputType> previousPipeline;
public Pipeline(IProcessor<ProcessorInputType, OutputType> processor, IPipeline<InputType, ProcessorInputType> previousPipeline)
: base(processor)
{
this.previousPipeline = previousPipeline;
}
public override OutputType ExecuteSubPipeline(InputType input, Context context)
{
var previousPipelineResult = previousPipeline.ExecuteSubPipeline(input, context);
return currentProcessor.Process(previousPipelineResult, context);
}
}
为了便于使用,我们还创建一个helper函数来创建一个终端启动管道(以允许类型参数推导):
public static class Pipeline
{
public static TerminalPipeline<InputType, OutputType> Create<InputType, OutputType>(IProcessor<InputType, OutputType> processor)
{
return new TerminalPipeline<InputType, OutputType>(processor);
}
}
然后,我们可以在各种处理器上使用这种结构。例如:
class FloatToStringProcessor : IProcessor<float, string>
{
public string Process(float input, Context context)
{
return input.ToString();
}
}
class RepeatStringProcessor : IProcessor<string, string>
{
public string Process(string input, Context context)
{
return input + input + input;
}
}
class Program
{
public static void Main()
{
var pipeline = Pipeline
.Create(new FloatToStringProcessor())
.AppendProcessor(new RepeatStringProcessor());
Context ctx;
var result = pipeline.Execute(5, out ctx);
Console.WriteLine($"Pipeline result: {result}");
Console.WriteLine($"Pipeline execution took {ctx.ProcessTimeInMilliseconds} milliseconds");
}
}
Pipeline result: 555
Pipeline execution took 6 milliseconds
我不明白你说的短路是什么意思。在我看来,短路只对(至少)不需要计算一个操作数的二元运算符有意义。但是,由于您的运算符都是一元的,所以不能真正应用。处理器总是可以检查输入,并在发现不需要处理时直接返回。
动态荷载可以通过添加类似
LoadProcessors()
IPipeline
接口,类似于
. 在这种情况下,处理器对象必须是代表(仍然是正确类型的)。然后,
可以在加载后用它们的实际处理器替换它们。