本篇預計閱讀時間 10 分鐘, 實作時間 10 分鐘。
在開始之前先來思考底下的練習題,閱讀後即可練習完成。
練習題
-
練習題 1
-
當宣告
A is X, Y, Z
時,___ 是最末端繼承合約
-
-
練習題 2
-
在 Solidity 中,宣告多重繼承時使用的是 _____ 演算法。
-
-
練習題 3
-
在繼承合約之後,可透過 ____ 來訪問繼承中最源頭的合約。
-
Solidity 中繼承有以下的特性:
-
當進行多重繼承(Multiple Inheritance)使用 C3 Linearization 演算法
-
具有多態性質(Polymorphism)
-
使用
is
關鍵字來做為繼承語法:-
當宣告
A is X, Y, Z
時,Z 是最末端繼承合約 -
可使用
super
來訪問(access)最源頭的合約
-
-
被繼承的合約會被佈署成單一合約,不會佈署多個合約上鏈
C3 Linearization 演算法用於決定多重繼承中的順序,其中包含子合約不可以改變父合約的方法搜尋順序、局部優先原則(A 繼承 B 和 C,C 繼承 B,那麼 A 讀取父合約方法時優先使用 C)。這會強制將繼承轉換為一個有向無環圖的特定順序,同時我們在進行繼承時的宣告順序也很重要(is 後放的合約順序)。
Inheritance
繼承是一個能夠使用其他 Contract 的一種方法。當一個合約從多個合約繼承之後,在區塊鏈上實際佈署(創造)的還是只有一個合約。
Solidity 某種程度上是以「複製」程式碼的行為來繼承合約的。
派生(子)合約:合約在繼承父合約之後,可以訪問父合約中的所有非私有成員,包括父合約內的變數和(可視性為 public 或 internal 的)函式。
//導入 // First import the contract
import B from 'path/to/B.sol';
//繼承//Then make your contract inherit from it
contract A is B {
// 呼叫父合約的建構子//Then call the constructor of the B
contractconstructor() B() {}
}
如果我們同時宣告一樣的函數在不同 contract:
contract B {
function foo() external {...}
}
contract A is B {function foo() external {...}
}
當我們 call 了 foo()
在 A 這個 contract 裡面,則屬於 A 的 A.foo()
會被執行。
但要注意以下這種情況屬於不同的函數:
contract B {
function foo(uint data) external {...}
}
contract A is B {
function foo() external {...}
}
當我們呼叫了 foo(1)
在 A 這個 contract 裡面,則 B.foo()
會被執行,因為只有 B 的 foo(uint)
有宣告 uint。
所以要注意的是所謂的「一模一樣的函數」,是需要連 returns
參數都一模一樣!
Modifier
我們可以使用 modifier
來對一個函數進行補充敘述,或者在滿足 modifier
定義的特定條件後才執行函數的內容。在父合約定義 modifier
後,可以在子合約定義某些函式時進行調用。
此外,modifier
可以有參數也可以沒有參數。
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;
contract Owner {
address owner;
constructor() public {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
modifier costs(uint price) {
if (msg.value >= price) {
_;
}
}
}
contract Register is Owner {
mapping (address => bool) registeredAddresses;
uint price;
constructor(uint initialPrice) public { price = initialPrice; }
function register() public payable costs(price) {
registeredAddresses[msg.sender] = true;
}
function changePrice(uint _price) public onlyOwner {
price = _price;
}
}
modifier
內容最後的 _;
代表的是定義這些敘述為 modifier
。
合約互動
我們可以使用 address()
將 contract
型態轉為 address
型態。
如果今天要使用同一個檔案裏面其他合約的函數,可以使用以下方法:
//SPDX-License-Identifier: MIT
contract A {
function foo() view external returns(uint) {...}
}
contract B {
function callFoo(address addrA) external {
uint result = A(addrA).foo();
}
}
甚至可以在一個合約裡面宣告其他合約。
//SPDX-License-Identifier: MIT
contract A {
constructor(uint a) {...}
function foo() external {...}
}
contract B {
function createA(uint a) external {
A AInstance = new A(a); // 記得傳遞建構子參數 pass constructor argument(s) if any
}
}
Function Overriding
-
virtual
: 當一個父合約的函數、modifier
或狀態變數被宣告virtual
時,表示他在之後繼承的合約中可以被改變(覆寫)。 -
override
: 當一個子合約的函數、modifier
或狀態變數被宣告override
時,表示他正在改變(覆寫)父合約的函數。
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;
contract Base {
function foo() virtual public {}
}
contract Middle is Base {}
contract Inherited is Middle {
function foo() public override {}
}
Function Overriding 有以下規定和重點:
-
在多重繼承的時候,最根本的合約需要特別宣告
override
在函數中。 -
如果這個函數的可視性為
private
則不能是virtual
。 -
在介面中所有函數都會自動被視為 virtual,也就是說在可視性允許的情況下,Solidity 預計未來這些函式將會被實作函式 body(某種程度上的 override)。
-
可以同時 override 兩個父合約的函式,範例如下:
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;
contract Base1 {
function foo() virtual public {}
}
contract Base2 {function foo() virtual public {}
}
contract Inherited is Base1, Base2 {
// Derives from multiple bases defining foo(),
// so we must explicitly override it
function foo() public override(Base1, Base2) {}}
Polymorphism
還記得 Solidity 教學: 函式 Function 時提到的 Function Overloading 以及上述 Function Overriding 嗎?
其實 Polymorphism 在繼承中最主要的兩種形式 Function Polymorphism 和 Contract Polymorphism 便分別指稱了以上兩項內容。
Function Polymorphism 指的是在同一個合約中,宣告相同的函式名稱,藉由不同的參數內容來決定我們呼叫之後,程式碼最終使用的是哪段函式內容,也就是 Function Overloading。
Contract Polymorphism 指的是在不同合約中,宣告相同的函式名稱,藉由繼承之間的定義來決定我們呼叫之後,程式碼最終使用的是哪段函式內容,也就是 FunctionOverriding。
多重繼承與 super
當一個合約從多個合約繼承時,在區塊鏈上只會有一個合約被建立,所有父合約的程式碼都被編譯到我們建立的子合約中。
在多重繼承的情況下 super
這個關鍵字在 Solidity 中可以調用最初的父合約。使用 super
的函數調用優先於大多數派生合約。
當我們擁有一個 contract A 並在其中有一個函數 f()
,並且它的父合約之中也有一個函數 f()
。此時 A 會覆寫(overrides)B 的 f()
。這代表 myInstanceOfA.f()
會呼叫 A 之中的 f()
,且 B 之中的 f()
將不可再被調用,但如果我們想要調用合約 B 的 f()
則可以再 A 之中使用 super.f()
。
或者我們也可以顯式地(explicitly)宣告父合約的函數。
以下是多重繼承的例子:
首先建立一個 c 合約,這個合約定義了一個變數 u。然後 b 合約去繼承 c 合約。這裡就不要定義變量了,使用的時 c 合約的變數。然後 a 合約繼承了 b 合約。
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;
contract C {
uint public u;
function f() public virtual {
u = 1;
}
}
contract B is C {
function f() public virtual override{
u = 2;
}
}
contract A is B {
function f() public override{ // will set u to 3
u = 3;
}
function f1() public {
// will set u to 2
super.f();
}
function f2() public {
// will set u to 2
B.f();
// 使用 super 去呼叫的話,是呼叫 b 合約的,而不是 c 合約的。}
function f3() public {
// will set u to 1
C.f();
// 如果要呼叫 c 合約中的函式,需要使用函式名。
}
}
需要注意的是父合約的 State Variables 不可以在子合約被更改,同時也不可以在子合約宣告在父合約們中已經宣告同樣名稱的 State Variables。
練習題解答
-
練習題解答 1
-
當宣告
A is X, Y, Z
時,Z 是最末端繼承合約
-
-
練習題解答 2
-
在 Solidity 中,宣告多重繼承時使用的是C3線性演算法。
-
-
練習題解答 3
-
在繼承合約之後,可透過 super 來訪問繼承中最源頭的合約。
-