title | tags | ||||
---|---|---|---|---|---|
13. Herança |
|
Recentemente, tenho estudado Solidity novamente para revisar os detalhes e escrever um "WTF Solidity Introdução Simples" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão lançadas de 1 a 3 aulas por semana.
Twitter: @0xAA_Science
Comunidade: Discord|Grupo do WeChat|Site oficial wtf.academy
Todo o código e tutoriais estão disponíveis no GitHub: github.com/AmazingAng/WTF-Solidity
Nesta aula, vamos falar sobre herança no Solidity, incluindo herança simples, herança múltipla e herança de modificadores e construtores.
A herança é uma parte importante da programação orientada a objetos, pois permite reduzir significativamente a repetição de código. Se considerarmos os contratos como objetos, o Solidity também é uma linguagem de programação orientada a objetos e suporta herança.
-
virtual
: Se você deseja que uma função do contrato pai possa ser sobrescrita pelo contrato filho, é necessário adicionar a palavra-chavevirtual
à função do contrato pai. -
override
: Se o contrato filho sobrescrever uma função do contrato pai, é necessário adicionar a palavra-chaveoverride
à função do contrato filho.
Observação: Ao usar override
para modificar uma variável pública, a função getter
com o mesmo nome será sobrescrita. Por exemplo:
mapping(address => uint256) public override balanceOf;
Vamos começar escrevendo um contrato avô simples chamado Yeye
, que contém um evento Log
e três funções: hip()
, pop()
e yeye()
, todas elas retornam "Yeye".
contract Yeye {
event Log(string msg);
function hip() public virtual {
emit Log("Yeye");
}
function pop() public virtual {
emit Log("Yeye");
}
function yeye() public virtual {
emit Log("Yeye");
}
}
Agora, vamos definir um contrato pai chamado Baba
, que herda o contrato Yeye
. A sintaxe para isso é contract Baba is Yeye
, que é bastante intuitiva. No contrato Baba
, vamos sobrescrever as funções hip()
e pop()
adicionando a palavra-chave override
e alterando a saída para "Baba". Também adicionaremos uma nova função chamada baba
, que também retorna "Baba".
contract Baba is Yeye {
function hip() public virtual override {
emit Log("Baba");
}
function pop() public virtual override {
emit Log("Baba");
}
function baba() public virtual {
emit Log("Baba");
}
}
Ao implantar o contrato, podemos ver que o contrato Baba
possui quatro funções. As funções hip()
e pop()
foram sobrescritas com sucesso para retornar "Baba", enquanto a função yeye()
herdada ainda retorna "Yeye".
Os contratos no Solidity podem herdar de vários contratos. Aqui estão as regras:
-
A ordem de herança deve ser da classe mais alta para a mais baixa. Por exemplo, se quisermos criar um contrato chamado
Erzi
que herda os contratosYeye
eBaba
, devemos escrevercontract Erzi is Yeye, Baba
e nãocontract Erzi is Baba, Yeye
, caso contrário, ocorrerá um erro. -
Se uma função estiver presente em vários contratos pai, como as funções
hip()
epop()
no exemplo, ela deve ser sobrescrita no contrato filho, caso contrário, ocorrerá um erro. -
Ao sobrescrever uma função que tem o mesmo nome em vários contratos pai, a palavra-chave
override
deve ser seguida pelos nomes de todos os contratos pai, por exemplo,override(Yeye, Baba)
.
Exemplo:
contract Erzi is Yeye, Baba {
function hip() public virtual override(Yeye, Baba) {
emit Log("Erzi");
}
function pop() public virtual override(Yeye, Baba) {
emit Log("Erzi");
}
}
Podemos ver que o contrato Erzi
sobrescreve as funções hip()
e pop()
, alterando a saída para "Erzi" e também herda as funções yeye()
e baba()
dos contratos Yeye
e Baba
, respectivamente.
Os modificadores no Solidity também podem ser herdados. O uso é semelhante à herança de funções, basta adicionar as palavras-chave virtual
e override
nos locais apropriados.
contract Base1 {
modifier exactDividedBy2And3(uint _a) virtual {
require(_a % 2 == 0 && _a % 3 == 0);
_;
}
}
contract Identifier is Base1 {
// Calcula os valores de um número dividido por 2 e por 3, mas o parâmetro deve ser um múltiplo de 2 e 3
function getExactDividedBy2And3(uint _dividend) public exactDividedBy2And3(_dividend) pure returns(uint, uint) {
return getExactDividedBy2And3WithoutModifier(_dividend);
}
// Calcula os valores de um número dividido por 2 e por 3
function getExactDividedBy2And3WithoutModifier(uint _dividend) public pure returns(uint, uint){
uint div2 = _dividend / 2;
uint div3 = _dividend / 3;
return (div2, div3);
}
}
O contrato Identifier
pode usar diretamente o modificador exactDividedBy2And3
do contrato pai no código ou substituí-lo usando a palavra-chave override
:
modifier exactDividedBy2And3(uint _a) override {
_;
require(_a % 2 == 0 && _a % 3 == 0);
}
Existem duas maneiras de um contrato filho herdar o construtor do contrato pai. Vamos dar um exemplo simples: o contrato pai A
possui uma variável de estado a
que é determinada pelos parâmetros do construtor:
// Herança de construtores
abstract contract A {
uint public a;
constructor(uint _a) {
a = _a;
}
}
-
Ao herdar, declaramos os parâmetros do construtor pai, por exemplo:
contract B is A(1)
-
No construtor do contrato filho, declaramos os parâmetros do construtor pai, por exemplo:
contract C is A { constructor(uint _c) A(_c * _c) {} }
Existem duas maneiras de um contrato filho chamar funções do contrato pai: chamada direta e uso da palavra-chave super
.
-
Chamada direta: O contrato filho pode chamar diretamente uma função do contrato pai usando o formato
nomeDoContratoPai.nomeDaFuncao()
, por exemploYeye.pop()
.function callParent() public { Yeye.pop(); }
-
Palavra-chave
super
: O contrato filho pode usar a palavra-chavesuper.nomeDaFuncao()
para chamar a função do contrato pai mais próximo. A ordem de herança no Solidity é da direita para a esquerda na declaração, por exemplo,contract Erzi is Yeye, Baba
, entãoBaba
é o contrato pai mais próximo esuper.pop()
chamaráBaba.pop()
em vez deYeye.pop()
:function callParentSuper() public { // Chamará a função do contrato pai mais próximo, Baba.pop() super.pop(); }
Na programação orientada a objetos, herança de diamante (ou herança em diamante) refere-se a uma classe derivada que tem duas ou mais classes base.
Ao usar a palavra-chave super
em uma cadeia de herança múltipla de diamante, é importante observar que o uso de super
chamará todas as funções relevantes em cada contrato da cadeia de herança, e não apenas a função do contrato pai mais próximo.
Vamos começar escrevendo um contrato chamado God
, em seguida, escreveremos dois contratos Adam
e Eve
que herdam o contrato God
. Por fim, criaremos um contrato chamado people
que herda Adam
e Eve
, e cada contrato terá duas funções: foo
e bar
.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
/* Cadeia de herança:
God
/ \
Adam Eve
\ /
people
*/
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();
}
}
Neste exemplo, chamar super.bar()
no contrato people
chamará as funções Eve
, Adam
e, por fim, God
.
Embora Eve
e Adam
sejam contratos filhos de God
, o contrato God
só será chamado uma vez durante todo o processo. Isso ocorre porque o Solidity adota uma abordagem semelhante ao Python, forçando um DAG (grafo acíclico direcionado) composto por classes base para garantir uma ordem específica. Para obter mais detalhes, consulte a documentação oficial do Solidity.
-
Exemplo de herança simples: observe que o contrato
Baba
possui as funções herdadas deYeye
-
Para o exemplo de herança múltipla, você pode seguir as etapas do exemplo de herança simples para implantar o contrato
Erzi
e observar as funções expostas e tentar chamá-las para verificar os logs. -
Exemplo de herança de modificadores
-
Exemplo de herança de construtores
-
Exemplo de chamada de função do contrato pai
-
Exemplo de herança de diamante
Nesta aula, apresentamos os conceitos básicos da herança no Solidity, incluindo herança simples, herança múltipla, herança de modificadores e construtores, chamada de funções do contrato pai e o problema da herança de diamante na herança múltipla.