Тверской государственный университет
Опубликован: 02.12.2009 | Доступ: свободный | Студентов: 2376 / 262 | Оценка: 4.47 / 4.24 | Длительность: 14:45:00
Лекция 9:

Универсальность. Классы с родовыми параметрами

Стек. От абстрактного, универсального класса к конкретным версиям

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

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.

Илья Ардов
Илья Ардов

Добрый день!

Я записан на программу. Куда высылать договор и диплом?

Дарья Федотова
Дарья Федотова
Михаил Алексеев
Михаил Алексеев
Россия, Уфа, УГАТУ, 2002
Олег Корсак
Олег Корсак
Латвия, Рига