Тверской государственный университет
Опубликован: 02.12.2009 | Доступ: свободный | Студентов: 2458 / 323 | Оценка: 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.

Федор Антонов
Федор Антонов

Здравствуйте!

Записался на ваш курс, но не понимаю как произвести оплату.

Надо ли писать заявление и, если да, то куда отправлять?

как я получу диплом о профессиональной переподготовке?

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

Добрый день!

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