niedziela, 3 marca 2013

Mój wzorzec pipeline

Pipeline to łańcuch połączonych kroków,które procesują informację. Każdy krok dostaje obiekt i wykonuje jakąś akcje na tym obiekcie zanim zostanie wysłany do następnego kroku. Pipeline łączy jednostki procesowania, które mogą być klasami, metodami lub komponentami. Object pipeline składa się z 3 grup: klientów, klasy bazowej oraz z klas przedstawiający dany kroku. Klient tworzy obiekt, który ma być procesowany i wie jaki jest pierwszy krok. Klasa bazowa jest abstrakcyjna, posiada metodą, która zarządza wywołaniem. Pobiera obiekt, przetwarza go a następnie przesyła do następnego kroku. Klasa bazowa posiada też obiekt typu klasy bazowej reprezentujący następny kroków, do której tylko klasy dziedziczące mają dostęp. Klasa z krokiem dziedziczy po klasie bazowej. Schemat jest przedstawiony poniżej:



Na samym początku stwórzmy pomocnicze metody:
public class HelpClass
    {
        public static IEnumerable<T> GenerateData<T>(Func<int, T> func)
        {
            for (int i = 0; ; i++)
            {
                yield return func(i);
            }
        }

        public static DescriptionAttribute GetClassDescriptionAttribute(Type type)
        {
            return type
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.FirstOrDefault() as DescriptionAttribute;
        }
    }
Następnie stwórzmy abstrakcyjną klasę bazową, po której klasy z implementacją kroku będą dziedziczyć.Klasa ta będzie miała abstrakcyjną metodę - Handle(T input).
      public abstract class BaseStep<T>
    {
        protected BaseStep<T> _nextStep;
        public void SetNextStep(BaseStep<T> nextStep)
        {
            _nextStep = nextStep;
        }
        public T HandleProcess(T input)
        {
            var desc = HelpClass
.GetClassDescriptionAttribute(this.GetType());

            if(desc != null)
                Console.WriteLine(desc.Description);
 
            var items = Handle(input);
            return _nextStep != null ? _nextStep.HandleProcess(items) : items;
        }
 
        protected  abstract T Handle(T input);
    }
Teraz możemy napisać poszczególne implementacje kroków. Niech pierwszy krok będzie usuwał pierwszy element w liście, a drugi krok będzie usuwał duplikaty. Skorzystamy z atrybutu Description, aby mieć opis w którym kroku jesteśmy.
      [Description("Filters first element")]
    public class FilterFirstStep<T> : BaseStep<IEnumerable<T>>
    {
        protected override IEnumerable<T> Handle(IEnumerable<T> input)
        {
              return input.Skip(1);
        }
    }
 
    [Description("Filters duplicated elements")]
    public class FilterDuplicatedStep<T> : BaseStep<IEnumerable<T>>
    {
        protected override IEnumerable<T> Handle(IEnumerable<T> input)
        {
            return input.Distinct();
        }
    }
Dodatkowo stwórzmy krok, który będzie zawierał informacje jak długo trwało przetwarzanie listy.
    [Description("Measuring step")]
    public class MeasureStep<T> : BaseStep<IEnumerable<T>>
    {
        public long Elapsed { get; private set; }
        protected override IEnumerable<T> Handle(IEnumerable<T> input)
        {
            Stopwatch stopwatch = Stopwatch.StartNew();
            var result = input.ToList();
            Elapsed = stopwatch.ElapsedMilliseconds;
            return result;
        }
    }
Teraz jak mamy wszystkie kroki to możemy zająć się testowaniem pipelineu.
            Random random = new Random();
            var items = HelpClass.GenerateData(i => random.Next(i)).Take(90000);
            MeasureStep<int> measure = new MeasureStep<int>();
            FilterFirstStep<int> filterFirst= new FilterFirstStep<int>();

            measure.SetNextStep(filterFirst);
            filterFirst.SetNextStep(new FilterDuplicatedStep<int>());


            var hanled = measure
                .HandleProcess(items)
                .ToList();

            Console.WriteLine(measure.Elapsed);
I na konsoli mamy:
Wzorzec object pipeline ma jeden duży minus - dane wejściowe musza być tego samego typu co dane wyjściowe.


Brak komentarzy:

Prześlij komentarz