Общие принципы дизайна в объектно

advertisement
Общие принципы дизайна в объектно-ориентированном
проектировании
Задание: Обобщенное программирование
Для заданной прикладной области реализовать обобщенный алгоритм
для преобразования набора данных одного типа в набор данных другого типа.
Описать полученные обобщенные методы с помощью встроенной документации XML. Поместить методы в библиотеку для повторного использования.
Написать приложение, демонстрирующее заданное преобразование.
Варианты прикладной области:
1 OrderBy – получает последовательность, отсортированную по некоторому столбцу; Where – фильтрует последовательность по некоторому предикату
2 Any – определяет существует ли хотя бы один элемент последовательности, удовлетворяющий условию; Concat – присоединяет две последовательности с одинаковыми типами элементов (f + s)
3 Contains – определяет, что последовательность содержит заданный элемент; Union – получает объединение двух последовательностей (f | s)
4 Intersect – получает пересечение двух последовательностей
(f & s); Last – получает последний элемент последовательности, который
соответствует заданному условию
5 Min – получает элемент последовательности, который является минимальным по заданному условию; Reverse – выполняет обращение элементов последовательности
6 Distinct – возвращает уникальные элементы последовательности (с использованием Хеш-таблицы); ElementAt – получает элемент по индексу
7 Max – получает элемент последовательности, который является максимальным по заданному условию; Repeat – формирует последовательность дублированием элемента заданное количество раз
8 Except – получает разницу двух последовательностей (f \ s); First – получает первый элемент последовательности который соответствует заданному условию
9 Single - получает элемент последовательности, который соответствует
заданному условию, если таких элементов несколько, то генерирует исключение; SequenceEqual – проверяет, что две последовательности одинаковые
10 Cast – осуществляет приведение элементов последовательности к заданному типу; All – проверяет, что все элементы последовательности удовлетворяют заданному условию
11 SkipWhile – пропускает элементы последовательности, пока условие воз-
вращает true и возвращает остальные элементы; Take – получает заданное количество первых элементов последовательности
12 TakeWhile – получает элементы последовательности, пока условие возвращает true и пропускает остальные элементы; ToArray – получает массив элементов последовательности
Минимальные требования к лабораторной работе
 Методы должны быть полностью задокументированы.
 Библиотека должна быть подписана цифровой подписью издателя.
 Библиотека должна соответствовать общеязыковой спецификации.
 Окончательная версия библиотеки не должна содержать: ошибки и
предупреждения компилятора C# по качеству кода; предупреждения компилятора C# по документированию; предупреждения статического анализатора
кода.
 Сгенерировать документацию, используя один из генераторов документации NDoc или Sandcastle.
Последовательность шагов при выполнении лабораторной работы
Создать два проекта в одном решении. Проект, который будет содержать
обобщенные алгоритмы должен быть Class Library.
В проекте демонстрационного приложения добавить ссылку на проект
общей библиотеки.
Общая библиотека должна содержать разрабатываемые методы и классы.
Включить генерацию документации для общей библиотеки.
Реализовать методы прикладной области, включая документацию.
Подписать общую сборку ключом, полученным во время выполнения
второй лабораторной работы.
Включить статический анализатор кода и исправить ошибки.
Сгенерировать документацию, используя один из генераторов документации NDoc или Sandcastle.
Реализовать демонстрационное приложение демонстрации заданного
преобразования.
Информация к выполнению лабораторной работы
Обобщение (generic) – один из механизмов повторного использования
кода, а именно повторным использованием алгоритма. Разработчик определяет алгоритм, например сортировку, поиск, замену, сравнение или преобразование, но не указывает конкретные типы данных, с которыми тот работает.
Поэтому алгоритм может обобщенно применяться к объектам разных типов.
Используя готовый алгоритм, другой разработчик применяет его к конкретным типам данных. Обобщение поддерживается не только на уровне компиляции, но и на уровне подсказок IntelliSense в Visual Studio. В курсе ООП
был объявлен класс стек с параметром-типом T.
public class Stack<T>
{
private const int length = 100;
private T[] array = new T[length];
private int size = 0;
public void Push(T item)
{
//...
}
public T Pop()
{
//...
}
}
Поскольку параметр типа применен к классу Stack, то этот класс является обобщенным классом, а параметр типа может использоваться в пределах
класса как символическая ссылка на тип. При использовании такого алгоритма указываются конкретные типы данных. Указанный тип данных называют аргументом-типом. Например, можно создать стек целых чисел или
стек для работы со строками:
Приведенные фрагменты показывают, как происходит подстановка типа
в методе на примере метода Push(…).
Параметры-типы могут применяться по отношению к классам, структурам интерфейсам, делегатам и методам.
Рассмотрим пример задания.
Реализовать следующие методы обработки последовательности:
 Получение проекции каждого элемента входной последовательности в
новую форму (выбор заданных столбцов из входной таблицы – Select).
 Получение среднего значения некоторого столбца таблицы (Average).
 Получение суммы элементов некоторого столбца таблицы (Sum).
 Получение Хеш-таблицы с ключами некоторого столбца для определения значений таблицы по заданному ключу (ToDictionary).
Действия этих методов рассмотрим на примере следующей таблицы:
Company
Coho Vineyard
Lucerne Publishing
Wingtip Toys
Adventure Works
Weight
25.2
18.7
6.0
33.8
Tracking Number
89453312
89112755
299456122
4665518773
Метод Select должен формировать из входной последовательности выходную последовательность с заданными столбцами. Например, если требуется выделить столбцы Company и Weight, то результат должен быть следующим:
Company
Coho Vineyard
Lucerne Publishing
Wingtip Toys
Adventure Works
Weight
25.2
18.7
6.0
33.8
Очевидно, должна существовать функция преобразования каждой строки входной последовательности в строку выходной последовательности.
Методам Average и Sum для нахождения среднего значения и суммы
требуется задать способ выделения данных из таблицы. Например, в столбце
Company определить среднюю длину имен компаний. В этом случае тоже
можно использовать функцию для получения длины строки названия компании. Аналогичную функцию можно использовать для задания ключа для
Хеш-таблицы.
Таким образом, обобщив условие задачи, можно прийти к выводу, что
каждый из методов мог бы принимать функцию, соответствующую делегату:
public delegate TResult Func<T, TResult>(T arg);
Здесь T и TResult являются параметрами-типами. Т – тип аргумента
функции. TResult – тип результата функции.
Все интересующие нас методы можно разместить в статическом классе
Processing.
Метод Select должен для каждого элемента входной последовательности
получать элементы выходной последовательности. Следовательно, такой метод должен работать обобщенно с двумя параметрами-типами (например,
TSource и TResult). Тогда метод Select может быть объявлен следующим образом:
public static IEnumerable<TResult> Select<TSource,
TResult>(IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
if (source == null) throw new ArgumentNullException("source",
"Значение параметра source - пустая ссылка.");
if (selector == null) throw new ArgumentNullException("selector",
"Значение параметра selector - пустая ссылка.");
foreach (TSource item in source)
{
yield return selector(item);
}
}
Метод сначала проверяет входные аргументы, затем для каждого элемента входной последовательности получает элемент выходной последовательности с помощью переданной функции selector. Полученный элемент
направляется в выходную последовательность.
Работу такого обобщенного метода рассмотрим на примере. Пусть объявлены два класса Package и ExtendPackage:
class Package
{
public string Company;
public double Weight;
public Package(string Company, double Weight)
{
this.Company = Company;
this.Weight = Weight;
}
}
class ExtendPackage : Package
{
public long TrackingNumber;
public ExtendPackage(string Company, double Weight, long TrackingNumber)
: base(Company, Weight)
{
this.TrackingNumber = TrackingNumber;
}
}
Требуется выделить из таблицы {Company, Weight, TrackingNumber}
столбцы {Company, Weight}.
Для преобразования элементов входной последовательности в элементы
выходной последовательности будет использована функция Translator:
private static Package Translator(ExtendPackage item)
{
return new Package(item.Company, item.Weight);
}
В качестве входной последовательности может выступать динамический
массив List<T>.
List<ExtendPackage> extendPackages = new List<ExtendPackage>();
extendPackages.Add(new ExtendPackage("Coho Vineyard", 25.2, 89453312L));
extendPackages.Add(new ExtendPackage("Lucerne Publishing", 18.7, 89112755L));
extendPackages.Add(new ExtendPackage("Wingtip Toys", 6.0, 299456122L));
extendPackages.Add(new ExtendPackage("Adventure Works", 33.8, 4665518773L));
//Выделение элементов Package из ExtendPackage с использованием метода
выделения Translator:
IEnumerable<Package> packages1 =
Processing.Select<ExtendPackage, Package>(extendPackages,
Translator);
В C# 2.0 появилась возможность объявить метод в теле другого метода с использованием
ключевого слова delegate. В следующем примере функция преобразования объявляется
при вызове метода Select:
IEnumerable<Package> packages2 =
Processing.Select<ExtendPackage,
Package>(extendPackages,
delegate(ExtendPackage item) { return new Package(item.Company,
item.Weight); });
Нововведения языка C# 3.0 позволяют существенно упростить вызов метода Select, если дополнительно добавить к объявлению метода атрибут
[Extension]:
var packages5 = extendPackages.Select(item => new { item.Company, item.Weight
});
Оценить преимущество спецификации C# 3.0 по отношению к C# 2.0
можно на следующем примере. Пусть требуется вычислить квадраты элементов некоторого динамического массива.
С использованием языка C# 2.0 эту задачу можно решить следующим
образом:
List<int> numbers1 = new List<int>(new int[] { 0, 1, 2, 3, 4, 5, 6, 7 });
IEnumerable<int> square1 =
Processing.Select<int, int>(numbers1, delegate(int x) { return x * x; });
Processing.Print(square1);
Здесь массив numbers1 содержит некоторую последовательность целых
чисел. Затем вызывается метод Select с аргументами последовательности и
анонимным методом, вычисляющим квадрат целого числа. Полученный объект является перечислимым. Он печатается на экран.
Спецификация языка C# 3.0 позволяет писать наглядный и более готовый к изменениям код:
var numbers2 = new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7 };
var square2 = numbers2.Select(x => x * x);
Processing.Print(square2);
Если придется изменить тип элемента массива с int на double, то в первом случае придется вносить 7 изменений, а во втором случае только 1.
Остальные методы не представляют трудностей.
Рис. 5
Download