Статьи

Шаблон труб и фильтров в .NET

Конвейер в программном контексте — это очень известный архитектурный стиль, в котором процесс состоит из последовательности шагов, которые необходимо выполнить для обработки данных, а вывод одного шага является вводом другого шага. Это также называется шаблоном проектирования труб и фильтров. Именование происходит из физического конвейера, поскольку этот архитектурный стиль очень похож на конвейер, в котором поток данных входит и уходит после обработки. Оригинальная идея конвейера в программном обеспечении реализована в Unix.

Этот шаблон используется во многих местах. Конвейер компиляции, ASP.NET HTTP Pipeline и рабочие процессы — вот три из многих примеров, которые я могу упомянуть.

Стиль труб и фильтров реализован на разных платформах с использованием различных технологий и технологий. Недавно я был в ситуации, чтобы реализовать этот шаблон и провел некоторое исследование, чтобы узнать больше о возможных вариантах реализации этого шаблона в .NET Framework.

Проводя исследование, я обнаружил много подходов, представленных членами сообщества, но наиболее зрелый метод — тот, который Орен Эйни описал в своем блоге, используя Generics. Существует также интересная методика описана с помощью Джереми Likness используя выход ключевого слова в C #.

В этой статье я собираюсь применить подход Орена и расширить его, чтобы написать простую реализацию классического примера KWIC в области разработки программного обеспечения. Мне понравился код Орен, потому что, как он сказал, он сравнительно проще, чем другие решения, представленные для этой проблемы в .NET Framework.

Обзор KWIC

KWIC расшифровывается как Key Word in Context и является классической проблемой в статьях Software Engineering, в которых вы пытаетесь создать индекс слов, сортируя и выравнивая каждое слово в куске текста. У Дэвида Парнаса есть известная статья о модульности , в которой в качестве примера используется KWIC.

Существует несколько базовых и расширенных реализаций KWIC на разных платформах, но основными шагами являются:

  • Чтение ввода
  • Сдвиг слов в каждой строке, чтобы получить новую перестановку
  • Сортировка результатов
  • Написание вывода

Интересно, что в этом случае выходные данные каждого шага являются входными данными следующего шага, что делает его хорошим кандидатом на паттерн Pipes and Filters.

 

 

Реализуйте паттерн «Трубы и фильтры» с помощью шаблонов

Техника Орен для реализации каналов и фильтров в .NET Framework основана на универсальном интерфейсе и универсальном классе. Общий интерфейс имитирует фильтр, а общий класс имитирует конвейер.

 

 

Интерфейс IOperation имеет единственный метод с именем Execute, который является реализацией логики фильтра. Каждый фильтр должен реализовывать этот интерфейс.

using System.Collections.Generic;namespace KwicPipesFilters{    public interface IOperation<T>    {        IEnumerable<T> Execute(IEnumerable<T> input);    }}

Использование универсального IEnumerable является хорошим выбором, поскольку он оставляет разработчикам достаточно места для подключения любого типа, который они хотят, и использования различных типов для своих фильтров.

Класс Pipeline имеет методы Execute и Register . Используя метод Register , вы добавляете различные фильтры в конвейер и, используя метод Execute , начинаете обрабатывать элемент во всех зарегистрированных фильтрах.

using System.Collections.Generic;namespace KwicPipesFilters{    public class Pipeline<T>    {        private readonly List<IOperation<T>> operations = new List<IOperation<T>>();        public Pipeline<T> Register(IOperation<T> operation)        {            operations.Add(operation);            return this;        }        public void Execute()        {            IEnumerable<T> current = new List<T>();            foreach (IOperation<T> operation in operations)            {                current = operation.Execute(current);            }            IEnumerator<T> enumerator = current.GetEnumerator();            while (enumerator.MoveNext());        }    }}

Реализация класса Pipeline проста: он хранит список фильтров и предоставляет функцию Register, которая позволяет добавлять новые фильтры в конвейер, а затем использовать метод Execute для выполнения всех фильтров в списке для обработки ввода.

читатель

Считыватель фильтр считывает входной текст из файла и возвращает IEnumerable список строк. Конечно, для первого фильтра в канале мы не заботимся о входных данных, поскольку входные данные считываются внутри самого фильтра.

using System;using System.Collections.Generic;using System.IO;namespace KwicPipesFilters{    public class Reader : IOperation<string>      {        public IEnumerable<string> Execute(IEnumerable<string> input)        {            Console.Title = "Pipes and Filters Pattern in .NET";            Console.WriteLine("Enter the path of the file:");            return File.ReadLines(Console.ReadLine());        }    }}

фазовращатель

Shifter фильтр , где реализуется основная логика приложения KWIC. Он сдвигает слова в каждой строке, чтобы найти все возможные перестановки, подходящие для индекса.

using System.Collections.Generic;namespace KwicPipesFilters{    public class Shifter : IOperation<string>      {        public IEnumerable<string> Execute(IEnumerable<string> input)        {            List<string> shifts = new List<string>();            foreach (string line in input)            {                string[] words = line.Split(new char[] { ' ' });                for (int i = 0; i <= words.Length - 1; i++)                {                    shifts.Add(string.Join(" ", words));                    string firstWord = words[0];                    for (int j = 1; j <= words.Length - 1; j++)                    {                        words.SetValue(words[j], j - 1);                    }                    words.SetValue(firstWord, words.Length - 1);                }            }            return shifts;        }    }}

Здесь мы имеем базовую реализацию фильтра Shifter, где мы разбиваем строку на отдельные слова на основе расстояния между ними, а затем сдвигаем все слова, чтобы найти различные перестановки.

сортировщик

Прежде чем возвращать окончательные результаты в фильтре Writer , нам нужно отсортировать индекс по алфавиту. Это делается в фильтре сортировщика .

using System.Collections.Generic;using System.Linq;namespace KwicPipesFilters{    public class Sorter : IOperation<string>    {        public IEnumerable<string> Execute(IEnumerable<string> input)        {            LineComparer lineComparer = new LineComparer();            input.ToList<string>().Sort(lineComparer);            return input;        }    }}

Здесь я использовал класс LineComparer для реализации интерфейса ICcomparer для строкового типа.

using System.Collections.Generic;namespace KwicPipesFilters{    public class LineComparer : IComparer<string>    {        public int Compare(string x, string y)        {            return string.Compare(x, y);        }    }}

писатель

Очевидно, что последний фильтр должен записать индекс в вывод для пользователя, и это цель фильтра Writer .

using System;using System.Collections.Generic;namespace KwicPipesFilters{    public class Writer : IOperation<string>    {        public IEnumerable<string> Execute(IEnumerable<string> input)        {            foreach (string line in input)            {                Console.WriteLine();                Console.WriteLine(line);            }            Console.ReadLine();            yield break;        }    }}

Как видите, этот фильтр использует разрыв доходности, чтобы избежать возврата какого-либо результата.

Трубопровод

После того, как весь фильтр реализован, мне также нужно реализовать сам конвейер, чтобы зарегистрировать фильтры и заставить все это работать. Я делаю это в своем классе KwicPipeline с помощью простого кода, который у него есть.

namespace KwicPipesFilters{    public class KwicPipeline : Pipeline<string>    {        public KwicPipeline()        {            Register(new Reader());            Register(new Shifter());            Register(new Sorter());            Register(new Writer());        }    }}

Я наследую от класса Pipeline и регистрирую свои фильтры в открытом конструкторе.

Положить его вместе

Остался только один шаг — собрать все это вместе, чтобы запустить конвейер. Все, что мне нужно сделать, это создать экземпляр класса KwicPipeline , вызвать его метод Execute и оставить остальное для моих каналов и фильтров.

namespace KwicPipesFilters{    class Program    {        static void Main(string[] args)        {            KwicPipeline pipeline = new KwicPipeline();            pipeline.Execute();        }    }}

Вывод

В этой статье я реализовал шаблон Pipes and Filters в .NET Framework, используя простую и обобщенную технику, которая использует Generics для реализации приложения KWIC. На мой взгляд, это один из лучших способов реализовать этот шаблон в .NET Framework. Я загрузил образец пакета исходного кода здесь . Обратите внимание, что решение создано с использованием Visual Studio 2010 RC1.

Существуют и другие методы для реализации этого шаблона в .NET, и один конкретный метод, который я имею в виду, — это использование Windows Workflow Foundation. Я могу больше работать над этой идеей и написать об этом позже.