Здравствуйте! Записался на ваш курс, но не понимаю как произвести оплату. Надо ли писать заявление и, если да, то куда отправлять? как я получу диплом о профессиональной переподготовке? |
Универсальность. Классы с родовыми параметрами
Стек. От абстрактного, универсального класса к конкретным версиям
Возьмем классическую задачу определения стека. Следуя схеме, определим абстрактный универсальный класс, описывающий всевозможные представления стеков:
abstract public class GenStack<T> { abstract public T item(); abstract public void remove(); abstract public void put(T t); abstract public bool empty(); }
В таком виде абстрактный класс задает сигнатуру методов класса, что в совокупности представляет сигнатуру класса. На этом этапе проектирования класса крайне важно задать не только сигнатуру класса, но и спецификацию методов. Язык C# позволяет сделать это с помощью специальных документирующих комментариев, используя которые, специальный инструментарий позволяет построить документацию проекта. Подробнее об этих комментариях поговорим в следующей лекции, а сейчас для задания спецификаций будем применять неоднократно появляющиеся в наших примерах теги summary, играющие три важные роли. С одной стороны - это комментарии, важные для разработчика проекта. На этапе сопровождения проекта значение этой роли только усиливается. Теги summary появляются как интеллектуальная подсказка у клиентов класса при работе с классом и его методами, - в этом их вторая роль. Наконец, третья роль - они позволяют автоматическое построение документации - XML-отчета.
Вот как выглядит наш абстрактный класс, дополненный подробными спецификациями:
/// <summary> /// Абстрактный класс GenStack{T} задает контейнер с доступом LIFO: /// Функции: /// конструктор new: -> GenStack{T} /// запросы: /// item: GenStack -> T /// empty: GenStack -> Boolean /// процедуры: /// put: GenStack*T -> GenStack /// remove: GenStack -> GenStack /// Аксиомы: /// remove(put(s,x)) = s /// item(put(s,x)) = x /// empty(new)= true /// empty(put(s,x)) = false /// </summary> abstract public class GenStack<T> { /// <summary> /// require: not empty(); /// </summary> /// <returns>элемент вершины(последний пришедший)</returns> abstract public T item(); /// <summary> /// require: not empty(); /// ensure: удален элемент вершины(последний пришедший) /// </summary> abstract public void remove(); /// <summary> /// require: true; ensure: elem находится в вершине стека /// </summary> /// <param name="elem"></param> abstract public void put(T elem); /// <summary> /// require: true; /// </summary> /// <returns>true если стек пуст, иначе false </returns> abstract public bool empty(); }// class GenStack
В приведенном примере программного текста - чуть-чуть. Основной текст задает описание спецификации класса и его методов. Заметьте, здесь спецификации заданы достаточно формально с использованием аксиом, характеризующих смысл операций, которые выполняются над стеком. Не хочется вдаваться в математические подробности, отмечу лишь, что, если задать последовательность операций над стеком, то аксиомы позволяют точно определить состояние стека в результате выполнения этих операций. Как неоднократно отмечалось с первых лекций курса, XML-отчет, построенный по этому проекту, будет содержать в читаемой форме все спецификации нашего класса. Отмечу еще, что все потомки класса должны удовлетворять этим спецификациям, хотя могут добавлять и собственные ограничения. К сожалению, потомки не наследуют текста, заданного в комментариях, - в тегах summary и других тегах, так что для потомков приходится повторно задавать теги summary.
Наш класс определен как универсальный класс с родовым параметром - стек может хранить элементы любого типа, и конкретизация типа будет производиться в момент создания экземпляра стека.
Наш класс определен как абстрактный класс - не задана ни реализация методов, ни то, как стек будет представлен. Эти вопросы будут решать потомки класса.
Перейдем теперь ко второму этапу и построим потомков класса, каждый из которых задает некоторое представление стека и соответствующую этому представлению реализацию методов. Из всех возможных представлений ограничимся двумя. В первом из них стек будет представлен линейной односвязной списковой структурой. Во втором - он строится на массиве фиксированного размера, задавая стек ограниченной емкости.
Реализация стека списком
Вот как выглядит первый потомок абстрактного класса:
/// <summary> /// Стек, построенный на односвязных элементах списка GenLinkable{T} /// </summary> public class OneLinkStack<T> : GenStack<T> { /// <summary> /// ссылка на стек (вершину стека) /// </summary> GenLinkable<T> last; /// <summary> /// Конструктор без аргументов /// </summary> public OneLinkStack() { last = null; } /** * <remarks>Реализация методов абстрактного класса</remarks> * */ /// <summary> /// require: not empty(); /// </summary> /// <returns>элемент вершины(последний пришедший)</returns> public override T item() { return (last.Item); }//item /// <summary> /// require: true; /// </summary> /// <returns>true если стек пуст, иначе false </returns> public override bool empty() { return (last == null); }//empty /// <summary> /// require: true; ensure: elem находится в вершине стека /// </summary> /// <param name="elem"></param> public override void put(T elem) { GenLinkable<T> newitem = new GenLinkable<T>(); newitem.Item = elem; newitem.Next = last; last = newitem; }//put /// <summary> /// require: not empty(); /// ensure: удален элемент вершины(последний пришедший) /// </summary> public override void remove() { last = last.Next; }//remove }//class OneLinkStack
Посмотрите, что происходит при наследовании от универсального класса. Во-первых, сам потомок также является универсальным классом:
public class OneLinkStack<T> : GenStack<T>
Во-вторых, если потомок является клиентом некоторого класса, то и этот класс, возможно, также должен быть универсальным, как в нашем случае происходит с классом GenLinkable<T>:
GenLinkable<T> last; //ссылка на стек (элемент стека)
В-третьих, тип T встречается в тексте потомка всюду, где речь идет о типе элементов, добавляемых в стек, как, например:
public override void put(T elem)
По ходу дела нам понадобился класс, задающий представление элементов стека в списковом представлении. Объявим его:
/// <summary> /// Элемент односвязного списка /// содержит два поля - информационное и ссылку /// </summary> /// <typeparam name="T">тип элементов, хранимых в списке</typeparam> public class GenLinkable<T> { /// <summary> /// Информационное поле - объект типа T /// </summary> public T Item; /// <summary> /// Ссылка на следующий элемент списка /// </summary> public GenLinkable<T> Next; /// <summary> /// Конструктор без аргументов /// Инициализиует поля значениями по умолчанию /// </summary> public GenLinkable() { Item = default(T); Next = null; } }
Класс устроен достаточно просто: у него два поля, одно для хранения элементов, помещаемых в стек и имеющее тип T, другое - указатель на следующий элемент. Обратите внимание на конструктор класса, в котором для инициализации элемента используется конструкция default(T), возвращающая значение, устанавливаемое по умолчанию для типа T.