Конвейер в программном контексте — это очень известный архитектурный стиль, в котором процесс состоит из последовательности шагов, которые необходимо выполнить для обработки данных, а вывод одного шага является вводом другого шага. Это также называется шаблоном проектирования труб и фильтров. Именование происходит из физического конвейера, поскольку этот архитектурный стиль очень похож на конвейер, в котором поток данных входит и уходит после обработки. Оригинальная идея конвейера в программном обеспечении реализована в 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. Я могу больше работать над этой идеей и написать об этом позже.