# 继承

By [startha](https://paragraph.com/@starha) · 2023-09-27

---

[继承](https://github.com/starthapro/starha_solidity/blob/main/12_Inheritance/readme.md#%E7%BB%A7%E6%89%BF)
---------------------------------------------------------------------------------------------------------

继承是面向对象编程最重要的组成部分，可以显著的减少重复代码。如果把合约当做对象的话，`solidity`也是面向对象的编程，也支持继承。

### [规则](https://github.com/starthapro/starha_solidity/blob/main/12_Inheritance/readme.md#%E8%A7%84%E5%88%99)

*   `virtual`：父合约中的函数，如果希望子合约重写，需要加上`virtual`关键字。
    
*   `override`：子合约重写父合约中的函数，需要加上`override`关键字再。
    

**注意**：用`override`修饰`public`变量，会重写与变量同名的`getter`函数，例如：

        mapping(address => uint256) public override balanceOf;
    

### [简单继承](https://github.com/starthapro/starha_solidity/blob/main/12_Inheritance/readme.md#%E7%AE%80%E5%8D%95%E7%BB%A7%E6%89%BF)

我们先简单写一个爷爷合约`Yeye`，里面包含一个`Log`事件和三个函数，`hip()`，`pop()`，`yeye()`，输出都是`Yeye`。

        contract Yeye {
    
        event Log(string msg);
    
        //定义3个函数：hip()，pop()，man()，Log值为Yeye
        function hip() public virtual {
            emit Log("Yeye");
        }
    
        function pop() public virtual{
            emit Log("Yeye");
        }
    
        function yeye() public virtual {
            emit Log("Yeye");
        }
    
    }
    

我们在定义一个爸爸合约`Baba`，让它继承`Yeye`合约，语法就是`contract Baba is Yeye`，非常直观。在`Baba`合约里，我们重写一下`hip()`和`pop()`这两个函数，加上`override`关键字，并将它们输出改为`Baba`，并且加一个新函数`baba()`，输出也是`Baba`。

        contract Baba is Yeye{
        // 继承两个function: hip()和pop()，输出改为Baba。
        function hip() public virtual override{
            emit Log("Baba");
        }
    
        function pop() public virtual override{
            emit Log("Baba");
        }
    
        function baba() public virtual{
            emit Log("Baba");
        }
    }
    

我们部署合约，可以看到`Baba`合约有4个函数，其中`hip()`和`pop()`的输出被成功改写成`Baba`，而继承来的`yeye()`的输出仍然是`Yeye`。

### [多重继承](https://github.com/starthapro/starha_solidity/blob/main/12_Inheritance/readme.md#%E5%A4%9A%E9%87%8D%E7%BB%A7%E6%89%BF)

solidity的合约可以继承多个合约，规则

1.  继承时按备份最高到最低的顺序排。比如我们写一个`Erzi`合约，继承`Yeye`合约和`Baba`合约，那么就写成`contract Erzi is Yeye, Baba`，而不是写成`contract Erzi is Baba, Yeye`,不然会报错。
    
2.  如果某一个函数在多个继承的合约里存在，比如合约里的`hip()`和`pop()`，在子合约里必须重写，不然会报错。
    
3.  重写在多个父合约都重名的函数时，`override`关键字后面要加上所有父合约名字，例如\`override(Yeye, Baba)。
    

        contract Erzi is Yeye, Baba{
        // 继承两个function: hip()和pop()，输出改为Erzi。
        function hip() public virtual override(Yeye, Baba){
            emit Log("Erzi");
        }
    
        function pop() public virtual override(Yeye, Baba) {
            emit Log("Erzi");
        }
    
        function callParent() public{
            Yeye.pop();
        }
    
        function callParentSuper() public{
            super.pop();
        }
    }
    

我们可以看到，`Erzi`合约里面重写了`hip()`和`pop()`两个函数，并且还分别从`Yeye`和`Baba`合约继承了`yeye()`和`baba()`两个函数。

### [修改器的基础](https://github.com/starthapro/starha_solidity/blob/main/12_Inheritance/readme.md#%E4%BF%AE%E6%94%B9%E5%99%A8%E7%9A%84%E5%9F%BA%E7%A1%80)

solidity中的修饰器（`modifier`）同样可以继承，用法与函数类似，相应的地方加上`override`和`virtual`关键字即可。

        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.4;
    
        contract Base1 {
            modifier exactDividedBy2And3(uint _a) virtual {
                require(_a % 2 == 0 && _a % 3 == 0);
                _;
            }
        }
    
        contract Identifier is Base1 {
    
            //计算一个数分别被2除和被3除的值，但是传入的参数必须是2和3的倍数
            function getExactDividedBy2And3(uint _dividend) public exactDividedBy2And3(_dividend) pure returns(uint, uint) {
                return getExactDividedBy2And3WithoutModifier(_dividend);
            }
    
            //计算一个数分别被2除和被3除的值
            function getExactDividedBy2And3WithoutModifier(uint _dividend) public pure returns(uint, uint){
                uint div2 = _dividend / 2;
                uint div3 = _dividend / 3;
                return (div2, div3);
            }
    
            //重写Modifier: 不重写时，输入9调用getExactDividedBy2And3，会revert，因为无法通过检查
            //删掉下面三行注释重写Modifier，这时候输入9调用getExactDividedBy2And3， 会调用成功
            // modifier exactDividedBy2And3(uint _a) override {
            //     _;
            // }
        }
    

`Identifier`合约可以直接在代码中使用父合约中的`exactDividedBy2And3`修改器，也可以利用`override`关键字重写修改器。

        modifier exactDividedBy2And3(uint _a) override {
            _;
            require(_a % 2 == 0 && _a % 3 == 0);
        }
    

### [构造函数的基础](https://github.com/starthapro/starha_solidity/blob/main/12_Inheritance/readme.md#%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E7%9A%84%E5%9F%BA%E7%A1%80)

子合约有两种方法继承父合约的构造函数。举个简单的例子，父合约`A`里面有一个状态变量`a`，并有构造函数的参数来确定：

        abstract contract A {
        uint public a;
    
        constructor(uint _a) {
            a = _a;
        }
    }
    

1.  在继承时声明父构造函数参数，例如：`contract B is A(1)`。
    
2.  在子合约的构造函数中声明构造函数的参数。
    

        contract B is A(1) {
        }
    
        contract C is A {
            constructor(uint _c) A(_c * _c) {}
        }
    

### [调用父合约的函数](https://github.com/starthapro/starha_solidity/blob/main/12_Inheritance/readme.md#%E8%B0%83%E7%94%A8%E7%88%B6%E5%90%88%E7%BA%A6%E7%9A%84%E5%87%BD%E6%95%B0)

子合约有两种方法调用父合约的函数，直接调用和利用`super`关键字。

1.  直接调用：子合约可以直接用`父合约.函数名`的方式来调用父合约函数，例如`Yeye.pop()`。
    

        function callParent() public{
            Yeye.pop();
        }
    

1.  `super`关键字：子合约可以利用`super.函数名()`来调用最近的父合约函数。solidity继承关系声明时从右到左的顺序是`contract Erzi is Yeye, Baba`，那么`Baba`是最近的父合约，`super.pop()`将调用`Baba.pop()`而不是`Yeye.pop()`。
    

        function callParentSuper() public{
            // 将调用最近的父合约函数，Baba.pop()
            super.pop();
        }
    

### [钻石继承](https://github.com/starthapro/starha_solidity/blob/main/12_Inheritance/readme.md#%E9%92%BB%E7%9F%B3%E7%BB%A7%E6%89%BF)

在面向对象编程中，钻石继承（菱形继承）值一个派生类同时又两个或两个以上的基类。

在多重加菱形继承链条上使用关键字`super`时，需要注意的是使用`super`会调用继承链条上的每一个合约的相关函数，而不是只调用最近的父合约。

我们先写一个`God`合约，再写两个`Adam`和`Eve`两个合约继承`God`合约，最后让创建合约`people`继承`Adam`和`Eve`，每个合约都有`foo`和`bar`两个函数。

        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.13;
    
        contract God {
            event Log(string message);
    
            function foo() public virtual {
                emit Log("God.foo called");
            }
    
            function bar() public virtual {
                emit Log("God.bar called");
            }
        }
    
        contract Adam is God {
            function foo() public virtual override {
                emit Log("Adam.foo called");
            }
    
            function bar() public virtual override {
                emit Log("Adam.bar called");
                super.bar();
            }
        }
    
        contract Eve is God {
            function foo() public virtual override {
                emit Log("Eve.foo called");
                God.foo();
            }
    
            function bar() public virtual override {
                emit Log("Eve.bar called");
                super.bar();
            }
        }
    
        contract people is Adam, Eve {
            function foo() public override(Adam, Eve) {
                super.foo();
            }
    
            function bar() public override(Adam, Eve) {
                super.bar();
            }
        }
    

在这个例子中，调用合约`people`中的`super.bar()`会依次调用`Eve`、`Adm`，最后是`God`合约。

---

*Originally published on [startha](https://paragraph.com/@starha/rn4wx1be1AigFqGSWCv4)*
