Название | Чистая архитектура. Искусство разработки программного обеспечения |
---|---|
Автор произведения | Роберт Мартин |
Жанр | Программирование |
Серия | Библиотека программиста (Питер) |
Издательство | Программирование |
Год выпуска | 2018 |
isbn | 978-5-4461-0772-8, 9780134494166 |
}
char* getName(struct NamedPoint* np) {
return np->name;
}
main.c
#include "point.h"
#include "namedPoint.h"
#include <stdio.h>
int main(int ac, char** av) {
struct NamedPoint* origin = makeNamedPoint(0.0, 0.0, "origin");
struct NamedPoint* upperRight = makeNamedPoint
(1.0, 1.0, "upperRight");
printf("distance=%f\n",
distance(
(struct Point*) origin,
(struct Point*) upperRight));
}
Внимательно рассмотрев основной код в файле main.c
, можно заметить, что структура данных NamedPoint
используется, как если бы она была производной от структуры Point
. Такое оказалось возможным потому, что первые два поля в NamedPoint
совпадают с полями в Point
. Проще говоря, NamedPoint
может маскироваться под Point
, потому что NamedPoint
фактически является надмножеством Point
и имеет члены, соответствующие структуре Point
, следующие в том же порядке.
Этот прием широко применялся[15] программистами до появления ОО. Фактически именно так C++ реализует единственное наследование.
То есть можно сказать, что некоторая разновидность наследования у нас имелась задолго до появления языков ОО. Впрочем, это утверждение не совсем истинно. У нас имелся трюк, хитрость, не настолько удобный, как настоящее наследование. Кроме того, с помощью описанного приема очень сложно получить что-то похожее на множественное наследование.
Обратите также внимание, как в main.c
мне пришлось приводить аргументы NamedPoint
к типу Point
. В настоящем языке ОО такое приведение к родительскому типу производится неявно.
Справедливости ради следует отметить, что языки ОО действительно сделали маскировку структур данных более удобной, хотя это и не совсем новая особенность.
Итак, мы не можем дать идее ОО ни одного очка за инкапсуляцию и можем дать лишь пол-очка за наследование. Пока что общий счет не впечатляет.
Но у нас есть еще одно понятие.
Полиморфизм?
Была ли возможность реализовать полиморфное поведение до появления языков ОО? Конечно! Взгляните на следующую простую программу copy на языке C.
#include <stdio.h>
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);
};
Драйвер консоли
15
И продолжает применяться.
16
В разных версиях UNIX требования разные; это всего лишь пример.