Solidity 支持面向对象编程的继承特性,允许合约复用代码、建立层次关系。同时,抽象合约和接口提供了定义标准规范的机制。
1. 基础继承
Solidity 使用 is 关键字实现继承,支持多重继承。
solidity// 父合约 contract Animal { string public name; constructor(string memory _name) { name = _name; } function speak() public pure virtual returns (string memory) { return "Some sound"; } } // 子合约继承父合约 contract Dog is Animal { constructor(string memory _name) Animal(_name) {} // 重写父合约函数 function speak() public pure override returns (string memory) { return "Woof!"; } } // 多重继承 contract PetDog is Animal, Pet { constructor(string memory _name) Animal(_name) Pet(_name) {} function speak() public pure override(Animal, Pet) returns (string memory) { return "Friendly Woof!"; } }
2. 构造函数继承
子合约需要显式调用父合约的构造函数。
soliditycontract Parent { uint256 public value; constructor(uint256 _value) { value = _value; } } // 方式 1:在继承列表中传递参数 contract Child1 is Parent(100) { // value 自动设置为 100 } // 方式 2:在子合约构造函数中传递参数 contract Child2 is Parent { constructor(uint256 _value) Parent(_value) { // 可以动态设置 value } } // 方式 3:使用输入参数 contract Child3 is Parent { constructor(uint256 _value) Parent(_value * 2) { // 可以对参数进行处理 } }
3. 函数重写(Override)
使用 virtual 和 override 关键字控制函数重写。
soliditycontract Base { // virtual 允许子合约重写 function getValue() public pure virtual returns (uint256) { return 1; } // 没有 virtual,不能重写 function fixedValue() public pure returns (uint256) { return 100; } } contract Derived is Base { // override 表示重写父合约函数 function getValue() public pure override returns (uint256) { return 2; } // 错误:不能重写没有 virtual 的函数 // function fixedValue() public pure override returns (uint256) { // return 200; // } }
4. 多重继承与线性化
Solidity 使用 C3 线性化算法解决多重继承的冲突。
soliditycontract A { function foo() public pure virtual returns (string memory) { return "A"; } } contract B is A { function foo() public pure virtual override returns (string memory) { return "B"; } } contract C is A { function foo() public pure virtual override returns (string memory) { return "C"; } } // 多重继承:D 继承 B 和 C // 顺序很重要!D is B, C 意味着 B 在 C 前面 contract D is B, C { // 必须重写 foo,因为 B 和 C 都重写了 A 的 foo function foo() public pure override(B, C) returns (string memory) { return super.foo(); // 调用 C.foo()(最右边的基类) } } // 顺序不同,结果不同 contract E is C, B { function foo() public pure override(C, B) returns (string memory) { return super.foo(); // 调用 B.foo() } }
5. 抽象合约(Abstract Contract)
抽象合约是包含至少一个未实现函数的合约,不能直接部署。
solidity// 抽象合约 abstract contract Animal { string public name; constructor(string memory _name) { name = _name; } // 未实现的函数(抽象函数) function speak() public pure virtual returns (string memory); // 已实现的函数 function getName() public view returns (string memory) { return name; } } // 具体合约必须实现所有抽象函数 contract Dog is Animal { constructor(string memory _name) Animal(_name) {} function speak() public pure override returns (string memory) { return "Woof!"; } } // 错误:不能直接部署抽象合约 // Animal animal = new Animal("Test"); // 编译错误!
6. 接口(Interface)
接口是更严格的抽象,只能包含函数声明,不能包含实现和状态变量。
solidity// ERC20 接口示例 interface IERC20 { // 函数声明(没有实现) function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address to, uint256 amount) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool); // 事件 event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); } // 实现接口 contract MyToken is IERC20 { mapping(address => uint256) private _balances; mapping(address => mapping(address => uint256)) private _allowances; uint256 private _totalSupply; function totalSupply() external view override returns (uint256) { return _totalSupply; } function balanceOf(address account) external view override returns (uint256) { return _balances[account]; } function transfer(address to, uint256 amount) external override returns (bool) { // 实现逻辑... return true; } function allowance(address owner, address spender) external view override returns (uint256) { return _allowances[owner][spender]; } function approve(address spender, uint256 amount) external override returns (bool) { // 实现逻辑... return true; } function transferFrom(address from, address to, uint256 amount) external override returns (bool) { // 实现逻辑... return true; } }
7. 抽象合约 vs 接口对比
| 特性 | 抽象合约 (Abstract Contract) | 接口 (Interface) |
|---|---|---|
| 构造函数 | ✅ 可以有 | ❌ 不能有 |
| 状态变量 | ✅ 可以有 | ❌ 不能有 |
| 函数实现 | ✅ 可以有 | ❌ 不能有 |
| 函数可见性 | 任意 | 必须是 external |
| 继承其他合约 | ✅ 可以 | ❌ 不能(只能继承接口) |
| 修饰符 | ✅ 可以有 | ❌ 不能有 |
| 使用场景 | 部分实现的基础合约 | 完全抽象的规范定义 |
8. 实际应用示例
使用抽象合约构建基础功能
solidityabstract contract Ownable { address public owner; constructor() { owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; } } abstract contract Pausable is Ownable { bool public paused; modifier whenNotPaused() { require(!paused, "Paused"); _; } function pause() public onlyOwner { paused = true; } function unpause() public onlyOwner { paused = false; } } contract MyContract is Pausable { function sensitiveOperation() public whenNotPaused { // 只有未暂停时才能执行 } }
使用接口实现标准兼容
solidityinterface IERC721 { function balanceOf(address owner) external view returns (uint256 balance); function ownerOf(uint256 tokenId) external view returns (address owner); function safeTransferFrom(address from, address to, uint256 tokenId) external; function transferFrom(address from, address to, uint256 tokenId) external; function approve(address to, uint256 tokenId) external; function getApproved(uint256 tokenId) external view returns (address operator); function setApprovalForAll(address operator, bool _approved) external; function isApprovedForAll(address owner, address operator) external view returns (bool); } contract NFTMarketplace { // 使用接口与任何 ERC721 合约交互 function buyNFT(address nftContract, uint256 tokenId) public payable { IERC721 nft = IERC721(nftContract); address seller = nft.ownerOf(tokenId); // 验证支付... nft.safeTransferFrom(seller, msg.sender, tokenId); } }
9. 继承的最佳实践
- 使用 OpenZeppelin 标准库:
solidityimport "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract MyToken is ERC20, Ownable { constructor() ERC20("MyToken", "MTK") { _mint(msg.sender, 1000000 * 10 ** decimals()); } }
- 注意继承顺序:
solidity// 正确:基础合约在前 contract Good is Ownable, Pausable, ERC20 {} // 避免:顺序混乱可能导致意外行为 contract Bad is ERC20, Pausable, Ownable {}
- 明确使用 override:
solidityfunction transfer(address to, uint256 amount) public override(ERC20, IERC20) // 明确指定重写的接口 returns (bool) { return super.transfer(to, amount); }
- 合理使用抽象和接口:
- 需要部分实现时用抽象合约
- 定义标准规范时用接口
- 避免过度设计,保持简单