Содержание

Наследование в Solidity

Solidity поддерживает множественное наследование путем копирования кода, включая полиморфизм.

Все вызовы функций виртуальны, что означает, что самая производная функция вызываема, за исключением случаев, когда явно указано имя контракта.

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

Общая система наследования очень похожа на ту, что в языке программирования Python, особенно в отношении множественного наследования.

Подробности приведены в следующем примере:

pragma solidity ^0.4.16;

contract owned {
    function owned() { owner = msg.sender; }
    address owner;
}

// Для наследования используется `is`. Наследуемые
// имеют доступ ко всем не private членам, включая
// internal-функции и глобальные переменные. Они не
// не могут быть доступны извне через `this`.
contract mortal is owned {
    function kill() {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

// Эти абстрактные контракты предоставляются только
// для того, чтобы создать интерфейс, известный компилятору.
// Здесь функция без тела. Если контракт не реализует
// все функции, он может использоваться только как интерфейс.
contract Config {
    function lookup(uint id) public returns (address adr);
}

contract NameReg {
    function register(bytes32 name) public;
    function unregister() public;
 }

// Возможно множественное наследование. Контракт `owned`
// является базовым и для `mortal`, но есть только
// единственный эксемпляр `owned`.
contract named is owned, mortal {
    function named(bytes32 name) {
        Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
        NameReg(config.lookup(1)).register(name);
    }

    // Функции могут быть переопределены другой функцией
    // с таким же именем и таким же количеством/типами входных данных.
    // Если функция, которая переопределяет, имеет другие типы
    // выходных параметров, это приведет к ошибке.
    // И локальные, и message-based вызовы функции учитывают
    // эти переопределения.
    function kill() public {
        if (msg.sender == owner) {
            Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
            NameReg(config.lookup(1)).unregister();
            // Все еще можно вызывать конкретную перезаписанную
            // функцию.
            mortal.kill();
        }
    }
}

// Если конструктор принимает аргумент, он должен быть
// предоставлен в заголовке (или в modifier-invocation-style
// в конструкторе наследуемого контракта (как в примере ниже)).
contract PriceFeed is owned, mortal, named("GoldFeed") {
   function updateInfo(uint newInfo) public {
      if (msg.sender == owner) info = newInfo;
   }

   function get() public view returns(uint r) { return info; }

   uint info;
}

В примере выше mortal.kill() вызывается, чтобы отправить запрос на разрушение. То, как это делается, проблематично, как показано в следующем примере:

pragma solidity ^0.4.0;

contract owned {
    function owned() public { owner = msg.sender; }
    address owner;
}

contract mortal is owned {
    function kill() public {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

contract Base1 is mortal {
    function kill() public { /* do cleanup 1 */ mortal.kill(); }
}

contract Base2 is mortal {
    function kill() public { /* do cleanup 2 */ mortal.kill(); }
}

contract Final is Base1, Base2 {
}

Вызов Final.kill() вызовет Base2.kill(), но эта функция обойдет Base1.kill(), в основном потому, что она даже не знает о Base1. Здесь нужно использовать super:

pragma solidity ^0.4.0;

contract owned {
    function owned() public { owner = msg.sender; }
    address owner;
}

contract mortal is owned {
    function kill() public {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

contract Base1 is mortal {
    function kill() public { /* do cleanup 1 */ super.kill(); }
}


contract Base2 is mortal {
    function kill() public { /* do cleanup 2 */ super.kill(); }
}

contract Final is Base2, Base1 {
}

Если Base1 вызывает функцию super, то он не просто вызывает эту функцию на одном из своих базовых контрактов. Скорее, он вызывает эту функцию на следующем базовом контракте в конечном графе наследования. Таким образом он вызовет Base2.kill() (конечная последовательность наследования, начиная с самого производного контракта, — Final, Base1, Base2, mortal, owned). Фактическая функция, вызываемая при использовании super, неизвестна в контексте класса, в котором она используется, хотя ее тип известен. Это похоже на обычный поиск виртуального метода.

Аргументы для базовых конструкторов

Производные контракты должны предоставляться все аргументы, нужные базовым конструкторам. Это можно сделать двумя способами:

pragma solidity ^0.4.0;

contract Base {
    uint x;
    function Base(uint _x) public { x = _x; }
}

contract Derived is Base(7) {
    function Derived(uint _y) Base(_y * _y) public {
    }
}

Первый способ — прямо в список наследования (is Base(7)). Второй — в заголовке конструктора производного контракта (Base(_y * _y)). Первый способ наиболее удобен, если аргумент конструктора является константой и определяет поведение контракта или описывает его. Второй способ нужно использовать, если аргументы конструктора базового зависят от аргументов производного контракта. Если, как в примере выше, используются оба способа, то второй способ имеет приоритет.

Множественное наследование и линеаризация

Языки программирования, которые предоставляют множественное наследование, сталкиваются с несколькими проблемами. Одни из них — проблема алмаза. Solidity следует по пути Python и использует С3-линеаризацию, чтобы форсировать определенный порядок в DAG базовых классов. Это приводит к желаемому свойству монотонности, но запрещает некоторые графы наследования. Главным образом, важен порядок, в котором базовые классы передаются через is. В следующем примере, Solidity выдаст ошибку “Linearization of inheritance graph impossible”.

// Этот код не скомпилируется

pragma solidity ^0.4.0;

contract X {}
contract A is X {}
contract C is A, X {}

Причина в том, что C требует X для переопределения A (задав в порядке A, X), но A сам просит переопределить X, и это вызывает противоречие.

Здесь простое правило: классы нужно перечислять начиная с самого базового и заканчивая самым производным.

Наследование разных членов с одинаковым именем

Когда наследование приводит к контракту с функцией и модификатором с тем же именем, это считается ошибкой. Эта ошибка создается также событием и модификатором с тем же именем, а также функцией и событием с тем же именем. В качестве исключения, геттер глобальной переменной может переопределить public-функцию.

Материал был полезен? Поделитесь в соц. сетях:
Логотип echain.ru

Добавить комментарий

Ваш e-mail не будет опубликован.