概要
同じコードのコピペにサヨナラ!オブジェクト指向の核心「継承」を解説します。Ownableのような共通機能を親コントラクトとして分離し、isキーワードでその全機能を子コントラクトに引き継ぐ方法を学習。
virtualとoverrideによる機能拡張も学び、安全で整理されたDApp開発を実現する、プロ必須の設計思想が身につきます。
-

-
Solidity学習講座:最終版 目次(全20話)
基礎編 第1話:未来のインターネットへようこそ!Solidityとスマートコントラクトの全体像 第2話:準備は1分!ブラウザだけで開発できる「Remix IDE」の基本操作 第3話:記念すべき初コント ...
続きを見る
目次
はじめに
第11話では、msg.senderとmsg.valueを駆使し、コントラクトに所有権の概念を導入し、さらにはETHを受け取る機能まで実装しました。特に、constructorとonlyOwner修飾子を使った所有者管理のパターンは、多くのDAppで必要となる、非常に汎用性の高い機能です。
ここで、賢いエンジニアであるあなたは、当然こう考えるはずです。 「この便利な所有者管理のロジック、次に新しいコントラクトを作るときも絶対使うよな…。そのたびに、また同じコードをコピー&ペーストするのか? もし元のコードにバグが見つかったら、コピペした全てのコントラクトを修正して回るのか?」
その通り。コピー&ペーストによる開発は、非効率的で、バグの温床となり、メンテナンス性を著しく低下させます。この課題を解決するのが、今回学ぶ**「継承(Inheritance)」**という、オブジェクト指向プログラミング(OOP)の三大原則の一つです。
継承は、既存のコントラクトを「親」として、その機能や性質をまるごと「子」のコントラクトに引き継がせる仕組みです。これにより、あなたのコードは**DRY(Don't Repeat Yourself - 同じことを繰り返すな)**の原則に従い、再利用可能で、整理され、そして拡張しやすい、美しい構造を手に入れることができるのです。
継承とは? コントラクトの親子関係 👨👧👦
継承とは、あるコントラクト(子コントラクトまたは派生コントラクト)が、別のコントラクト(親コントラクトまたはベースコントラクト)の機能やデータを「受け継ぐ」仕組みです。
子コントラクトは、親コントラクトで定義された状態変数、関数、修飾子を、まるで自分自身で定義したかのように利用できます。
継承がもたらすメリットは絶大です。
- コードの再利用性:
Ownable(所有者管理)のような共通機能を一度だけ書き、様々なコントラクトで再利用することで、開発効率が飛躍的に向上します。 - 整理された構造: 複雑なシステムを、「所有権」「支払い機能」「ユーザー管理」といった、機能ごとの小さな部品(コントラクト)に分割できます。これは、コードを読みやすく、理解しやすく、そしてテストしやすくします。
- 安全な拡張: OpenZeppelinなどが提供する、世界中の専門家によって監査された「安全な親コントラクト」を継承することで、そのセキュリティ機能を自分のコントラクトに簡単に取り入れ、より安全なDAppを構築できます。
Solidityにおける継承の使い方
Solidityで継承を行うのは非常に簡単です。コントラクト名の後ろに**is**キーワードを付けて、継承したい親コントラクト名を記述するだけです。
contract 子コントラクト is 親コントラクト { ... }
実践:Ownableコントラクトを継承する
それでは、第11話で学んだ所有者管理のロジックを、再利用可能なOwnable.solという独立した親コントラクトとして切り出してみましょう。(実際の開発では、このように機能ごとにファイルを分割するのが一般的です。)
Solidity
// ファイル名: Ownable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Ownable: caller is not the owner");
_;
}
function transferOwnership(address _newOwner) public virtual onlyOwner {
require(_newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(owner, _newOwner);
owner = _newOwner;
}
}
(virtualキーワードについては後述します)
次に、このOwnable.solを継承して、新しいMyStoreコントラクトを作ってみましょう。
Solidity
// ファイル名: MyStore.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// Ownable.solファイルをインポートして、Ownableコントラクトを使えるようにする
import "./Ownable.sol";
// "MyStoreは、Ownableである" と宣言
contract MyStore is Ownable {
// これだけで、MyStoreは以下の機能"すべて"を自動的に手に入れました!
// ・address public owner;
// ・constructor()
// ・modifier onlyOwner()
// ・function transferOwnership()
uint public itemPrice = 1 ether;
// 継承したonlyOwner修飾子を早速使ってみる
function setPrice(uint _newPrice) public onlyOwner {
// この関数は、ownerだけが実行できる
itemPrice = _newPrice;
}
function purchase() public payable {
require(msg.value >= itemPrice, "Not enough ETH sent.");
// ... 購入処理 ...
}
}
ここでimport "./Ownable.sol";という新しい構文が登場しました。これは、別のファイルに書かれたコードを読み込むための命令です。
is Ownableと宣言しただけで、MyStoreコントラクトはOwnableの全てのpublicおよびinternalなメンバー(状態変数や関数、修飾子)を引き継ぎます。privateなメンバーは継承されないことに注意してください。これにより、MyStore内では、まるで自分で書いたかのようにonlyOwner修飾子を自然に使うことができるのです。
関数のオーバーライド(上書き)
子コントラクトは、親コントラクトから継承した関数の振る舞いを**変更(オーバーライド)**することもできます。そのためには、2つのキーワードが必要です。
virtual: 親コントラクトの関数に付けます。「この関数は、子コントラクトによって上書きされることを許可しますよ」という意思表示です。override: 子コントラクトの関数に付けます。「この関数は、親の同名関数を意図的に上書きしていますよ」という宣言です。
例えば、所有権を移転する際に、特別なログを追加したい場合を考えてみましょう。
Solidity
// 親の`transferOwnership`をオーバーライド
contract MyStore is Ownable {
event MyStoreOwnerChanged(address newOwner);
// overrideキーワードで、親の関数を上書きすることを明示
function transferOwnership(address _newOwner) public override onlyOwner {
// superを使って、親のオリジナルの処理を呼び出す
super.transferOwnership(_newOwner);
// 子コントラクト独自の処理を追加
emit MyStoreOwnerChanged(_newOwner);
}
}
super.transferOwnership(_newOwner);のように、superキーワードを使うことで、親の元の関数ロジックを呼び出しつつ、子コントラクトで独自の機能を追加できます。これにより、既存の機能を安全に拡張していくことが可能になります。
多重継承
Solidityは、複数の親コントラクトから同時に継承(多重継承)することもサポートしています。
contract MyContract is Ownable, Pausable, ReentrancyGuard { ... }
カンマ(,)で区切って、継承したいコントラクトを並べます。これにより、様々な機能をレゴブロックのように組み合わせて、高機能なコントラクトを効率的に構築できます。ただし、継承の順序が重要になる場合があるため、注意が必要です。コンパイラはC3線形化というルールに従って、矛盾がないかチェックしてくれます。
まとめ:巨人の肩の上に立つ
お疲れ様でした!今回は、オブジェクト指向の核心である「継承」を学び、あなたの開発スキルを新たな次元へと引き上げました。
- 継承は、
isキーワードを使って、親コントラクトの機能とデータを子コントラクトに引き継がせる仕組みである。 - 継承により、コードの再利用性、整理性、安全性が劇的に向上する。
importで外部ファイルを読み込み、モジュール化された開発が可能になる。virtualとoverrideで、継承した関数の振る舞いを安全に拡張・変更できる。
継承をマスターしたあなたは、もはや全てのコードをゼロから書く必要はありません。OpenZeppelinのような信頼できるオープンソースのライブラリを継承し、その堅牢な基盤の上に、あなた独自のロジックを構築していくことができます。まさに「巨人の肩の上に立つ」ように、効率的で安全な開発が可能になるのです。
さて、これまでのレッスンで、あなたのコントラクトはETHを受け取れるようになりました。しかし、受け取るだけでなく、コントラクト自身が能動的にETHを送金するにはどうすればよいでしょうか?
次回、**第13話「お金のやり取りを実装!payable修飾子とEtherの送受信」**では、コントラクトから外部アドレスへETHを送金する3つの方法(transfer, send, call)と、それぞれが持つ重要なセキュリティ上の意味合いについて、詳しく解説していきます。ユーザーの資産を直接扱う、極めて重要なトピックです。
-

-
Solidity学習講座:最終版 目次(全20話)
基礎編 第1話:未来のインターネットへようこそ!Solidityとスマートコントラクトの全体像 第2話:準備は1分!ブラウザだけで開発できる「Remix IDE」の基本操作 第3話:記念すべき初コント ...
続きを見る

