10.1 单继承
继承是面向对象编程很重要的组成部分,可以显著减少重复代码。如果把合约看作是对象的话,solidity也是面向对象的编程,也支持继承。
10.1.1 virtual和override
- virtual:在父合约中,如果允许自合约重写该函数,则函数应当加上virtual关键字。
- override:在子合约中,如果要对父合约中的函数进行重写,则应加上override关键字。
10.1.2 例子
contract Animal {
function getName() public pure virtual returns (string memory) {
return "Animal";
}
function walk() public pure virtual returns (string memory) {
return "walk";
}
}
contract Dog is Animal {
function getName() public pure override returns (string memory) {
return "Dog";
}
}
- 上述代码定义了一个父合约:Animal,一个子合约:Dog。Dog合约继承Animal合约,语法为:
contract Dog is Animal {}
。 - 父合约Animal中定义了两个函数:
getName()
和walk()
,并且都用virtual
关键字进行修饰,即允许子合约重写。 - 子合约Dog中,重写了父合约的
getName()
方法。(使用关键字override
修饰)。 - 部署子合约,调用
getName()
方法,返回值为“Dog”,即该方法被重写成功了。 - 对于方法
walk()
,子合约没有进行重写,因此调用的是父合约的walk()
方法,返回值依然为“walk”。
10.2 多线继承
solidity中支持多继承,即一个合约可以继承自多个合约。我们再定义一个Keji合约,继承自Animal和Dog两个合约。
继承结构如下:
// Animal
// / \
// Dog \
// \ /
// \ /
// Keji
示例代码如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract Animal {
function getName() public pure virtual returns (string memory) {
return "Animal";
}
function walk() public pure virtual returns (string memory) {
return "walk";
}
}
contract Dog is Animal {
function getName() public pure override virtual returns (string memory) {
return "Dog";
}
function speak() public pure virtual returns (string memory) {
return "Wang Wang";
}
}
contract Keji is Animal, Dog {
function getName() public pure override(Dog, Animal) returns (string memory) {
return "Keji";
}
}
注意:
- 多继语句为:
contract ContractC is ContractA, ContractB{}
,其中ContractA是比ContractB更上层的合约,顺序不能错,否则编译不通过。本例中,Animal比Dog更上层,因此Keji合约的定义语句为:contract Keji is Animal, Dog{}
,Animal在前,Dog在后。 - 如果一个函数,在多个父合约中均有实现,则子合约必需重回写,否则编译不通过。例如,
getName()
函数必需在Keji合约重重写。 - 重写关键字
override
后要加上重写函数所在的函数名,顺序不做要求:override(Dog, Animal)
。 - 本例中,Keji合约实例有三个方法:
-
getName()
:调用自己的getName()
方法,返回:Keji。 -
speak()
:调用Dog父合约的speak()
方法,返回:Wang Wang。 -
walk()
:调用Animal父合约的walk()
方法,返回:walk。
-
10.3 菱形继承(钻石继承)
我们构造一种继承关系,B和C合约继承A合约,而D合约由继承B和C合约,继承关系如下:
// A
// / \
// B C
// \ /
// D
这种继承机构称为菱形继承或者钻石继承。示例代码如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract A {
event Log(string message);
function foo() public virtual{
emit Log("A.foo");
}
function bar() public virtual{
emit Log("A.bar");
}
}
contract B is A {
function foo() public override virtual {
A.foo();
emit Log("B.foo");
}
function bar() public override virtual {
super.bar();
emit Log("B.bar");
}
}
contract C is A {
function foo() public override virtual {
A.foo();
emit Log("C.foo");
}
function bar() public override virtual {
super.bar();
emit Log("C.bar");
}
}
contract D is B,C {
function foo() public override(B,C) virtual {
B.foo();
emit Log("D.foo");
}
function bar() public override(B,C) virtual {
super.bar();
emit Log("D.bar");
}
}
注意:
- 每个合约中都有
foo()
和bar()
两个函数,子合约在重写函数时都调用了父类的方法。调用父类函数方法如下:- 父合约名.父合约函数。例如在D合约中:
B.foo()
。 - super.父合约函数。例如在D合约中:
super.bar()
。
- 父合约名.父合约函数。例如在D合约中:
- 两种调用父合约函数的结果不一定相同。
B.foo()
只会调用父合约B的对应函数,而super.bar()
会调用所有父合约(B,C)对应的函数。 - D合约中调用了B、C合约的
bar()
方法,B、C合约中的bar()
方法又都调用了A合约中的bar()
方法,但是A合约中的bar()
方法只会被调用一次。原因是Solidity借鉴了Python的方式,强制一个由基类构成的DAG(有向无环图)使其保证一个特定的顺序。更多细节你可以查阅Solidity的官方文档。 - 因此,本例中D合约
foo()
和bar()
输出的事件消息分别如下:
// ***************************** foo() *****************************
[
{
"from": "0xad153c5e12dB58a47f2454691b26582A430803fB",
"topic": "0xcf34ef537ac33ee1ac626ca1587a0a7e8e51561e5514f8cb36afa1c5102b3bab",
"event": "Log",
"args": {
"0": "A.foo",
"message": "A.foo"
}
},
{
"from": "0xad153c5e12dB58a47f2454691b26582A430803fB",
"topic": "0xcf34ef537ac33ee1ac626ca1587a0a7e8e51561e5514f8cb36afa1c5102b3bab",
"event": "Log",
"args": {
"0": "B.foo",
"message": "B.foo"
}
},
{
"from": "0xad153c5e12dB58a47f2454691b26582A430803fB",
"topic": "0xcf34ef537ac33ee1ac626ca1587a0a7e8e51561e5514f8cb36afa1c5102b3bab",
"event": "Log",
"args": {
"0": "D.foo",
"message": "D.foo"
}
}
]
// ***************************** bar() *****************************
[
{
"from": "0xad153c5e12dB58a47f2454691b26582A430803fB",
"topic": "0xcf34ef537ac33ee1ac626ca1587a0a7e8e51561e5514f8cb36afa1c5102b3bab",
"event": "Log",
"args": {
"0": "A.bar",
"message": "A.bar"
}
},
{
"from": "0xad153c5e12dB58a47f2454691b26582A430803fB",
"topic": "0xcf34ef537ac33ee1ac626ca1587a0a7e8e51561e5514f8cb36afa1c5102b3bab",
"event": "Log",
"args": {
"0": "B.bar",
"message": "B.bar"
}
},
{
"from": "0xad153c5e12dB58a47f2454691b26582A430803fB",
"topic": "0xcf34ef537ac33ee1ac626ca1587a0a7e8e51561e5514f8cb36afa1c5102b3bab",
"event": "Log",
"args": {
"0": "C.bar",
"message": "C.bar"
}
},
{
"from": "0xad153c5e12dB58a47f2454691b26582A430803fB",
"topic": "0xcf34ef537ac33ee1ac626ca1587a0a7e8e51561e5514f8cb36afa1c5102b3bab",
"event": "Log",
"args": {
"0": "D.bar",
"message": "D.bar"
}
}
]
10.4 修饰器继承
Solidity中的修饰器(Modifier)同样可以继承,用法与函数继承类似,在相应的地方加virtual和override关键字即可。
contract A {
modifier requireGreater(uint _a) virtual {
require(_a > 5, "Require greater than 5");
_;
}
}
contract B is A {
function double(uint _a) public pure requireGreater(_a) returns (uint){
return _a * 2;
}
}
重写修饰器:
contract C is A {
modifier requireGreater(uint _a) override {
require(_a > 10, "Require greater than 10");
_;
}
function double(uint _a) public pure requireGreater(_a) returns (uint){
return _a * 2;
}
}
10.5 构造函数继承
子合约有两种方法继承父合约的构造函数。举个简单的例子,父合约A里面有一个状态变量a,并由构造函数的参数来确定:
contract A {
uint public a;
constructor(uint _a) {
a = _a;
}
}
方法一:在继承时传入父构造函数的参数
contract B is A(5) {}
方法二:在子合约的构造函数中声明构造函数的参数
contract C is A {
constructor(uint _a) A(_a*2){}
}
网友评论