深入理解Solidity——继承

继承

Solidity通过复制包括多态性的代码来支持多重继承
除非合约是显式给出的,所有的函数调用都是虚拟的,绝大多数派生函数可被调用。
当一个合约继承自多个合约时,只会在区块链上创建单个合约,并将所有父合约中的代码复制到创建的合约中。
Solidity的继承与Python非常相似,特别是多继承。
以下是个例子:

pragma solidity ^0.4.16;

contract owned {
    function owned() { owner = msg.sender; }
    address owner;
}
// 使用`is`继承另一个合约。
// 子合约可以访问所有非私有成员,包括
// 内部函数和状态变量。 
// 不过,不能通过`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`的实例(类似C++中的虚拟继承)。
contract named is owned, mortal {
    function named(bytes32 name) {
        Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
        NameReg(config.lookup(1)).register(name);
    }
    // 函数可以被另一个具有相同名称
    // 和相同数量/类型的输入参数的函数覆盖。
    // 如果重写函数有不同输出参数的类型,会发生错误。
    // 本地和基于消息的函数调用都会将这些覆盖函数考虑在内。
    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() 来“forward” 析构请求。这种做法是有问题的,请看下面的例子:

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 Base1, Base2 {
}

Base1 调用了super函数,它不是简单地调用基本合约之一的函数, 它是调用最后继承关系的下一个基本合约的函数。所以它会调用Base1.kill()(注意,最后的继承顺序是–从最后的派生合约开始:Final, Base1, Base2, mortal, owned)。当使用类的上下文中super不知道的情况下,真正的函数将被调用,虽然它的类型已经知道。这个和普通的virtual方法的查找相似。

父构造函数的参数(Arguments for Base Constructors)

派生的合约需要为父构造函数提供所有的参数。有两种方式:

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)

使用原则:

  • 如果构造函数参数是一个常量,并且定义了合约的行为或描述了它的行为,第一种方式比较方便。
  • 如果父构造函数参数依赖于派生合约的构造函数,则必须使用第二种方法。
  • 如果在这个荒谬的例子中,这两个地方都被使用,修饰符样式的参数优先

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

允许多重继承的编程语言必须处理这几个问题,其中一个是Diamond问题。Solidity是沿用Python的方式, 使用C3线性化,在基类的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(定义AX这个顺序),但A本身的要求重写X,这是一个矛盾,不能解决。

一个简单的规则是要指定父类中的顺序从左到右为“most base-like”到“most derived”。

继承不同类型的同名成员(Inheriting Different Kinds of Members of the Same Name)

When the inheritance results in a contract with a function and a modifier of the same name, it is considered as an error. This error is produced also by an event and a modifier of the same name, and a function and an event of the same name. As an exception, a state variable getter can override a public function.

当继承导致存在有相同名称的函数和修饰符的合约时,会发生错误。 这个错误也会由同名的事件和修饰符以及同名的函数和事件产生的。

例外是状态变量getter可以覆盖public 函数。

上一篇:深入理解Solidity——事件(Events)

下一篇:深入理解Solidity——抽象合约和接口

你可能感兴趣的:(Solidity文档翻译系列,以太坊去中心化应用开发)