继承(Inheritance)

优质
小牛编辑
136浏览
2023-12-01

Solidity通过复制包括多态的代码来支持多重继承。

所有函数调用是虚拟(virtual)的,这意味着最远的派生方式会被调用,除非明确指定了合约。

当一个合约从多个其它合约那里继承,在区块链上仅会创建一个合约,在父合约里的代码会复制来形成继承合约。

基本的继承体系与python有些类似,特别是在处理多继承上面。

下面用一个例子来详细说明:

pragma solidity ^0.4.0;
contract owned {
    function owned() { owner = msg.sender; }
    address owner;
}

// Use "is" to derive from another contract. Derived
// contracts can access all non-private members including
// internal functions and state variables. These cannot be
// accessed externally via `this`, though.
contract mortal is owned {
    function kill() {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

// These abstract contracts are only provided to make the
// interface known to the compiler. Note the function
// without body. If a contract does not implement all
// functions it can only be used as an interface.
contract Config {
    function lookup(uint id) returns (address adr);
}

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

// Multiple inheritance is possible. Note that "owned" is
// also a base class of "mortal", yet there is only a single
// instance of "owned" (as for virtual inheritance in C++).
contract named is owned, mortal {
    function named(bytes32 name) {
        Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970);
        NameReg(config.lookup(1)).register(name);
    }
    // Functions can be overridden by another function with the same name and
    // the same number/types of inputs.  If the overriding function has different
    // types of output parameters, that causes an error.
    // Both local and message-based function calls take these overrides
    // into account.
    function kill() {
        if (msg.sender == owner) {
            Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970);
            NameReg(config.lookup(1)).unregister();
            // It is still possible to call a specific
            // overridden function.
            mortal.kill();
        }
    }
}

// If a constructor takes an argument, it needs to be
// provided in the header (or modifier-invocation-style at
// the constructor of the derived contract (see below)).
contract PriceFeed is owned, mortal, named("GoldFeed") {
   function updateInfo(uint newInfo) {
      if (msg.sender == owner) info = newInfo;
   }
   function get() constant returns(uint r) { return info; }
   uint info;
}

上面的例子的named合约的kill()方法中,我们调用了motal.kill()调用父合约的销毁函数(destruction)。但这样可能什么引发一些小问题。

pragma solidity ^0.4.0;
contract mortal is owned {
    function kill() {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

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

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

contract Final is Base1, Base2 {
}

Final.kill()的调用只会调用Base2.kill(),因为派生重写,会跳过Base1.kill,因为它根本就不知道有Base1。一个变通方法是使用super

pragma solidity ^0.4.0;
contract mortal is owned {
    function kill() {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

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

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

contract Final is Base2, Base1 {
}

如果Base1调用了函数super,它不会简单的调用基类的合约函数,它还会调用继承关系图谱上的下一个基类合约,所以会调用Base2.kill()。需要注意的最终的继承图谱将会是:Final,Base1,Base2,mortal,owned。使用super时会调用的实际函数在使用它的类的上下文中是未知的,尽管它的类型是已知的。这类似于普通虚函数查找(ordinary virtual method lookup)

基类构造器的方法(Arguments for Base Constructors)

派生的合约需要提供所有父合约需要的所有参数,所以用两种方式来做,见下面的例子:

pragma solidity ^0.4.0;
contract Base {
    uint x;
    function Base(uint _x) { x = _x; }
}

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

或者直接在继承列表中使用is Base(7),或像修改器(modifier)使用方式一样,做为派生构造器定义头的一部分Base(_y * _y)。第一种方式对于构造器是常量的情况比较方便,可以大概说明合约的行为。第二种方式适用于构造的参数值由派生合约的指定的情况。在上述两种都用的情况下,第二种方式优先(一般情况只用其中一种方式就好了)。

多继承与线性化(Multiple Inheritance and Linearization)

实现多继承的编程语言需要解决几个问题,其中之一是菱形继承问题又称钻石问题,如下图。

Solidity的解决方案参考Python,使用C3_linearization来强制将基类合约转换一个有向无环图(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的,所以这是一个不可解决的矛盾。

一个简单的指定基类合约的继承顺序原则是从most base-likemost derived

继承有相同名字的不同类型成员

当继承最终导致一个合约同时存在多个相同名字的修改器或函数,它将被视为一个错误。同新的如果事件与修改器重名,或者函数与事件重名都将产生错误。作为一个例外,状态变量的getter可以覆盖一个public的函数。