以太坊智能合約語言Solitidy是一種面向對象的語言,本文結合面嚮對象語言的特性,講清楚Solitidy語言的多態(Polymorphism)(重寫,重載),繼承(Inheritance)等特性。編程
Solidity 合約相似於面嚮對象語言中的類。合約中有用於數據持久化的狀態變量,和能夠修改狀態變量的函數。 調用另外一個合約實例的函數時,會執行一個 EVM 函數調用,這個操做會切換執行時的上下文,這樣,前一個合約的狀態變量就不能訪問了。編程語言
面向對象(Object Oriented,OO)語言有3大特性:封裝,繼承,多態,Solidity語言也具備着3中特性。ide
面嚮對象語言3大特性的說明解釋以下:函數
封裝(Encapsulation)學習
封裝,就是把客觀事物封裝成抽象的類,而且類能夠把本身的數據和方法只讓可信的類或者對象操做,對不可信的進行信息隱藏。一個類就是一個封裝了數據以及操做這些數據的代碼的邏輯實體。在一個對象內部,某些代碼或某些數據能夠是私有的,不能被外界訪問。經過這種方式,對象對內部數據提供了不一樣級別的保護,以防止程序中無關的部分意外的改變或錯誤的使用了對象的私有部分。區塊鏈
繼承(Inheritance)ui
繼承,指可讓某個類型的對象得到另外一個類型的對象的屬性的方法。它支持按級分類的概念。繼承是指這樣一種能力:它可使用現有類的全部功能,並在無需從新編寫原來的類的狀況下對這些功能進行擴展。 經過繼承建立的新類稱爲「子類」或「派生類」,被繼承的類稱爲「基類」、「父類」或「超類」。繼承的過程,就是從通常到特殊的過程。要實現繼承,能夠經過 「繼承」(Inheritance)和「組合」(Composition)來實現。繼承概念的實現方式有二類:實現繼承與接口繼承。實現繼承是指直接使用 基類的屬性和方法而無需額外編碼的能力;接口繼承是指僅使用屬性和方法的名稱、可是子類必須提供實現的能力。this
多態(Polymorphism)編碼
多態,是指一個類實例的相同方法在不一樣情形有不一樣表現形式。多態機制使具備不一樣內部結構的對象能夠共享相同的外部接口。這意味着,雖然針對不一樣對象的具體操做不一樣,但經過一個公共的類,它們(那些操做)能夠經過相同的方式予以調用。指針
另外也解釋一下重載和重寫。
重載(Override)是多態的一種形式,是一個類的內部,方法中多個參數,根據入參的個數不一樣,會返回不一樣的結果。
重寫(Overwrited),是子類繼承父類,重寫父類的方法。多態性是容許你將父對象設置成爲一個或更多的他的子對象相等的技術,賦值以後,父對象就能夠根據當前賦值給它的子對象的特性以不一樣的方式運做。簡單的說,就是一句話:容許將子類類型的指針賦值給父類類型的指針。多態性在Object Pascal和C++中都是經過虛函數的。
合約能夠具備多個不一樣參數的同名函數。這也適用於繼承函數。如下示例展現了合約 A 中的重載函數 f。
pragma solidity ^0.4.16; contract A { function f(uint _in) public pure returns (uint out) { out = 1; } function f(uint _in, bytes32 _key) public pure returns (uint out) { out = 2; } }
重載函數也存在於外部接口中。若是兩個外部可見函數僅區別於 Solidity 內的類型而不是它們的外部類型則會致使錯誤。
// 如下代碼沒法編譯 pragma solidity ^0.4.16; contract A { function f(B _in) public pure returns (B out) { out = _in; } function f(address _in) public pure returns (address out) { out = _in; } } contract B { }
以上兩個 f 函數重載都接受了 ABI 的地址類型,雖然它們在 Solidity 中被認爲是不一樣的。
3.1 重載解析和參數匹配
經過將當前範圍內的函數聲明與函數調用中提供的參數相匹配,能夠選擇重載函數。 若是全部參數均可以隱式地轉換爲預期類型,則選擇函數做爲重載候選項。若是一個候選都沒有,解析失敗。
pragma solidity ^0.4.16; contract A { function f(uint8 _in) public pure returns (uint8 out) { out = _in; } function f(uint256 _in) public pure returns (uint256 out) { out = _in; } }
調用 f(50) 會致使類型錯誤,由於 50 既能夠被隱式轉換爲 uint8 也能夠被隱式轉換爲 uint256。 另外一方面,調用 f(256) 則會解析爲 f(uint256) 重載,由於 256 不能隱式轉換爲 uint8。
註解:返回參數不做爲重載解析的依據。
經過複製包括多態的代碼,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(); } } } // 若是構造函數接受參數, // 則須要在聲明(合約的構造函數)時提供, // 或在派生合約的構造函數位置以修飾器調用風格提供(見下文)。 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() 來「轉發」銷燬請求。 這樣作法是有問題的,在下面的例子中能夠看到:
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 { /* 清除操做 1 */ mortal.kill(); } } contract Base2 is mortal { function kill() public { /* 清除操做 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 { /* 清除操做 1 */ super.kill(); } } contract Base2 is mortal { function kill() public { /* 清除操做 2 */ super.kill(); } } contract Final is Base1, Base2 { }
若是 Base2 調用 super 的函數,它不會簡單在其基類合約上調用該函數。 相反,它在最終的繼承關係圖譜的下一個基類合約中調用這個函數,因此它會調用 Base1.kill() (注意最終的繼承序列是——從最遠派生合約開始:Final, Base2, Base1, mortal, ownerd)。 在類中使用 super 調用的實際函數在當前類的上下文中是未知的,儘管它的類型是已知的。 這與普通的虛擬方法查找相似。
4.1 基類構造函數的參數
派生合約須要提供基類構造函數須要的全部參數。這能夠經過兩種方式來完成:
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))。 另外一種方法是像 修飾器modifier 使用方法同樣, 做爲派生合約構造函數定義頭的一部分,(Base(_y * _y))。 若是構造函數參數是常量而且定義或描述了合約的行爲,使用第一種方法比較方便。 若是基類構造函數的參數依賴於派生合約,那麼必須使用第二種方法。 若是像這個簡單的例子同樣,兩個地方都用到了,優先使用 修飾器modifier 風格的參數。
4.2 多重繼承與線性化
編程語言實現多重繼承須要解決幾個問題。 一個問題是 鑽石問題。 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 (由於定義的順序是 A, X ), 可是 A 自己要求重寫 X,沒法解決這種衝突。
能夠經過一個簡單的規則來記憶: 以從「最接近的基類」(most base-like)到「最遠的繼承」(most derived)的順序來指定全部的基類。
4.3 繼承有相同名字的不一樣類型成員
當繼承致使一個合約具備相同名字的函數和 修飾器modifier 時,這會被認爲是一個錯誤。 當事件和 修飾器modifier 同名,或者函數和事件同名時,一樣會被認爲是一個錯誤。 有一種例外狀況,狀態變量的 getter 能夠覆蓋一個 public 函數。
本文做者:HiBlock區塊鏈社區技術佈道者輝哥
原文發佈於簡書
如下是咱們的社區介紹,歡迎各類合做、交流、學習:)