以太坊智能合约语言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区块链社区技术布道者辉哥
原文发布于简书
如下是咱们的社区介绍,欢迎各类合做、交流、学习:)