Размер шрифта
-
+

Чистая архитектура. Искусство разработки программного обеспечения - стр. 19

Этот прием широко применялся[15] программистами до появления ОО. Фактически именно так C++ реализует единственное наследование.

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

Обратите также внимание, как в >main.c мне пришлось приводить аргументы >NamedPoint к типу >Point. В настоящем языке ОО такое приведение к родительскому типу производится неявно.

Справедливости ради следует отметить, что языки ОО действительно сделали маскировку структур данных более удобной, хотя это и не совсем новая особенность.

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

Но у нас есть еще одно понятие.

Полиморфизм?

Была ли возможность реализовать полиморфное поведение до появления языков ОО? Конечно! Взгляните на следующую простую программу copy на языке C.


>#include


>void copy() {

> int c;

> while ((c=getchar())!= EOF)

> putchar(c);

>}


Функция >getchar() читает символы из >STDIN. Но какое устройство в действительности скрыто за ширмой >STDIN? Функция >putchar() записывает символы в устройство >STDOUT. Но что это за устройство? Эти функции являются полиморфными – их поведение зависит от типов устройств >STDIN и >STDOUT.

В некотором смысле >STDIN и >STDOUT похожи на интерфейсы в силе Java, когда для каждого устройства имеется своя реализация этих интерфейсов. Конечно, в примере программы на C нет никаких интерфейсов, но как тогда вызов >getchar() передается драйверу устройства, который фактически читает символ?

Ответ на этот вопрос прост: операционная система UNIX требует, чтобы каждый драйвер устройства ввода/вывода реализовал пять стандартных функций[16]: >open, >close, >read, >write и >seek. Сигнатуры этих функций должны совпадать для всех драйверов.

Структура FILE имеет пять указателей на функции. В нашем случае она могла бы выглядеть как-то так:


>struct FILE {

> void (*open)(char* name, int mode);

> void (*close)();

> int (*read)();

> void (*write)(char);

> void (*seek)(long index, int mode);

>};


Драйвер консоли определяет эти функции и инициализирует указатели на них в структуре FILE примерно так:


>#include "file.h"


>void open(char* name, int mode) {/*…*/}

>void close() {/*…*/};

>int read() {int c;/*…*/ return c;}

>void write(char c) {/*…*/}

>void seek(long index, int mode) {/*…*/}


>struct FILE console = {open, close, read, write, seek};


Если теперь предположить, что символ >STDIN определен как указатель >FILE* и ссылается на структуру console, тогда >getchar() можно реализовать как-то так:


>extern struct FILE* STDIN;


>int getchar() {

> return STDIN->read();

>}


Иными словами, >getchar() просто вызывает функцию, на которую ссылается указатель read в структуре >FILE, на которую, в свою очередь, ссылается >STDIN.

Этот простой трюк составляет основу полиморфизма в ОО. В C++, например, каждая виртуальная функция в классе представлена указателем в таблице виртуальных методов >vtable и все вызовы виртуальных функций выполняются через эту таблицу. Конструкторы производных классов просто инициализируют таблицу

Страница 19