Рис. 22.2.3. 3: Родовое порождение: уточняются типы данных; порождается класс путем подстановки конкретных типов На этапе спецификации, как правило, создается абстрактный, универсальный класс, где задана только сигнатура методов, но не их реализация; где определены имена типов, но не их конкретизация. Здесь же, используя возможности тегов класса, формально или неформально задаются спецификации, описывающие семантику методов класса. Далее в ходе разработки, благодаря механизму наследования, появляются потомки абстрактного класса, каждый из которых задает реализацию методов. На следующем этапе, благодаря механизму универсализации, появляются экземпляры универсального класса, каждый из которых выполняет операции класса над данными соответствующих типов. Для наполнения этой схемы реальным содержанием давайте рассмотрим некоторый пример с прохождением всех трех этапов. Стек. От абстрактного, универсального класса к конкретным версиям Возьмем классическую задачу определения стека. Следуя схеме, определим абстрактный универсальный класс, описывающий всевозможные представления стеков: /// <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: удален элемент вершины(последний пришедший) |