C\#파이프 프로 그래 밍 깊이 이해

머리말
C\#프로 그래 밍 에서 파이프 프로 그래 밍(Pipeline Style programming)은 사실 존재 한 지 오래 되 었 고 가장 뚜렷 한 것 은 우리 가 자주 사용 하 는 LINQ 이다.DotNetCore 세계 에 들 어간 후에 이런 프로 그래 밍 방식 은 더욱 뚜렷 해 졌 다.예 를 들 어 각종 미들웨어 의 사용 이다.이런 프로 그래 밍 방식 을 사용 함으로써 코드 의 유지 가능성 을 크게 향상 시 키 고 업무 의 조합 방식 을 최적화 시 켰 다.
파이프 프로 그래 밍 은 다음 과 같은 장점 을 가진다.
  • 유창 한 프로 그래 밍 범례 를 만 들 고 문 구 를 표현 식 으로 바 꾸 어 연결 합 니 다
  • 코드 를 선형 정렬 로 교체 합 니 다
  • 변수 성명 제거-var
  • 필요 없 음
  • 특정한 형식의 가 변 불변성 과 범위 격 리 를 제공한다
  • 구조 코드 를 명확 한 직책 을 가 진 작은 lambda 표현 식 으로 작성 합 니 다
  • ......
  • 기초 실현
    이 예시 에서,우 리 는 double->int->string 의 형식 변환 파 이 프 를 구축 하여 목표 데 이 터 를 최종 적 으로 문자열 로 변환 합 니 다.
  • 우선,우 리 는 하나의 기능 인 터 페 이 스 를 정의 하여 모든 기능 함수 의 구체 적 인 실현 을 제약 해 야 한다.예시 코드 는 다음 과 같다.
  • 
    public interface IPipelineStep<INPUT, OUTPUT>
    {
        OUTPUT Process(INPUT input);
    }
  • 그 다음 에 우 리 는 두 가지 유형의 전환 기능 류 를 정의 하고 상기 인 터 페 이 스 를 계승 하고 실현 한다.예시 코드 는 다음 과 같다.
  • 
    public class DoubleToIntStep : IPipelineStep<double, int>
    {
        public int Process(double input)
        {
            return Convert.ToInt32(input);
        }
    }
    public class IntToStringStep : IPipelineStep<int, string>
    {
        public string Process(int input)
        {
            return input.ToString();
        }
    }
  • 이 어 확장 함 수 를 정의 하여 상기 각 기능 함 수 를 연결 하 는 데 사용 합 니 다.예제 코드 는 다음 과 같 습 니 다.
  • 
    public static class PipelineStepExtensions
    {
        public static OUTPUT Step<INPUT, OUTPUT>(this INPUT input, IPipelineStep<INPUT, OUTPUT> step)
        {
            return step.Process(input);
        }
    }
  • 마지막 으로 우 리 는 완전한 파 이 프 를 구축 하여 우리 의 데이터 형식 전환 에 사용 할 수 있다.예제 코드 는 다음 과 같다.
  • 
    class Program
    {
        static void Main(string[] args)
        {
            double input = 1024.1024;
            //        
            string result = input.Step(new DoubleToIntStep())
                                 .Step(new IntToStringStep());
            Console.WriteLine(result);
        }
    }
    이때,우 리 는 double 형식의 데 이 터 를 string 형식 으로 바 꾸 는 데 성공 했다.상기 예 시 를 소개 함으로써 우 리 는 파이프 식 프로 그래 밍 을 간단하게 정의 기능 인터페이스->실현 기능 함수->조립 기능 함수 로 요약 할 수 있다.
    의존 주입
    상기 코드 는 일반적인 상황 에서 정상적으로 작 동 할 수 있 지만 주입(DI)에 의존 하 는 방식 으로 주입 하 기 를 원한 다 면 우 리 는 우리 의 파 이 프 를 조립 하여 포장 하여 하나의 통 일 된 서비스 로 시스템 에 주입 하 는 데 편리 하도록 해 야 한다.
  • 먼저,우 리 는 파이프 조립 에 사용 되 는 추상 적 인 패 키 징 을 정의 해 야 한다.예제 코드 는 다음 과 같다.
  • 
    public abstract class Pipeline<INPUT,OUTPUT>
    {
        public Func<INPUT, OUTPUT> PipelineSteps { get; protected set; }
        public OUTPUT Process(INPUT input)
        {
            return PipelineSteps(input);
        }
    }
  • 그 다음 에 우 리 는 상기 추상 류 를 계승 하 는 구체 적 인 파이프 조립 류 를 만 들 수 있다.예시 코드 는 다음 과 같다.
  • 
    public class TrivalPipeline : Pipeline<double, string>
    {
        public TrivalPipeline()
        {
            PipelineSteps = input => input.Step(new DoubleToIntSetp())
                                          .Step(new IntToStringStep());
        }
    }
    마지막 으로 우 리 는 TrivalPipeline 이 구체 적 인 파 이 프 를 우리 시스템 에 주입 할 수 있다.마찬가지 로 우 리 는 직접 사용 할 수 있 습 니 다.예제 코드 는 다음 과 같 습 니 다.
    
    class Program
    {
        static void Main(string[] args)
        {
            double input = 1024.1024;
            //      Microsoft.Extensions.DependencyInjection
            var services = new ServiceCollection();
            services.AddTransient<TrivalPipeline>();
            var  provider = services.BuildServiceProvider();
            var trival = provider.GetService<TrivalPipeline>();
            string result = trival.Process(input);
            Console.WriteLine(result);
        }
    }
    조건식 조립
    상기 두 예제 코드 가 보 여 준 파이프 조립 식 은 어떠한 조건 도 제한 하지 않 습 니 다.매개 변수 가 합 법 적 이 든 그렇지 않 든 모두 이렇게 파 이 프 를 조립 합 니 다.그러나 실제 개발 과정 에서 우 리 는 일정한 업무 모듈 에 대해 조건 부 조립 을 해 야 하기 때문에 이 럴 때 우 리 는 우리 의 코드 를 보완 해 야 합 니 다.
    우선,우 리 는 위의Pipeline<INPUT,OUTPUT>종 류 를 수정 하여 계승IPipelineStep<INPUT, OUTPUT> 인 터 페 이 스 를 수정 해 야 한다.예시 코드 는 다음 과 같다.
    
    public abstract class Pipeline<INPUT, OUTPUT> : IPipelineStep<INPUT, OUTPUT>
    {
        public Func<INPUT, OUTPUT> PipelineSteps { get; protected set; }
        public OUTPUT Process(INPUT input)
        {
            return PipelineSteps(input);
        }
    }
  • 그 다음 에 우 리 는 조건 이 있 는 파이프 장식 기 류 를 정의 합 니 다.예제 코드 는 다음 과 같 습 니 다.
  • 
    public class OptionalStep<INPUT, OUTPUT> : IPipelineStep<INPUT, OUTPUT> where INPUT : OUTPUT
    {
        private readonly IPipelineStep<INPUT, OUTPUT> _step;
        private readonly Func<INPUT, bool> _choice;
        public OptionalStep(Func<INPUT,bool> choice,IPipelineStep<INPUT,OUTPUT> step)
        {
            _choice = choice;
            _step = step;
        }
        public OUTPUT Process(INPUT input)
        {
            return _choice(input) ? _step.Process(input) : input;
        }
    }
  • 이어서 우 리 는 새로운 기능 류 와 조건 판단 을 지원 하 는 파이프 포장 류 를 정의 했다.예시 코드 는 다음 과 같다.
  • 
    public class ThisStepIsOptional : IPipelineStep<double, double>
    {
        public double Process(double input)
        {
            return input * 10;
        }
    }
    public class PipelineWithOptionalStep : Pipeline<double, double>
    {
        public PipelineWithOptionalStep()
        {
            //         1024,   ThisStepIsOptional()   
            PipelineSteps = input => input.Step(new OptionalStep<double, double>(i => i > 1024, new ThisStepIsOptional()));
        }
    }
  • 마지막 으로 우 리 는 다음 과 같은 방식 으로 테스트 할 수 있다.
  • 
    class Program
    {
        static void Main(string[] args)
        {
            PipelineWithOptionalStep step = new PipelineWithOptionalStep();
            Console.WriteLine(step.Process(1024.1024));  //    10241.024
            Console.WriteLine(step.Process(520.520));    //    520.520
        }
    }
    사건 감청
    때때로 우 리 는 우리 파이프 에서 실행 되 는 모든 단계 가 시작 과 끝 날 때 상층 모듈 은 해당 하 는 사건 통 지 를 받 을 수 있 기 를 희망 한다.이때 우 리 는 우리 의 파이프 포장 기 를 바 꾸 어 이 수 요 를 지원 하도록 해 야 한다.
    우선,우 리 는 이벤트 감청 을 지원 하 는 구체 적 인 기능 류 를 실현 해 야 한다.예시 코드 코드 는 다음 과 같다.
    
    public class EventStep<INPUT, OUTPUT> : IPipelineStep<INPUT, OUTPUT>
    {
        public event Action<INPUT> OnInput;
        public event Action<OUTPUT> OnOutput;
        private readonly IPipelineStep<INPUT, OUTPUT> _innerStep;
        public EventStep(IPipelineStep<INPUT,OUTPUT> innerStep)
        {
            _innerStep = innerStep;
        }
        public OUTPUT Process(INPUT input)
        {
            OnInput?.Invoke(input);
            var output = _innerStep.Process(input);
            OnOutput?.Invoke(output);
            return output;
        }
    }
  • 그 다음 에 우 리 는 이벤트 파 라 메 터 를 전달 할 수 있 는 파이프 포장 기 류 를 정의 해 야 한다.예제 코드 는 다음 과 같다.
  • 
    public static class PipelineStepEventExtensions
    {
        public static OUTPUT Step<INPUT, OUTPUT>(this INPUT input, IPipelineStep<INPUT, OUTPUT> step, Action<INPUT> inputEvent = null, Action<OUTPUT> outputEvent = null)
        {
            if (inputEvent != null || outputEvent != null)
            {
                var eventDecorator = new EventStep<INPUT, OUTPUT>(step);
                eventDecorator.OnInput += inputEvent;
                eventDecorator.OnOutput += outputEvent;
                return eventDecorator.Process(input);
            }
            return step.Process(input);
        }
    }
  • 마지막 으로 상부 호출 이 상대 적 으로 간단 하 다.예시 코드 는 다음 과 같다.
  • 
    public class DoubleStep : IPipelineStep<int, int>
    {
        public int Process(int input)
        {
            return input * input;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var input = 10;
            Console.WriteLine($"Input Value:{input}[{input.GetType()}]");
            var pipeline = new EventStep<int, int>(new DoubleStep());
            pipeline.OnInput += i => Console.WriteLine($"Input Value:{i}");
            pipeline.OnOutput += o => Console.WriteLine($"Output Value:{o}");
            var output = pipeline.Process(input);
            Console.WriteLine($"Output Value: {output} [{output.GetType()}]");
            Console.WriteLine("\r
    "); // : Console.WriteLine(10.Step(new DoubleStep(), i => { Console.WriteLine($"Input Value:{i}"); }, o => { Console.WriteLine($"Output Value:{o}"); })); } }
    출력 결 과 는 다음 그림 과 같 습 니 다.

    반복 실행 가능
    교체 실행 이란 우리 의 파이프 에 여러 기능 모듈 이 등록 되 어 있 을 때 한꺼번에 실행 되 기 때문에 기능 모듈 이 아니 라 매번 하나의 기능 만 수행 하 는 것 을 말한다.후속 기능 은 다음 에 이 파이프 에 대응 하 는 코드 블록 을 실행 할 때 이 파이프 에 있 는 기능 모듈 이 실 행 될 때 까지 계속 실행 된다.이 특성 은 주로 yield return 을 통 해 이 루어 집 니 다.
    우선,우 리 는 이 특성의 파이프 포장 기 류 를 실현 해 야 한다.예시 코드 는 다음 과 같다.
    
    public class LoopStep<INPUT, OUTPUT> : IPipelineStep<IEnumerable<INPUT>, IEnumerable<OUTPUT>>
    {
        private readonly IPipelineStep<INPUT, OUTPUT> _internalStep;
        public LoopStep(IPipelineStep<INPUT,OUTPUT> internalStep)
        {
            _internalStep = internalStep;
        }
        public IEnumerable<OUTPUT> Process(IEnumerable<INPUT> input)
        {
            foreach (INPUT item in input)
            {
                yield return _internalStep.Process(item);
            }
            //        
            //return from INPUT item in input
            //       select _internalStep.Process(item);
        }
    }
  • 그 다음 에 상기 유형의 기능 조립 을 지원 하 는 확장 방법 을 정의 합 니 다.예제 코드 는 다음 과 같 습 니 다.
  • 
    public static class PipelineStepLoopExtensions
    {
        public static IEnumerable<OUTPUT> Step<INPUT, OUTPUT>(this IEnumerable<INPUT> input, IPipelineStep<INPUT, OUTPUT> step)
        {
            LoopStep<INPUT, OUTPUT> loopDecorator = new LoopStep<INPUT, OUTPUT>(step);
            return loopDecorator.Process(input);
        }
    }
  • 마지막 으로 상부 호출 은 다음 과 같다.
  • 
    class Program
    {
        static void Main(string[] args)
        {
            var list = Enumerable.Range(0, 10);
            foreach (var item in list.Step(new DoubleStep()))
            {
                Console.WriteLine(item);
            }
        }
    }
    총결산
    이 글 은 여기까지 입 니 다.당신 에 게 도움 이 되 기 를 바 랍 니 다.또한 당신 이 우리 의 더 많은 내용 에 관심 을 가 질 수 있 기 를 바 랍 니 다!

    좋은 웹페이지 즐겨찾기