Solidity的继承中的调用关系与多继承(二十六)|入门系列

2017/6/11 posted in  Solidity入门系列

在继承链中,由于继承实现是代码复制。如果出现函数名重写,最终使用的是继承链上哪个合约定义的代码呢?实际执行时,依据的是最远继承的原则(most derived)1。下面来看一个例子:

pragma solidity ^0.4.0;

contract Base1{
  function data() returns(uint){
    return 1;
  }
}

contract Base2{
  function data() returns(uint){
    return 2;
  }
}

contract MostDerived1 is Base1, Base2{
  function call() returns(uint){
    return data();
  }
}
contract MostDerived2 is Base2, Base1{
  function call() returns(uint){
    return data();
  }
}

上面的例子中,根据最远继承原则,大家可以想想MostDerived1,和MostDerived2中调用call()方法的运行结果。

实际上呢,MostDerived1MostDerived2call()将分别返回21。因为对于MostDerived1的最远继承合约是Base2,所以会使用其对应的函数,对于MostDerived2最远的继承者将是Base1

指定调用父合约方法

虽然存在最远继承原则,但是我们仍可以在子合约中主动调用父合约被重写的方法来触发被覆盖的函数。下面来看一个清理的例子:

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

contract mortal is owned {
    event mortalCalled(string);
    function kill() {
        mortalCalled("mortalCalled");
        if (msg.sender == owner) selfdestruct(owner);
    }
}


contract SpecifyBase is mortal {
    event SpecifyBase(string);
    function kill() {
      SpecifyBase("do own cleanup");
      mortal.kill();
    }
}

SpecifyBase我们打算完成一些特有的清理流程,但想复用父合约的清理流程,可以通过mortal.kill()的方式直接调用父合约的函数。

super关键字

当我们在进行一些清理的时候,有些时候,我们希望继承链条上每一个函数都能被调用以进行一些清理工作,这个时候,我们需要用到super关键字,继续来看一个更多复杂的清理流程的代码:

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

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.kill(),解决方案是使用super

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

contract mortal is owned {
    event mortalCalled(string);
    function kill() {
        mortalCalled("mortalCalled");
        if (msg.sender == owner) selfdestruct(owner);
    }
}


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


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


contract FinalWithSuper is Base1, Base2 {
}

在上面的代码中,我们调用FinalWithSuperkill()方法,将触发按最远继承原则形成的链 FinalBase2Base1motalowned的调用,上述代码运行的事件如下:

Base2Called[
  "Base2Called"
]
Base1Called[
  "Base1Called"
]
mortalCalled[
  "mortalCalled"
]

这样我们就实现了对链条上的所有方法的调用。另外,不知大家是否已经注意到,在使用super的上下文中,实际并不知道最终的调用链是如何的,还与继承的关系有关。

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

在Solidity中,允许多继承,你可以同时继承多个合约。实现多继承的编程语言需要解决几个问题,其中之一是菱形继承问题又称钻石问题,如下图。

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

关于作者

专注基于以太坊(Ethereum)的相关区块链(Blockchain)技术,了解以太坊,Solidity,Truffle,web3.js。

个人博客: http://tryblockchain.org
版权所有,转载注明出处

参考资料

友情链接: 区块链技术中文社区