4.6 Видимость и функции доступа

Предыдущая  Содержание  Следующая V*D*V

Теперь можно попытаться реализовать Circle_draw(). Сокрытие информации диктует, чтобы мы использовали три файла для каждого класса на основе принципа "необходимого знания". Circle.h содержит интерфейс абстрактного типа данных; чтобы сделать декларации доступными для унаследованных методов, в подклассе подключается файл интерфейса суперкласса :

 

#include "Point.h"

 

extern const void * Circle; /* new(Circle, x, y, rad) */

 

Файл интерфейса Circle.h подключается кодом приложения и для реализации класса; он защищён от многократного подключения.

Представление окружности декларируется во втором заголовочном файле, Circle.r. Подкласс подключает файл представления суперкласса, так что можно получить представление подкласса путём расширения суперкласса:

 

#include "Point.r"

 

struct Circle { const struct Point _; int rad; };

 

Подкласс нуждается в представлении суперкласса чтобы реализовать наследование: struct Circle содержит const struct Point. Точка, конечно, не постоянна — move() будет менять её координаты — но ключевое слово const защищает от случайной перезаписи этих компонентов. Файл представления Circle.r подключается только для реализации класса; он защищён от многократного подключения.

И, наконец, реализация окружности определяется в исходном файле Circle.c, который подключает файлы интерфейса и представления для класса и для управления объектами:

 

 

#include "Circle.h"

#include "Circle.r"

#include "new.h"

#include "new.r"

 

static void Circle_draw (const void * _self) {

    const struct Circle * self = _self;

 

    printf("circle at %d,%d rad %d\n",

    self -> _.x, self -> _.y, self -> rad);

}

 

В Circle_draw() читаются компоненты точки для круга, вторжением в компонент подкласса с помощью "невидимого имени"' _. С точки зрения сокрытия информации это не очень хорошая идея. Хотя чтение значений координат не должно создать серьёзных проблем, мы никогда не сможем быть уверены, что в других ситуациях реализация подкласса не попробует смухлевать и изменить свой компонент суперкласса непосредственно, потенциально имея возможность разрушить его неизменность.

Эффективность достигается когда подкласс обращается к компонентам своего суперкласса непосредственно. Сокрытие информации и надёжность требуют, чтобы суперкласс как можно лучше скрывал своё собственное представление от своих подклассов. Если мы выбираем последнее, то должны предоставить функции доступа ко всем тем компонентам суперкласса, которые разрешено видеть подклассу, а также функции для модификации компонентов, которые подкласс может изменить, если таковые имеются.

Функции доступа и модификации являются статически скомпонованными методами. Если мы объявим их в файле представления суперкласса, который подключается только в реализации подклассов, то сможем использовать макросы, потому что побочные эффекты не являются проблемой, если макрос использует каждый аргумент только один раз. В качестве примера в Point.r мы определяем следующие макросы доступа (* В ANSI-C параметризованный макрос раскрывается только если имя макроса появляется перед левой скобкой. В другом месте имя макроса ведёт себя также как и любой другой идентификатор.):

 

#define x(p) (((const struct Point *)(p)) -> x)

#define y(p) (((const struct Point *)(p)) -> y)

 

Эти макросы могут быть применены к указателю на любой объект, который начинается со struct Point, то есть к объектам любого подкласса наших точек. Техника заключается в повышающем приведении указателя к нашему суперклассу и обращению к интересующему компоненту. const в блоках приведения блокирует присвоение к результату. Если бы ключевое слово const было опущено

 

#define x(p) (((struct Point *)(p)) -> x)

 

вызов макроса x(p) производит L-значение, которое может быть целью присвоения. Лучшей функцией модификации было бы макроопределение

 

#define set_x(p,v) (((struct Point *)(p)) -> x = (v))

 

которое производит присвоение.

Вне реализации подкласса для функций доступа и модификации можно использовать лишь статически скомпонованные методы. Мы не можем прибегать к макросам, так как внутреннее представление суперкласса не доступно для обращения к нему с помощью макроса. Сокрытие информации осуществляется через не предоставление для подключения в приложение файла представления Point.r.

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

 

struct Point {

    const void * class;

    int x, y;          /* координаты */

};

 

Для реализаций подклассов мы предлагаем следующую непрозрачную версию:

 

struct Point {

    const char _ [ sizeof( struct {

        const void * class;

        int x, y;           /* координаты */

    })];

};

 

Эта структура имеет такой же размер, как и раньше, но нельзя ни прочитать, ни записать компоненты, так как они скрыты в анонимной внутренней структуре. Загвоздка в том, что обе декларации должны содержать идентичные объявления компонентов, а это трудно поддерживать без препроцессора.

 

Предыдущая  Содержание  Следующая