1,摘要
以太坊智能合約語言Solitidy是一種面向?qū)ο蟮恼Z言若厚,本文結(jié)合面向?qū)ο笳Z言的特性拦英,講清楚Solitidy語言的多態(tài)(Polymorphism)(重寫,重載)盹沈,繼承(Inheritance)等特性龄章。
2,合約說明
Solidity 合約類似于面向?qū)ο笳Z言中的類乞封。合約中有用于數(shù)據(jù)持久化的狀態(tài)變量做裙,和可以修改狀態(tài)變量的函數(shù)。 調(diào)用另一個合約實例的函數(shù)時肃晚,會執(zhí)行一個 EVM 函數(shù)調(diào)用锚贱,這個操作會切換執(zhí)行時的上下文,這樣关串,前一個合約的狀態(tài)變量就不能訪問了拧廊。
面向?qū)ο?Object Oriented,OO)語言有3大特性:封裝,繼承晋修,多態(tài)吧碾,Solidity語言也具有著3中特性。
面向?qū)ο笳Z言3大特性的說明解釋如下:
封裝(Encapsulation)
封裝墓卦,就是把客觀事物封裝成抽象的類倦春,并且類可以把自己的數(shù)據(jù)和方法只讓可信的類或者對象操作,對不可信的進(jìn)行信息隱藏落剪。一個類就是一個封裝了數(shù)據(jù)以及操作這些數(shù)據(jù)的代碼的邏輯實體睁本。在一個對象內(nèi)部,某些代碼或某些數(shù)據(jù)可以是私有的忠怖,不能被外界訪問呢堰。通過這種方式,對象對內(nèi)部數(shù)據(jù)提供了不同級別的保護(hù)凡泣,以防止程序中無關(guān)的部分意外的改變或錯誤的使用了對象的私有部分枉疼。繼承(Inheritance)
繼承皮假,指可以讓某個類型的對象獲得另一個類型的對象的屬性的方法。它支持按級分類的概念往衷。繼承是指這樣一種能力:它可以使用現(xiàn)有類的所有功能钞翔,并在無需重新編寫原來的類的情況下對這些功能進(jìn)行擴(kuò)展。 通過繼承創(chuàng)建的新類稱為“子類”或“派生類”席舍,被繼承的類稱為“基類”、“父類”或“超類”哮笆。繼承的過程来颤,就是從一般到特殊的過程。要實現(xiàn)繼承稠肘,可以通過 “繼承”(Inheritance)和“組合”(Composition)來實現(xiàn)福铅。繼承概念的實現(xiàn)方式有二類:實現(xiàn)繼承與接口繼承。實現(xiàn)繼承是指直接使用 基類的屬性和方法而無需額外編碼的能力项阴;接口繼承是指僅使用屬性和方法的名稱滑黔、但是子類必須提供實現(xiàn)的能力。多態(tài)(Polymorphism)
多態(tài)环揽,是指一個類實例的相同方法在不同情形有不同表現(xiàn)形式略荡。多態(tài)機(jī)制使具有不同內(nèi)部結(jié)構(gòu)的對象可以共享相同的外部接口。這意味著歉胶,雖然針對不同對象的具體操作不同汛兜,但通過一個公共的類,它們(那些操作)可以通過相同的方式予以調(diào)用通今。
另外也解釋一下重載和重寫粥谬。
重載(Override)是多態(tài)的一種形式,是一個類的內(nèi)部辫塌,方法中多個參數(shù)漏策,根據(jù)入?yún)⒌膫€數(shù)不同,會返回不同的結(jié)果臼氨。
重寫(Overwrited)掺喻,是子類繼承父類,重寫父類的方法一也。
多態(tài)性是允許你將父對象設(shè)置成為一個或更多的他的子對象相等的技術(shù)巢寡,賦值之后,父對象就可以根據(jù)當(dāng)前賦值給它的子對象的特性以不同的方式運(yùn)作椰苟。簡單的說抑月,就是一句話:允許將子類類型的指針賦值給父類類型的指針。多態(tài)性在Object Pascal和C++中都是通過虛函數(shù)的舆蝴。
3谦絮,函數(shù)重載(Override)
合約可以具有多個不同參數(shù)的同名函數(shù)题诵。這也適用于繼承函數(shù)。以下示例展示了合約 A 中的重載函數(shù) 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;
}
}
重載函數(shù)也存在于外部接口中性锭。如果兩個外部可見函數(shù)僅區(qū)別于 Solidity 內(nèi)的類型而不是它們的外部類型則會導(dǎo)致錯誤。
// 以下代碼無法編譯
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 函數(shù)重載都接受了 ABI 的地址類型叫胖,雖然它們在 Solidity 中被認(rèn)為是不同的草冈。
3.1 重載解析和參數(shù)匹配
通過將當(dāng)前范圍內(nèi)的函數(shù)聲明與函數(shù)調(diào)用中提供的參數(shù)相匹配,可以選擇重載函數(shù)瓮增。 如果所有參數(shù)都可以隱式地轉(zhuǎn)換為預(yù)期類型怎棱,則選擇函數(shù)作為重載候選項。如果一個候選都沒有绷跑,解析失敗拳恋。
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;
}
}
調(diào)用 f(50) 會導(dǎo)致類型錯誤,因為 50 既可以被隱式轉(zhuǎn)換為 uint8 也可以被隱式轉(zhuǎn)換為 uint256砸捏。 另一方面谬运,調(diào)用 f(256) 則會解析為 f(uint256) 重載,因為 256 不能隱式轉(zhuǎn)換為 uint8垦藏。
注解
- 返回參數(shù)不作為重載解析的依據(jù)梆暖。
4,繼承
通過復(fù)制包括多態(tài)的代碼膝藕,Solidity 支持多重繼承式廷。
所有的函數(shù)調(diào)用都是虛擬的,這意味著最遠(yuǎn)的派生函數(shù)會被調(diào)用芭挽,除非明確給出合約名稱滑废。
當(dāng)一個合約從多個合約繼承時,在區(qū)塊鏈上只有一個合約被創(chuàng)建袜爪,所有基類合約的代碼被復(fù)制到創(chuàng)建的合約中蠕趁。
總的來說,Solidity 的繼承系統(tǒng)與 Python的繼承系統(tǒng) 辛馆,非常 相似俺陋,特別是多重繼承方面。
下面的例子進(jìn)行了詳細(xì)的說明昙篙。
pragma solidity ^0.4.16;
contract owned {
function owned() { owner = msg.sender; }
address owner;
}
// 使用 is 從另一個合約派生腊状。派生合約可以訪問所有非私有成員,包括內(nèi)部函數(shù)和狀態(tài)變量苔可,
// 但無法通過 this 來外部訪問缴挖。
contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
}
}
// 這些抽象合約僅用于給編譯器提供接口。
// 注意函數(shù)沒有函數(shù)體焚辅。
// 如果一個合約沒有實現(xiàn)所有函數(shù)映屋,則只能用作接口苟鸯。
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);
}
// 函數(shù)可以被另一個具有相同名稱和相同數(shù)量/類型輸入的函數(shù)重載瘫析。
// 如果重載函數(shù)有不同類型的輸出參數(shù)砌梆,會導(dǎo)致錯誤。
// 本地和基于消息的函數(shù)調(diào)用都會考慮這些重載颁股。
function kill() public {
if (msg.sender == owner) {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).unregister();
// 仍然可以調(diào)用特定的重載函數(shù)么库。
mortal.kill();
}
}
}
// 如果構(gòu)造函數(shù)接受參數(shù),
// 則需要在聲明(合約的構(gòu)造函數(shù))時提供甘有,
// 或在派生合約的構(gòu)造函數(shù)位置以修飾器調(diào)用風(fēng)格提供(見下文)。
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;
}
注意葡缰,在上邊的代碼中亏掀,我們調(diào)用 mortal.kill() 來“轉(zhuǎn)發(fā)”銷毀請求。 這樣做法是有問題的泛释,在下面的例子中可以看到:
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 {
}
調(diào)用 Final.kill() 時會調(diào)用最遠(yuǎn)的派生重載函數(shù) 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 調(diào)用 super 的函數(shù)间影,它不會簡單在其基類合約上調(diào)用該函數(shù)。 相反茄茁,它在最終的繼承關(guān)系圖譜的下一個基類合約中調(diào)用這個函數(shù)魂贬,所以它會調(diào)用 Base1.kill() (注意最終的繼承序列是——從最遠(yuǎn)派生合約開始:Final, Base2, Base1, mortal, ownerd)。 在類中使用 super 調(diào)用的實際函數(shù)在當(dāng)前類的上下文中是未知的裙顽,盡管它的類型是已知的付燥。 這與普通的虛擬方法查找類似。
4.1 基類構(gòu)造函數(shù)的參數(shù)
派生合約需要提供基類構(gòu)造函數(shù)需要的所有參數(shù)愈犹。這可以通過兩種方式來完成:
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 {
}
}
一種方法直接在繼承列表中調(diào)用基類構(gòu)造函數(shù)(is Base(7))键科。 另一種方法是像 修飾器modifier 使用方法一樣, 作為派生合約構(gòu)造函數(shù)定義頭的一部分漩怎,(Base(_y * _y))勋颖。 如果構(gòu)造函數(shù)參數(shù)是常量并且定義或描述了合約的行為,使用第一種方法比較方便勋锤。 如果基類構(gòu)造函數(shù)的參數(shù)依賴于派生合約饭玲,那么必須使用第二種方法。 如果像這個簡單的例子一樣怪得,兩個地方都用到了咱枉,優(yōu)先使用 修飾器modifier 風(fēng)格的參數(shù)卑硫。
4.2 多重繼承與線性化
編程語言實現(xiàn)多重繼承需要解決幾個問題。 一個問題是 鉆石問題蚕断。 Solidity 借鑒了 Python 的方式并且使用“ C3 線性化 ”強(qiáng)制一個由基類構(gòu)成的 DAG(有向無環(huán)圖)保持一個特定的順序欢伏。 這最終反映為我們所希望的唯一化的結(jié)果,但也使某些繼承方式變?yōu)闊o效亿乳。尤其是硝拧,基類在 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聊训,無法解決這種沖突抱究。
可以通過一個簡單的規(guī)則來記憶: 以從“最接近的基類”(most base-like)到“最遠(yuǎn)的繼承”(most derived)的順序來指定所有的基類。
4.3 繼承有相同名字的不同類型成員
當(dāng)繼承導(dǎo)致一個合約具有相同名字的函數(shù)和 修飾器modifier 時带斑,這會被認(rèn)為是一個錯誤鼓寺。 當(dāng)事件和 修飾器modifier 同名,或者函數(shù)和事件同名時勋磕,同樣會被認(rèn)為是一個錯誤妈候。 有一種例外情況,狀態(tài)變量的 getter 可以覆蓋一個 public 函數(shù)挂滓。