【问题标题】:How to enforce method execution order in c#如何在 C# 中强制执行方法的顺序
【发布时间】:2021-08-13 08:38:39
【问题描述】:

长话短说,我有以下课程:

public class FlowBasePipeline<T>
{
    private List<StepBaseBusiness<T>> stepList = new List<StepBaseBusiness<T>>();
    
    public void Push(StepBaseBusiness<T> step)
    {
        stepList.Add(step);
    }
    
    public void Trigger(T result)
    {
        foreach (var step in stepList )
        {
            result = step.Execute(result);
            if (!result.IsSuccess)
            {
                break;
            }
        }
    }
}

我正在寻找的是强制程序员首先调用Push 方法,然后让他们访问Trigger 方法,在这种情况下,以下情况是不允许的

var pipeline=new FlowBasePipeline<MyStepResult>();
pipeline.Trigger()// Trigger method is not recognized

我们应该先调用Push方法

var pipeline=new FlowBasePipeline<MyStepResult>();
pipeline.Push(new MyStep()).Trigger()//Now Trigger is recognized

我做了什么:

我应用了如下显式接口方法实现以使其工作:

public interface IBasePipeline<T> where T:BaseResult,new()
{
    void Trigger();
    IBasePipeline<T> Push(StepBaseBusiness<T> step);
}


  public class FlowBasePipeline<T>:IBasePipeline<T> where T:BaseResult,new()
   {
          private List<StepBaseBusiness<T>> stepList = new List<StepBaseBusiness<T>>();
        
            public IBasePipeline<T> Push(StepBaseBusiness<T> step)
                {
                    stepList.Add(step);
                    return this;
                }
        
                void IBasePipeline<T>.Trigger(T result)
                {
                    foreach (var step in stepList )
                    {
                        result = step.Execute(result);
                        if (!result.IsSuccess)
                        {
                            break;
                        }
        
                    }
                }
    }

现在它运行良好,我们无法在 Push 方法之前访问 Trigger 方法,但从我的角度来看,这不是一个好方法,因为我们可能需要更多级别的订单,我不知道怎么做可以通过这种方式完成。

据我所知,方法链是函数式编程的关键规则之一。

是否有任何模式或策略来实现这种链接?

更新

我们需要多次调用push方法

var pipeline=new FlowBasePipeline<MyStepResult>();
pipeline.Push(new MyStep1()).Push(new MyStep2()).Trigger();

第一次推送后,推送和触发即可。

【问题讨论】:

  • Push 返回带有Trigger 方法的东西。
  • 作为旁注,您能重新格式化您的代码吗?我怀疑它在 Visual Studio 中看起来不像这样,并且在所有地方都像这样缩进,真的很难阅读。
  • 您可以尝试考虑构建器模式。调用 build() 方法将返回最终的对象,您可以使用它来调用 trigger
  • 真的它在 Visual Studio 中的样子吗?我会很惊讶。例如,在前两行中......您的类声明真的与stepList 的声明处于同一缩进级别吗?如果它在 Visual Studio 中的格式也很糟糕,我会让 VS 重新格式化它以开始......然后确保它在这里看起来不错。花时间让帖子中的代码看起来尽可能可读总是值得的...通读帖子并问自己如果您回答问题。
  • 如果您想了解更多有关此类设计问题的信息,请参阅“时间耦合”。

标签: c# design-patterns functional-programming


【解决方案1】:

一种方法是使用接口通过指定接口作为结果来限制对特定方法的访问。

public interface IStartCar
{
  IDriveCar Start(string key);
}

public interface IDriveCar
{
  IParkCar Drive(string address);
}

public interface IParkCar
{
  IStopCar Park();
}

public interface IStopCar
{
  IParkCar Drive(string address);
  void Stop();
}

public class Car : IStartCar, IDriveCar, IParkCar, IStopCar
{
  public IDriveCar Start(string key);
  public IParkCar Drive(string address);
  public IStopCar Park();
  public IStopCar Park();

  private Car() { }

  public static IStartCar Get()
  {
    var result = new Car();
    return result;
  }
}

现在要获取汽车,您使用 CarFactory 方法Get(),它返回汽车,但您实际上只能访问接口结果。这种模式只允许开发人员将特定的方法串在一起:

var car = Car.Get();
car.Start("key").Drive("address1").Park().Drive("address2").Park().Stop();

【讨论】:

  • 我喜欢这个解决方案,而且读起来很容易。值得注意的是,即使它对某人来说应该是一个巨大的危险信号,开发人员也可以通过反射或简单的投射来轻松打破这一点。我见过小辈们做一些疯狂的事情。如果您真的想阻止开发人员在代码中执行 X,我通常会在构建管道的某个地方强制执行它。例如,使用lock 的任何用法来防止代码访问 repo。
  • @Abolfazl 这只是一个设计。真的没有一个最好的设计,因为它总是取决于......
  • @Zer0 同意。与所有事情一样,它总是取决于什么是必要的。这是最容易实现的方法之一,但任何人都可以将对象投射到汽车上并做各种疯狂的事情。但是,我真的很想知道开发人员在这种情况下的想法,而不是询问其他开发人员。
  • 你是对的,但正如我所见,这是接口隔离的最佳用例!不是吗?
  • @Abolfazl 据我所知,那将是 no。 ISP 是一种将接口方法减少到所有实现类都将使用的方法的做法。添加仅由特定实现使用的方法会破坏 ISP。
【解决方案2】:

几个小时后,我想出了这个设计:

   public interface IBasePipelineRegister<T> where T:BaseResult
    {
       IStagePipeline<T> Push(StepBaseBusiness<T> step);
       List<StepBaseBusiness<T>> Steps { get; set; }
    }


   public interface IBasePipelineTrigger<T> where T:BaseResult
    {
        void Trigger(T result);
    }

   public interface IStagePipeline<T>: IBasePipelineTrigger<T>,IBasePipelineRegister<T> where T:BaseResult 
    {

    }

   public class FlowBasePipeline<TResult> : IBasePipelineRegister<TResult> where TResult : BaseResult,new()
    {
        public List<StepBaseBusiness<TResult>> Steps { get ; set ; }
        private IStagePipeline<TResult> _stagePipeline;
        public BasePipeline()
        {
            this.Steps = new List<StepBaseBusiness<TResult>>();
            this._stagePipeline = new StagePipeline<TResult>(this);
        }
        public IStagePipeline<TResult> Push(StepBaseBusiness<TResult> step)
        {
            Steps.Add(step);
            return _stagePipeline;

        }
    }

如您所见,BasePipeline 只是实现了IBasePipelineRegister,Register 方法提供了新的 StagePipeline 类,该类由当前类和触发器实现组成。

public class StagePipeline<T>: IStagePipeline<T> where T:BaseResult
{
    private readonly IBasePipelineRegister<T> pipelineRegister;
    public List<StepBaseBusiness<T>> Steps { get; set; }

    public StagePipeline(IBasePipelineRegister<T> pipelineRegister)
    {           
        this.pipelineRegister = pipelineRegister;
        Steps = pipelineRegister.Steps;
    }


    public IStagePipeline<T> Push(StepBaseBusiness<T> step)
    {
       return pipelineRegister.Push(step);
    }

    public void Trigger(T result)
    {
        foreach (var step in Steps)
        {
            result = step.Execute(result);
            if (!result.IsSuccess)
            {
                break;
            }

        }
    }


}

现在每个方法都添加一个新功能而不是替换新功能。

var pipeline=new FlowBasePipeline<MyStepResult>();
pipeline.Push(new MyStep1()).Push(new MyStep2()).Trigger();

【讨论】:

  • 你好像在使用装饰器模式,新方法中添加了新功能?
  • 也许吧,但我尽量尊重函数式编程。
【解决方案3】:

我用于 api 的方法示例,它固有地以流畅的语法“引导”调用者:

public class Pipeline
{
    readonly List<Action> _steps = new List<Action>();
    
    // only Push is available when Pipeline is initialized
    public PipelineWithSteps Push(Action step)
    {
        _steps.Add(step);
        // or cache this if you want 'Push' repeatable
        return new PipelineWithSteps(this);
    }

    public class PipelineWithSteps
    {
        // not required but often the chained context wants/needs access to the first context
        readonly Pipeline _context;
        
        // api is public but ctor cannot be invoked by external caller
        internal PipelineWithSteps(Pipeline context) => _context = context;
        
        // now Trigger is available only after something was pushed
        public PipelineWithSteps Trigger()
        {
            foreach(var step in _context._steps)
                step();
            Console.WriteLine();
            return this;
        }
        
        // usually I don't repeat an initialization method;
        // this could be done using the 'context' 
        // but would have to be refactored to return the existing 'PipelineWithSteps'
        public PipelineWithSteps Push(Action step)
        {
            _context._steps.Add(step);
            return this;
        }
    }
}

用法:

    var pipeline = new Pipeline();
    pipeline.Push(() => Console.WriteLine("A"))
            .Push(() => Console.WriteLine("B"))
            .Trigger()
            .Push(() => Console.WriteLine("C"))
            .Trigger();

输出:

A
B

A
B
C

【讨论】:

  • 干得好。但在你的设计中使用合同会很好。
  • @Abolfazl 绝对。只是为了清晰起见保持简单
最近更新 更多