русс | укр

Языки программирования

ПаскальСиАссемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование

Все о программировании


Linux Unix Алгоритмические языки Аналоговые и гибридные вычислительные устройства Архитектура микроконтроллеров Введение в разработку распределенных информационных систем Введение в численные методы Дискретная математика Информационное обслуживание пользователей Информация и моделирование в управлении производством Компьютерная графика Математическое и компьютерное моделирование Моделирование Нейрокомпьютеры Проектирование программ диагностики компьютерных систем и сетей Проектирование системных программ Системы счисления Теория статистики Теория оптимизации Уроки AutoCAD 3D Уроки базы данных Access Уроки Orcad Цифровые автоматы Шпаргалки по компьютеру Шпаргалки по программированию Экспертные системы Элементы теории информации

Обширный интерфейс


Дата добавления: 2015-06-12; просмотров: 573; Нарушение авторских прав


Когда обсуждались абстрактные типы ($$13.3) и узловые классы ($$13.4), было подчеркнуто, что все функции базового класса реализуются в самом базовом или в производном классе. Но существует и другой способ построения классов. Рассмотрим, например, списки, массивы, ассоциативные массивы, деревья и т.д. Естественно желание для всех этих типов, часто называемых контейнерами, создать обобщающий их класс, который можно использовать в качестве интерфейса с любым из перечисленных типов. Очевидно, что пользователь не должен знать детали, касающиеся конкретного контейнера. Но задача определения интерфейса для обобщенного контейнера нетривиальна. Предположим, что такой контейнер будет определен как абстрактный тип, тогда какие операции он должен предоставлять? Можно предоставить только те операции, которые есть в каждом контейнере, т.е. пересечение множеств операций, но такой интерфейс будет слишком узким. На самом деле, во многих, имеющих смысл случаях такое пересечение пусто. В качестве альтернативного решения можно предоставить объединение всех множеств операций и предусмотреть динамическую ошибку, когда в этом интерфейсе к объекту применяется "несуществующая" операция. Объединение интерфейсов классов, представляющих множество понятий, называется обширным интерфейсом. Опишем "общий" контейнер объектов типа T:

class container {

public:

struct Bad_operation { // класс особых ситуаций

const char* p;

Bad_operation(const char* pp) : p(pp) { }

};

virtual void put(const T*)

{ throw Bad_operation("container::put"); }

virtual T* get()

{ throw Bad_operation("container::get"); }

virtual T*& operator[](int)

{ throw Bad_operation("container::[](int)"); }

virtual T*& operator[](const char*)

{ throw Bad_operation("container::[](char*)"); }



// ...

};

Все-таки существует мало реализаций, где удачно представлены как индексирование, так и операции типа списочных, и, возможно, не стоит совмещать их в одном классе.

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

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

class slist_container : public container, private slist {

public:

void put(const T*);

T* get();

T*& operator[](int)

{ throw Bad_operation("slist::[](int)"); }

T*& operator[](const* char)

{ throw Bad_operation("slist::[](char*)"); }

// ...

};

Чтобы упростить обработку динамических ошибок для списка введены операции индексирования. Можно было не вводить эти нереализованные для списка операции и ограничиться менее полной информацией, которую предоставляют особые ситуации, запущенные в классе container:

class vector_container : public container, private vector {

public:

T*& operator[](int);

T*& operator[](const char*);

// ...

};

Если быть осторожным, то все работает нормально:

void f()

{

slist_container sc;

vector_container vc;

// ...

}

 

void user(container& c1, container& c2)

{

T* p1 = c1.get();

T* p2 = c2[3];

// нельзя использовать c2.get() или c1[3]

// ...

}

Все же для избежания ошибок при выполнении программы часто приходится использовать динамическую информацию о типе ($$13.5) или особые ситуации ($$9). Приведем пример:

void user2(container& c1, container& c2)

/*

обнаружение ошибки просто, восстановление - трудная задача

*/

{

try {

T* p1 = c1.get();

T* p2 = c2[3];

// ...

}

catch(container::Bad_operation& bad) {

// Приехали!

// А что теперь делать?

}

}

или другой пример:

void user3(container& c1, container& c2)

/*

обнаружение ошибки непросто,

а восстановление по прежнему трудная задача

*/

{

slist* sl = ptr_cast(slist_container,&c1);

vector* v = ptr_cast(vector_container, &c2);

if (sl && v) {

T* p1 = c1.get();

T* p2 = c2[3];

// ...

}

else {

// Приехали!

// А что теперь делать?

}

}

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

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

Каркас области приложения

Мы перечислили виды классов, из которых можно создать библиотеки, нацеленные на проектирование и повторное использование прикладных программ. Они предоставляют определенные "строительные блоки" и объясняют как из них строить. Разработчик прикладного обеспечения создает каркас, в который должны вписаться универсальные строительные блоки. Задача проектирования прикладных программ может иметь иное, более обязывающее решение: написать программу, которая сама будет создавать общий каркас области приложения. Разработчик прикладного обеспечения в качестве строительных блоков будет встраивать в этот каркас прикладные программы. Классы, которые образуют каркас области приложения, имеют настолько обширный интерфейс, что их трудно назвать типами в обычном смысле слова. Они приближаются к тому пределу, когда становятся чисто прикладными классами, но при этом в них фактически есть только описания, а все действия задаются функциями, написанными прикладными программистами.

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

class filter {

public:

class Retry {

public:

virtual const char* message() { return 0; }

};

 

virtual void start() { }

virtual int retry() { return 2; }

virtual int read() = 0;

virtual void write() { }

virtual void compute() { }

virtual int result() = 0;

};

Нужные для производных классов функции описаны как чистые виртуальные, остальные функции просто пустые. Каркас содержит основной цикл обработки и зачаточные средства обработки ошибок:

int main_loop(filter* p)

{

for (;;) {

try {

p->start();

while (p->read()) {

p->compute();

p->write();

}

return p->result();

}

catch (filter::Retry& m) {

cout << m.message() << '\n';

int i = p->retry();

if (i) return i;

}

catch (...) {

cout << "Fatal filter error\n";

return 1;

}

}

}

Теперь прикладную программу можно написать так:

class myfilter : public filter {

istream& is;

ostream& os;

char c;

int nchar;

public:

int read() { is.get(c); return is.good(); }

void compute() { nchar++; };

int result()

{ os << nchar

<< "characters read\n";

return 0;

}

 

myfilter(istream& ii, ostream& oo)

: is(ii), os(oo), nchar(0) { }

};

и вызывать ее следующим образом:

int main()

{

myfilter f(cin,cout);

return main_loop(&f);

}

Настоящий каркас, чтобы рассчитывать на применение в реальных задачах, должен создавать более развитые структуры и предоставлять больше полезных функций, чем в нашем простом примере. Как правило, каркас образует дерево узловых классов. Прикладной программист поставляет только классы, служащие листьями в этом многоуровневом дереве, благодаря чему достигается общность между различными прикладными программами и упрощается повторное использование полезных функций, предоставляемых каркасом. Созданию каркаса могут способствовать библиотеки, в которых определяются некоторые полезные классы, например, такие как scrollbar ($$12.2.5) и dialog_box ($$13.4). После определения своих прикладных классов программист может использовать эти классы.



<== предыдущая лекция | следующая лекция ==>
Правильное и неправильное использование динамической информации о типе | Интерфейсные классы


Карта сайта Карта сайта укр


Уроки php mysql Программирование

Онлайн система счисления Калькулятор онлайн обычный Инженерный калькулятор онлайн Замена русских букв на английские для вебмастеров Замена русских букв на английские

Аппаратное и программное обеспечение Графика и компьютерная сфера Интегрированная геоинформационная система Интернет Компьютер Комплектующие компьютера Лекции Методы и средства измерений неэлектрических величин Обслуживание компьютерных и периферийных устройств Операционные системы Параллельное программирование Проектирование электронных средств Периферийные устройства Полезные ресурсы для программистов Программы для программистов Статьи для программистов Cтруктура и организация данных


 


Не нашли то, что искали? Google вам в помощь!

 
 

© life-prog.ru При использовании материалов прямая ссылка на сайт обязательна.

Генерация страницы за: 0.096 сек.