Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лек_ООП_2_8 - 2_9 Реалізація поліморфізму та Ш...doc
Скачиваний:
0
Добавлен:
01.03.2025
Размер:
291 Кб
Скачать
☆

Два основных механизма объектной технологии

Наследование и универсальность являются двумя основными механизмами, обеспечивающими мощность объектной технологии разработки.

Наследование позволяет специализировать операции класса, уточнить, как должны выполняться операции.

Универсализация позволяет специализировать данные, уточнить, над какими данными выполняются операции.

На этапе задания спецификаций появляются абстрактные, универсальные классы, которые в ходе разработки становятся вполне конкретными классами с конкретными типами данных.

Механизмы наследования и универсализации позволяют существенно сократить объем кода, описывающего программную систему, поскольку потомки не повторяют наследуемый код своих родителей, а единый код универсального класса используется при каждой конкретизации типов данных.

На этапе спецификации, как правило, создается абстрактный универсальный класс, где задана только сигнатура методов, но не их реализация; где определены имена типов, но не их конкретизация. Здесь же, используя возможности тегов класса, формально или неформально задаются спецификации, описывающие семантику методов класса.

Далее в ходе разработки, благодаря механизму наследования, появляются потомки абстрактного класса, каждый из которых задает реализацию методов.

На следующем этапе, благодаря механизму универсализации, появляются экземпляры универсального класса, каждый из которых выполняет операции класса над данными соответствующих типов.

ПРИМЕР: Стек. От абстрактного, универсального класса к конкретным версиям

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

/// <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 t);

/// <summary>

/// require: true;

/// </summary>

/// <returns>true если стек пуст, иначе false </returns>

abstract public bool empty();

}// class GenStack

Описанный класс является универсальным - стек может хранить элементы любого типа, и конкретизация типа будет производиться в момент создания экземпляра стека.

Описанный класс является абстрактным - не задана ни реализация методов, ни то, как стек будет представлен. Эти вопросы будут решать потомки класса.

Построим два потомка класса: В первом из них стек будет представлен линейной односвязной списковой структурой. Во втором стек строится на массиве фиксированного размера, задавая стек ограниченной емкости.

Для первого потомка нам понадобится класс, задающий представление элементов в списковом представлении. Объявим его:

public class GenLinkable<T>

{

public T Item; // Значение элемента

public GenLinkable<T> Next; // Следующий элемент

public GenLinkable() // Конструктор

{ Item = default(T); Next = null; }

}

Класс устроен достаточно просто, у него два поля: одно для хранения элементов, помещаемых в стек и имеющее тип T, другое - указатель на следующий элемент. Обратите внимание на конструктор класса, в котором для инициализации элемента используется новая конструкция default(T), которая возвращает значение, устанавливаемое по умолчанию для типа T.

/// <summary>

/// Стек, построенный на односвязных элементах списка GenLinkable<T>

/// </summary>

public class OneLinkStack<T> : GenStack<T>

{

public OneLinkStack()

{ last = null; }

GenLinkable<T> last; //ссылка на стек (вершину стека)

public override T item()

{ return (last.Item); }//item

public override bool empty()

{ return (last == null); }//empty

public override void put(T elem)

{

GenLinkable<T> newitem = new GenLinkable<T>();

newitem.Item = elem; newitem.Next = last;

last = newitem;

}//put

public override void remove()

{

last = last.Next;

}//remove

}//class OneLinkStack

Посмотрите, что происходит при наследовании от универсального класса.

Во-первых, сам потомок также является универсальным классом

Во-вторых, если потомок является клиентом некоторого класса, то и этот класс, возможно, также должен быть универсальным, как в нашем случае происходит с классом GenLinkable<T>:

В-третьих, тип T встречается в тексте потомка всюду, где речь идет о типе элементов, добавляемых в стек.

Второй потомок абстрактного класса реализует стек по-другому, используя представление в виде массива. Потомок задает стек ограниченной емкости. Емкостью стека можно управлять в момент его создания. В ряде ситуаций использование такого стека предпочтительнее по соображениям эффективности, поскольку не требует динамического создания элементов.

public class ArrayUpStack<T> : GenStack<T>

{

int SizeOfStack;

T[] stack;

int top;

/// <summary>

/// конструктор

/// </summary>

/// <param name="size">размер стека</param>

public ArrayUpStack(int size)

{ SizeOfStack = size; stack = new T[SizeOfStack]; top = 0; }

/// <summary>

/// require: (top < SizeOfStack)

/// </summary>

/// <param name="x"> элемент, помещаемый в стек</param>

public override void put(T x)

{ stack[top] = x; top++; }

public override void remove()

{ top--; }

public override T item()

{ return (stack[top - 1]); }

public override bool empty()

{ return (top == 0); }

}//class ArrayUpStack

На третьем этапе порождаются конкретные экземпляры потомков - универсальных классов, в этот момент и происходит конкретизация типов, и два экземпляра одного универсального класса могут работать с данными различных типов. Этот процесс создания экземпляров с подстановкой конкретных типов называют родовым порождением экземпляров.

OneLinkStack<int> stack1 = new OneLinkStack<int>();

stack1.put(11);

OneLinkStack<string> stack2 = new OneLinkStack<string>();

stack2.put("Петров"); stack2.put("Васильев");

ArrayUpStack<double> stack3 = new ArrayUpStack<double>(10);

stack3.put(3.33);