繼承可以減少重複的程式碼,可以把合約看作物件,Solidity 也是支持繼承的物件導向程式語言。Solidity 的繼承包括簡單繼承、多重繼承、修飾器繼承和建構子的繼承。
virtual
和 overide
是繼承的關鍵字。
virtual
:父合約中的函數,如果希望子合約重寫,則需要加上 virtual
關鍵字。overide
:子合約重寫了父合約中的函數,需要加上 override
關鍵字。合約之間簡單繼承的語法:contract <子合約名稱> is <父合約名稱>
,我們可以在子合約中重寫函數,只要加上 override
關鍵字即可。
contract Grandpa{
event Log(string msg);
function f1() public virtual{
emit Log("Grandpa");
}
function f2() public virtual{
emit Log("Grandpa");
}
function grandpa() public virtual{
emit Log("Grandpa");
}
}
contract Father is Grandpa{
function f1() public virtual override{
emit Log("Father");
}
function f2() public virtual override{
emit Log("Father");
}
function father() public virtual{
emit Log("Father");
}
}
分別部署父合約 Grandpa
、子合約 Father
。
部署 Father
子合約後,可以發現 Father
子合約中也有 Grandpa
父合約的函數,也繼承了未重寫的 grandpa()
函數。
若父合約 Grandpa
調用 f1()
函數,可以在 log 中找到日誌對應 Grandpa.f1()
的 log 輸出,若子合約 Father
調用 f1()
函數,因為重寫了 f1()
所以日誌輸出和從 Grandpa
父合約調用是不同的 log 輸出結果。
合約可以繼承多個合約。
Son
合約,繼承 Grandpa
合約和 Father
合約,那麼就要寫成 contract Son is Grandpa, Father
,而不能寫成 contract Son is Father, Grandpa
,不然就會報錯。f1
和 f2
,在子合約裡必須重寫,不然編譯器無法判斷應該繼承哪個函數會報錯。override
關鍵字後面要加上所有父合約名字,例如 override(Grandpa, Father)
。contract Son is Grandpa, Father{
function f1() public virtual override(Grandpa, Father){
emit Log("Son");
}
function f2() public virtual override(Grandpa, Father) {
emit Log("Son");
}
}
Son 合約裡面重寫了 f1()
和 f2()
函數,將輸出改為 "Son",並分別從 Grandpa 和 Father 合約繼承了 grandpa()
和 father()
兩個函數。
修飾器繼承的用法與函數繼承類似,在對應的地方加上 virtual
和 override
關鍵字即可。
contract Base{
modifier exactDividedBy2And3(uint _a) virtual {
require(_a % 2 == 0 && _a % 3 == 0);
_;
}
}
contract Identifier is Base {
function getExactDividedBy2And3(uint _dividend) public exactExactDividedBy2And3(_dividend) pure returns(uint, uint) {
return getExactDividedBy2And3WithoutModifier(_dividend);
}
function getExactDividedBy2And3WithoutModifier(uint _dividend) public pure returns(uint, uint){
uint div2 = _dividend / 2;
uint div3 = _dividend / 3;
return (div2, div3);
}
}
部署 Identifier
合約,getExactDividedBy2And3(uint _divided)
參數輸入 15,交易會被 revert,因為 Identifier
合約繼承了 Base
合約的修飾器 exactDividedBy2And3(_dividend)
,檢查到 15 不能被 2 整除,所以 getExactDividedBy2And3()
函數未成功執行。
getExactDividedBy2And3WithoutModifier() 函數沒有裝飾器限制參數必須被 2 或 3 整除,所以可以看到回傳的兩個參數分別是 7 和 5。
override
關鍵字重寫父合約修飾器。modifier exactDividedBy2And3(uint _a) override {
_;
require(_a % 2 == 0 && _a % 3 == 0);
}
abstract contract A{
uint public a;
constructor(uint _a){
a = _a;
}
}
contract B is A(1)
。contract C is A {
constructor(uint _c) A(_c * _c){}
}
選擇 C
合約部署,給定建構子參數為 10。
因為繼承了 A
合約,所以 A
合約的建構子參數被指定為 10 * 10,將 A
合約的狀態變數 a
修改為 100。
<父合約名>.<函數名>()
呼叫父合約函數。function callParent() public {
Grandpa.f2();
}
super
關鍵字:子合約可以利用 super.<函數名稱>()
來呼叫最近的父合約函數。Solidity 繼承關係依宣告時從右到左的順序是:contract Son is Grandpa, Father
,Father 是最近的父合約,super.f2()
將呼叫 Father.f2()
而不是Grandpa.f2()
。function callParentSuper() public{
super.f2();
}
新增 callParent()
和 callParentSuper()
到 Son
合約並重新部署 Son
合約。
可以觀察到子合約函數可以用合約名稱呼叫繼承的父合約的函數。
利用 super
呼叫的是最近的父合約函數。
在物件導向程式設計中,鑽石繼承(菱形繼承)指一個衍生類別同時有兩個或兩個以上的基底類別。在多重+菱形繼承鏈上使用 super
關鍵字時,需要注意的是使用 super
會呼叫繼承鏈上的每一個合約的相關函數,而不是只呼叫最近的父合約。
我們先寫一個合約 God
,再寫 Adam
和 Eve
兩個合約繼承 God
合約,最後讓創建合約 people
繼承自 Adam
和Eve
,每個合約都有 foo
和bar
兩個函數。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
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");
super.foo();
}
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");
super.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、Adam,最後是 God 合約。雖然 Eve、Adam 都是 God 的子合約,但整個過程中 God 合約只會被呼叫一次。原因是 Solidity 借鑒了 Python 的方式,強制一個由基類構成的 DAG(有向無環圖)使其保證一個特定的順序。