
模块化开发是大型合约系统不可或缺的组成部分。本课简单剖析 Solidity 中的继承(Inheritance)、接口(Interface)、抽象合约(Abstract Contract)等关键机制,帮你在合约系统中正确地拆分职责、重用逻辑、规范合约交互,而不是简单复制粘贴。
继承是 Solidity 的核心语言特性之一。它允许你将多个合约组织在一起形成层级结构,子合约可以继承父合约的状态变量、函数、事件和修饰器(modifier)。
contract Parent {
string public name = "parent";
}
contract Child is Parent {
function getName() public view returns (string memory) {
return name;
}
}这里 Child 自动继承了 Parent 中的 name 状态变量。
Solidity 支持显式传参给父合约的构造函数:
contract A {
uint public x;
constructor(uint _x) {
x = _x;
}
}
contract B is A {
constructor() A(42) {}
}contract A {
constructor() { /* A init */ }
}
contract B is A {
constructor() A() { /* B init */ }
}
contract C is A, B {
constructor() A() B() {}
}初始化顺序遵循继承声明顺序,不是构造函数中的调用顺序。
virtual 和 override如果你希望某个函数可以被子合约覆盖(重写),你必须在父合约中标记为 virtual。子合约中实现该函数时必须使用 override。
contract Base {
function foo() public pure virtual returns (string memory) {
return "Base";
}
}
contract Derived is Base {
function foo() public pure override returns (string memory) {
return "Derived";
}
}多重继承时需指定 override 的所有来源:
contract A {
function bar() public pure virtual returns (string memory) {
return "A";
}
}
contract B {
function bar() public pure virtual returns (string memory) {
return "B";
}
}
contract C is A, B {
function bar() public pure override(A, B) returns (string memory) {
return "C";
}
}当合约包含至少一个未实现的函数(即不提供函数体),它就变成了抽象合约。
abstract contract Animal {
function speak() public view virtual returns (string memory);
}抽象合约不能被部署,必须由继承它的合约来实现所有未实现函数:
contract Dog is Animal {
function speak() public pure override returns (string memory) {
return "Woof!";
}
}抽象合约非常适合做模板、接口的默认实现等用途。
接口定义了一组标准的函数签名,用于合约之间通信时的约定。与抽象合约不同的是:
externalinterface IERC20 {
function transfer(address to, uint256 value) external returns (bool);
function balanceOf(address owner) external view returns (uint256);
}使用示例:
IERC20 token = IERC20(tokenAddress);
token.transfer(msg.sender, 1000);场景 | 使用模式 | 理由与优势 |
|---|---|---|
重用通用逻辑或变量 | 继承 | 降低代码重复率,可共用函数、状态等 |
动态组合、插拔式模块(如插件) | 组合 | 解耦更彻底,适合通过部署地址传入外部依赖 |
合约之间通信或依赖 | 接口 | 减少依赖方对实现方的了解程度,便于升级和替换 |
多模块逻辑共用一份代码 | 抽象合约 | 提供默认行为或统一接口逻辑的模板式架构 |
src/Base.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Base {
function value() public pure virtual returns (uint256) {
return 1;
}
}src/Child.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./Base.sol";
contract Child is Base {
function value() public pure override returns (uint256) {
return 2;
}
}test/Inherit.t.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/Child.sol";
contract InheritTest is Test {
function testOverrideValue() public {
Child child = new Child();
assertEq(child.value(), 2);
}
}执行测试:
➜ counter git:(main) ✗ forge test --match-path test/Inherit.t.sol -vvv
[⠊] Compiling...
[⠒] Compiling 3 files with Solc 0.8.30
[⠑] Solc 0.8.30 finished in 506.50ms
Compiler run successful!
Ran 1 test for test/Inherit.t.sol:InheritTest
[PASS] testOverrideValue() (gas: 71422)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.70ms (458.38µs CPU time)
Ran 1 test suite in 215.73ms (1.70ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)Solidity 使用 C3 线性化规则解决多重继承路径冲突,声明顺序决定调用顺序。
src/Inheritance.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract A {
event Log(string msg);
constructor() {
emit Log("A initialized");
}
}
contract B is A {
constructor() {
emit Log("B initialized");
}
}
contract C is A {
constructor() {
emit Log("C initialized");
}
}
contract D is B, C {
constructor() {
emit Log("D initialized");
}
}test/InheritanceTest.t.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
import "../src/Inheritance.sol";
contract InheritanceTest is Test {
function testInitOrder() public {
vm.recordLogs();
D d = new D();
Vm.Log[] memory entries = vm.getRecordedLogs();
for (uint i = 0; i < entries.length; i++) {
emit log(string(entries[i].data));
}
}
}执行结果:
➜ counter git:(main) ✗ forge test --match-path test/InheritanceTest.t.sol -vvv
[⠊] Compiling...
[⠒] Compiling 5 files with Solc 0.8.30
[⠑] Solc 0.8.30 finished in 520.22ms
Compiler run successful with warnings:
Warning (2072): Unused local variable.
--> test/InheritanceTest.t.sol:10:9:
|
10 | D d = new D();
| ^^^
Ran 1 test for test/InheritanceTest.t.sol:InheritanceTest
[PASS] testInitOrder() (gas: 75328)
Logs:
A initialized
B initialized
C initialized
D initialized
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.21ms (760.88µs CPU time)
Ran 1 test suite in 200.50ms (4.21ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)C3 Linearization 是一种算法,用于解决多重继承中的方法解析顺序(MRO,Method Resolution Order),即当一个合约继承了多个父合约时,编译器如何确定哪个父合约的函数/构造函数先执行或使用。
它的目标是生成一个线性、有序、无重复的父类列表,用于:
super.foo() 调用哪一个版本);给定一个继承链,比如:
contract A {}
contract B is A {}
contract C is A {}
contract D is B, C {}计算 D 的线性化顺序 L(D) 的规则如下:
L(D) = [D] + merge(L(B), L(C), [B, C])其中 + 表示连接,merge 是核心算法,它按如下方式合并多个列表(线性化链 + 当前继承顺序):
如果不能找到这样的类,说明继承存在冲突(比如循环继承),编译报错。
contract A {}
contract B is A {}
contract C is A {}
contract D is B, C {}计算线性化顺序如下:
L(A) = [A]
L(B) = [B] + merge(L(A), [A]) = [B, A]
L(C) = [C] + merge(L(A), [A]) = [C, A]L(D) = [D] + merge(L(B), L(C), [B, C])
= [D] + merge([B, A], [C, A], [B, C])使用 merge:
最终:L(D) = [D, B, C, A]
第 9 课:Solidity 事件与日志机制 —— 合约世界的 printf 与事件监听基础
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。