Перейти к содержанию

Наследование в Solidity: расширение реестра

Библиотека объединяет операцию. Наследование позволяет организовать тип с общими полями и поведением. Чтобы понять разницу, создадим наблюдателя реестра: общий базовый тип хранит подключение, а производный тип сообщает, виден ли активный курс.

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

Интерфейс не является копией состояния

Сначала зададим нужный договор чтения:

interface ICourseCatalog {
    function courses(uint256 id)
        external
        view
        returns (bytes32 contentHash, bool active);
}

Это описание метода чужого экземпляра. Объявление интерфейса не создаёт реестр, не переносит его mapping и не доказывает, что произвольный адрес поддерживает нужный код. Оно позволяет компилятору и клиенту выразить форму вызова. Адрес выбирается отдельно при создании наблюдателя.

Getter публичного mapping базового контракта соответствует нужной паре полей. Если другой реестр меняет типы или порядок, интерфейс следует пересмотреть. Название ICourseCatalog не делает любой ответ совместимым автоматически.

Базовый тип соединения

abstract contract CatalogConnection {
    error MissingCatalog();
    ICourseCatalog public immutable catalog;

    constructor(address catalog_) {
        if (catalog_ == address(0)) revert MissingCatalog();
        catalog = ICourseCatalog(catalog_);
    }
}

abstract обозначает тип, предназначенный здесь как основа композиции. Он хранит адрес в форме интерфейса и проверяет нулевой вход. Приведение ICourseCatalog(catalog_) не выполняет аудит адреса и не вызывает getter. Поэтому проверка на ненулевой адрес не является полной проверкой совместимости.

Поле catalog неизменяемое. Производный наблюдатель не получает скрытый метод переключения на другой реестр. Если переключение требуется приложению, нужен отдельный договор управления и его ограничения. Для первой композиции постоянная ссылка делает источник ответа определённым.

Производный наблюдатель

В том же файле добавим тип:

contract CourseVisibility is CatalogConnection {
    constructor(address catalog_) CatalogConnection(catalog_) {}

    function visible(uint256 id) external view returns (bool) {
        (bytes32 digest, bool active) = catalog.courses(id);
        return digest != bytes32(0) && active;
    }
}

is CatalogConnection вводит наследование общей настройки. Конструктор производного типа передаёт адрес базовому конструктору. Метод visible делает внешний вызов выбранного реестра и соединяет два результата в одно условие. При H1,true ожидается true; при нулях либо неактивности — false.

Здесь есть граница доверия, которой не было у pure-инспектора. Наблюдатель зависит от ответа другого контракта. Если адрес неверен, вызов может откатиться или вернуть неподходящие данные. Мы не подменяем такие ошибки нейтральным false: это скрыло бы неправильное подключение под отсутствием материала.

Наследование и внешний вызов

Производный тип получает предусмотренные члены базового типа внутри своей программы. Но catalog.courses обращается к отдельному адресу, у которого своё состояние. Это два разных механизма, хотя оба присутствуют в одном листинге.

Если бы мы наследовали сам CourseCatalog, возник бы новый экземпляр с собственным состоянием по его модели. Он не читал бы автоматически старый реестр по другому адресу. Поэтому наследование не является способом миграции данных. Для перехода между экземплярами нужны явные чтение, перенос или объявление нового источника.

Также доступность членов зависит от видимости. internal подходит использованию в производном коде, private имеет более узкую область. Изменение видимости ради наследования может расширить возможные пути работы, и его следует рассмотреть с точки зрения требований, а не только компиляции.

Virtual и override

Для изменения поведения наследуемой функции язык использует предусмотренные объявления virtual и override. Однако право переопределения не гарантирует сохранения всех исходных свойств. Производный метод способен убрать проверку роли, поменять порядок действий или добавить внешний вызов.

В нашем базовом реестре первая версия не проектируется как универсальный расширяемый framework. Поэтому перед введением переопределяемых методов нужно сформулировать, какие свойства обязаны остаться: ненулевой активный hash, ограничение издателем и сохранение отпечатка при отключении. Простая способность заменить код не является доказательством такой совместимости.

Для наблюдателя переопределение сейчас не нужно. Он имеет один короткий метод и фиксированную ссылку. Это позволяет изучить конструкторы и интерфейс без длинной иерархии, где читатель теряет, в каком файле появилась проверка.

Конструкция Связь Где находится состояние
Библиотечный pure-вызов Повторное использование операции Постоянного состояния нет
Наследование Connection Общая часть программы В экземпляре viewer
Интерфейс catalog Внешний вызов по адресу В отдельном реестре

Мы получили законченный наблюдатель, который наследует настройку и обращается к внешнему источнику. Его ответ удобен для другого контракта, но не заменяет подробную карточку сайта, которой нужен hash и объяснение активности. На следующей странице соединим источник состояния с внешним манифестом выпуска. Наследование Solidity описывает порядок конструкторов и переопределение.

Общий тип не даёт общую администрацию

У CourseVisibility нет права публикации только потому, что он знает адрес реестра. Он вызывает getter, который доступен для чтения. Чтобы изменить курс, другой метод должен обратиться к publish и пройти условие издателя. Передача ссылки на объект не является передачей полномочий.

Это полезно для построения нескольких представлений библиотеки. Один наблюдатель может вычислять видимость, другой — представлять текущий hash, третий — читать отдельную версию. Все они относятся к одному источнику, если настроены на его адрес. Но их экземпляры, кэш и дополнительные поля остаются независимыми.

Если базовый Connection разрешит менять catalog, изменится доверие к каждому производному типу. Ответ visible после переключения может относиться к другому издателю. Поэтому функция назначения адреса потребовала бы отдельного администратора, событий и проверки процедуры. Мы не добавили её, чтобы источник оставался стабильным.

Нужно также понимать, что внешний getter способен откатиться. Наблюдатель не должен молча превращать такой отказ в неактивность курса. Для сайта это две разные причины: реестр сообщил false или источник не дал корректного ответа. Сохранение ошибки помогает обнаружить неверную конфигурацию.

Для самостоятельного случая создайте мысленно два CourseVisibility, подключённых к C1 и C2. У обоих одна программа, но courseId 17 может давать разные ответы. Наследование общего типа не соединяет состояния выбранных источников. Результат зависит от конкретного адреса, переданного в конструктор. Сохраните оба адреса рядом с наблюдениями, чтобы их отличие не потерялось в общей таблице курса.

Оглавление курса. Листинги, расчёты и наблюдения здесь разобраны как ожидаемое поведение; компиляция, учебные программы и проверки при написании не запускались.

Оглавление серии