Решение, представленное в предыдущем разделе, хоть и правильное, но имеет уже упомянутый недостаток: необходимо транслировать программу целиком. Между тем не хотелось бы компилировать каждый раз заново уже проверенные и отлаженные модули. Если модуль не изменяется, то можно оттранслировать его один раз, а потом просто компоновать с остальными — именно так поступают с библиотечными функциями.
Для этого разделим наш модуль с классом TStack на две части: интерфейс и реализацию. Интерфейс класса является его определением, хотя для методов указаны только прототипы. Но по интерфейсу компилятор может вычислить размер класса, поэтому интерфейса достаточно, чтобы объявлять объекты этого класса.
Файл с интерфейсом назовем TStack.h, а файл с реализацией пусть, как и прежде, имеет имя TStack.cpp. Файл TStack.h называется заголовком (header), и именно он подключается к другим модулям с помощью препроцессора. Стража нужно прописать в нем (листинг 13.5).
Листинг 13.5. Файл TStack.h — интерфейс класса TStack
#ifndef _STACK #define _STACK class TStack {public: TStackO: void push(void *d); void *top()const: void *pop(): bool emptyO const: private:
struct Elem:
Elem * Head:
TStack(const TStack &); // закрыли копирование
TStack& operator=(const TStack &); // закрыли присваивание };
#endif /* _STACK */
В проекте два файла: файл реализации TStack.cpp и файл main.cpp с программой-клиентом.
Файл реализации просто включается в проект интегрированной среды1. Естественно, интерфейс класса обязан присутствовать и в этом файле (листинг 13.6).
Листинг 13.6. Файл TStack.cpp — реализация класса TStack #include "TStack.h" // подключение интерфейса
struct TStack::Elem {void *data; Elem *next;
Elem (void *d, Elem *p): data(d). next(p) {} };
TStack::TStack(): Head(0), count(0) {}void TStack::push(void *d) { Head = new Elem(d, Head); }void * TStack::top() const {return Head? Head->data:0; }void * TStack::pop() {if (Head == 0) return 0; void *top = Head->data;
Elem *oldHead = Head; Head = Head->next; delete oldHead; return top; } bool TStack::empty() const {return Head==0; }
В программе-клиенте тоже подключается только интерфейс (листинг 13.7).
Опубликовал Kest
January 16 2014 12:56:42 ·
13 Комментариев ·
4315 Прочтений ·
• Не нашли ответ на свой вопрос? Тогда задайте вопрос в комментариях или на форуме! •
Комментарии
Павел January 17 2014 17:13:18
Интерфейс и реализация, это то на чем держатся многие современные объектно-ориентированные языки программирования.
Благодаря такой реализации можно достичь высокую прозрачность кода в плане его читабельности, а также создать предпосылки для его масштабируемость в будущем. Кроме того этот подход даст вам возможность создавать серьезные проекты с хорошо читаемой структурой и иерархией.
Вадим January 18 2014 08:44:35
Сейчас на данной парадигме строятся все языки высокого уровня (ООП). Это и понятно, ведь проекты становятся все сложнее и сложнее, а код при этом не упрощается.
По сути это этап эволюции самих языков программирования, где данная парадигма является его следующей ступенью в развитии. Интересно, что будет дальше?
Александр January 18 2014 22:41:30
Необходимо транслировать программу целиком точно подмечено. Важна проработка и целевой анализ кода не только на "интерфейс-этапе" , но и про реализацию не стоит забывать. Время не стоит на месте, написание начинает носить иной характер, совсем не тот, что пару лет назад был доступен почти каждому начинающему программисту. Очень важно понимать и разделять 2 тесно связанных понятия : иерархия и структура.
Михаил January 19 2014 14:39:55
Хотя методика использования классов-дескрипторов имеет свои преимущества и безусловно приближает нас к возможности безопасного извлечения классов из DLL, она также имеет свои недостатки. Хочу заметити, что класс интерфейса вынужден явно передавать каждый вызов метода классу реализации. Для простого класса вроде FastString только с двумя открытыми операторами, конструктором и деструктором, это не проблема. Для большой библиотеки классов с сотнями или тысячами методов написание этих передающих процедур было бы весьма утомительным и явилось бы потенциальным источником ошибок. Наконец, методика классов-дескрипторов не полностью решает проблемы совместимости транслятора/компоновщика, а они все же должны быть решены, если мы хотим иметь основу, действительно пригодную для создания компонентов повторного использования. Если я не прав прошу уточнить в чем или поправить меня
Борис January 19 2014 19:42:29
Главное отличие класса от интерфейса — в том, что класс состоит из интерфейса и реализации.
Любой класс всегда неявно объявляет свой интерфейс — то, что доступно при использовании класса извне. Если у нас есть класс Ключ и у него публичный метод Открыть, который вызывает приватные методы Вставить, Повернуть и Вынуть, то интерфейс класса Ключ состоит из метода Открыть. Когда мы унаследуем какой-то класс от класса Ключ, он унаследует этот интерфейс. Может немного не по теме, но как то так товарищи
Вениамин January 19 2014 19:48:46
Борис, послушай, кроме этого интерфейса, у класса есть также реализация — методы Вставить, Повернуть, Вынуть и их вызов в методе Открыть. Между прочим наследники Ключа, наследуют вместе с интерфейсом также реализацию.
Вот здесь я вам скажу ребят... именно здесь таятся проблемы. итак, предположим, у нас есть некоторая модель, которая предполагает использование ключа для открытия двери. Также Она знает интерфейс Ключа и поэтому вызывает метод Открыть. \кто не согласен , пишите.
Генадий January 19 2014 19:56:31
когдато совсем давно был замечательный клас-интерфейс, такой который имел и protected (конструктор) между прочим по-умолчанию... так же деструктор только уже виртуальный. он имел запрещенные в свою очередь констуктор копирования иприсваивания\\ . И было так у него всего две реализации: назовем их мы AlphaGraphics и 2я BetaGraphics.
Деонис January 19 2014 20:01:16
Восходящее преобразование имеет место даже в такой простой команде: Shape s = new CircleO;
Здесь создается объект Circle, и полученная ссылка немедленно присваивается типу Shape. На первый взгляд это может показаться ошибкой (присвоение одного типа другому), но в действительности все правильно, потому что тип Circle (окружность) является типом Shape (фигура) посредством наследования. Компилятор принимает команду и не выдает сообщения об ошибке.
Яков January 19 2014 20:05:01
Базовый класс Shape устанавливает общий интерфейс для всех классов, производных от Shape — то есть любую фигуру можно нарисовать (draw()) и стереть (erase()). Производные классы переопределяют этот интерфейс, чтобы реализовать уникальное поведение для каждой конкретной фигуры. Это я еще на 1 курсе Рыбтеха знал.
Юлий January 19 2014 20:06:52
Яков, добавлю вот еще чего: Класс RandomShapeGenerator — своего рода «фабрика», при каждом вызове метода next() производящая ссылку на случайно выбираемый объект Shape. Заметьте, что восходящее преобразование выполняется в командах return, каждая из которых получает ссылку на объект Circle, Square или Triangle, а выдает ее за пределы next() в виде возвращаемого типа Shape. Таким образом, при вызове этого метода вы не сможете определить конкретный тип объекта, поскольку всегда получаете просто Shape.
Цикорий January 19 2014 20:08:31
Метод main() содержит массив ссылок на Shape, который заполняется последовательными вызовами RandomShapeGenerator.next(). К этому моменту вам известно, что имеются объекты Shape, но вы не знаете об этих объектах ничего конкретного (так же, как и компилятор). Но если перебрать содержимое массива и вызвать draw() для каждого его элемента, то, как по волшебству, произойдет верное, свойственное для определенного типа действие — в этом нетрудно убедиться, взглянув на результат работы программы.
Щебра January 19 2014 20:14:03
Итак, Случайный выбор фигур в нашем примере всего лишь помогает понять, что компилятор во время компиляции кода не располагает информацией о том, какую реализацию следует вызывать. Все вызовы метода draw() проводятся с применением позднего связывания. Согласитесь все логично и без данного метода впринципе не обойтись..
Изольда January 19 2014 20:36:30
Теперь вернемся к программе Music.java. Благодаря полиморфизму вы можете добавить в нее сколько угодно новых типов, не изменяя метод tune(). В хорошо спланированной ООП-программе большая часть ваших методов (или даже все методы) следуют модели метода tune(), оперируя только с интерфейсом базового класса.
Добавить комментарий
Рейтинги
Рейтинг доступен только для пользователей.
Пожалуйста, залогиньтесь или зарегистрируйтесь для голосования.
Нет данных для оценки.
Гость
Вы не зарегистрированны? Нажмите здесь для регистрации.