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

Читать онлайн.



Скачать книгу

= name;

      }

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

      };

      Драйвер консоли



<p>15</p>

И продолжает применяться.

<p>16</p>

В разных версиях UNIX требования разные; это всего лишь пример.