niedziela, 10 marca 2013

Dynamic pipeline

Wcześniej pisałem o moim wzorcu pipeline. Wzorzec ten ma jeden duży minus. Może być zastosowany dla jednego typu obiektu. Jeżeli obiekt typu T jest daną wejściową to daną wyjściową też musi być typu T. Jeden duży pipeline może składać się z kilku pipeline-ów, w których ta zasada musi być spełniona.




A co jeżeli chcielibyśmy stworzyć pipeline z innym typem wejścia i wyjścia? Wtedy będziesz musiał zastosować type dynamic. Spójrz na klasę podstawową reprezentującą dany krok, która została przedstawiona w poprzednim rozwiązaniu:
public abstract class BaseStep<T>
    {
        protected BaseStep<T> _nextStep;
        public void SetNextStep(BaseStep<T> nextStep)
        {
            _nextStep = nextStep;
        }
        public T Process(T input)
        {
            var items = Handle(input);
            return _nextStep != null ? _nextStep.Process(items) : items;
        }
 
        protected  abstract T Handle(T input);
    }

Zmieńmy w tej klasie type dla następnego kroku, wprowadźmy dodatkowy parametr generyczny, aby odróżnić typ wejściowy od wyjściowego.
Cała klasa bazowa musi dziedziczyć po DynamicObject.
public abstract class ProceseBase<T, TReturn> : DynamicObject
    {
        protected dynamic _nextStep;
        public void SetNextStep(dynamic nextStep)
        {
            _nextStep = nextStep;
        }
        public dynamic Process(T input)
        {
            var items = Handle(input);
            return _nextStep != null ? _nextStep.Process(items) : items;
        }

        protected abstract TReturn Handle(T input);
    }

Taka zmiana pozwoli nam na dowolną modyfikację zwracanych typów obiektu z pipeline.



Przykład użycia wygląda następująco. Dajmy na to, że z wygenerowanych licz, chcemy sprawdzić ile jest licz dla danej pierwszej cyfrą. Dla liczb { 1,11,13,29,32,300 } mamy dla cyfry 1 - 3 liczby, dla 2 - 1 liczba, dla 3 - 2 liczby.
Na samym początku stwórzmy klasę, która będzie reprezentowała pierwszy krok - konwersja na string.
 public class ConvertToStringProcess<T> 
 : ProceseBase<IEnumerable<T>, IEnumerable<string>>
    {
        protected override IEnumerable<string> Handle(IEnumerable<T> input)
        {
            return input.Select(x => x.ToString());
        }
    }  

Następnie krok grupujący oraz krok zliczający liczbę elementów. Każdy z tych kroków ma inny typ wejściowy i wyjściowy.
 public class GroupByFirstCharProcess 
: ProceseBase<IEnumerable<string>, IEnumerable<IGrouping<char,string>>>
    {
        protected override IEnumerable<IGrouping<char, string>> 
Handle(IEnumerable<string> input)
        {
            return input.GroupBy(x => x.First());
        }
    } 

 public class CountItemsDictionary 
: ProceseBase<IEnumerable<IGrouping<char, string>>, Dictionary<char, int>>
    {
        protected override Dictionary<char, int> 
Handle(IEnumerable<IGrouping<char, string>> input)
        {
            return input.ToDictionary(x => x.Key, x => x.Count());
        }
    } 

A na końcu wywołanie dynamic pipelinu. Każde rozwiązanie ma jakieś plusy i minusy. W tym rozwiązaniu minusem jest używanie dynamikc :)
(tutaj, tutaj i tutaj)
 var numbers= HelpClass.GenerateData<int>(x => x * x).Take(300);

 dynamic itemsStrings= new ConvertToStringProcess<int>();
 GroupByFirstCharProcess groupedByFirstChar = new GroupByFirstCharProcess();
 CountItemsDictionary countedItems = new CountItemsDictionary();

 itemsStrings.SetNextStep(groupedByFirstChar);
 groupedByFirstChar.SetNextStep(countedItems);

 var countedNumbers= itemsStrings.Process(numbers);



Brak komentarzy:

Prześlij komentarz