代码之家  ›  专栏  ›  技术社区  ›  mohammad rostami siahgeli

如何在C中创建通用管道?[关闭]

  •  2
  • mohammad rostami siahgeli  · 技术社区  · 8 年前

    我正在尝试创建一个通用(通用) pipeline ASP.NET Core Middleware BRE )

    它需要获得一个输入模型,管理一系列先前加载过的处理器,并在输入旁边返回一个封装在超级模型中的输出模型。

    这是我所做的。我创造了一个 Context 类,它表示总体数据/模型:

    public class Context<InputType, OutputType> where InputType : class, new() where OutputType : class, new()
    {
        public Context()
        {
            UniqueToken = new Guid();
            Logs = new List<string>();
        }
    
        public InputType Input { get; set; } 
    
        public OutputType Output { get; set; }
    
        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
    {
        void Process<InputType, OutputType>(Context<InputType, OutputType> context, IProcessor next) where InputType : class, new() where OutputType : class, new();
    }
    

    然后我创造了一个 Container

    public class Container<InputType, OutputType> where InputType : class, new() where OutputType : class, new()
    {
        public static List<IProcessor> Processors { get; set; }
    
        public static void Initialize()
        {
            LoadProcessors();
        }
    
        private static void LoadProcessors()
        {
            // loading processors from assemblies dynamically
        }
    
        public static Context<InputType, OutputType> Execute(InputType input)
        {
            if (Processors.Count == 0)
            {
                throw new FrameworkException("No processor is found to be executed");
            }
            if (input.IsNull())
            {
                throw new BusinessException($"{nameof(InputType)} is not provided for processing pipeline");
            }
            var message = new Context<InputType, OutputType>();
            message.Input = input;
            message.ProcessStartedAt = DateTime.Now;
            Processors[0].Process(message, Processors[1]);
            message.ProcessEndedAt = DateTime.Now;
            return message;
        }
    }
    

    我知道如何从给定文件夹中的程序集动态加载处理器,所以这不是问题。但我被困在以下几点上:

    1. Next 属性,但我想它是反对的 SRP
    2. 如何确保正确的订购(一种选择是 Order SRP公司 ,每个处理器只关心处理,而不关心其顺序)
    3. 如何确保使用简单?在为团队创建基础设施时,对开发人员友好是一件大事。否则团队成员不会接受。
    1 回复  |  直到 8 年前
        1
  •  5
  •   Nico Schertler    8 年前

    我建议换个稍微不同的设计。这个想法是基于装饰图案的。

    首先,我会 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 接口,类似于 . 在这种情况下,处理器对象必须是代表(仍然是正确类型的)。然后, 可以在加载后用它们的实际处理器替换它们。