広告

第12話:コードを再利用!「継承 (inheritance)」でスマートコントラクトを賢く拡張する

2025年7月31日

第12話:コードを再利用!「継承 (inheritance)」でスマートコントラクトを賢く拡張する

概要

同じコードのコピペにサヨナラ!オブジェクト指向の核心「継承」を解説します。Ownableのような共通機能を親コントラクトとして分離し、isキーワードでその全機能を子コントラクトに引き継ぐ方法を学習。

virtualとoverrideによる機能拡張も学び、安全で整理されたDApp開発を実現する、プロ必須の設計思想が身につきます。

目次
Solidity学習講座:最終版 目次(全20話)
Solidity学習講座:最終版 目次(全20話)

基礎編 第1話:未来のインターネットへようこそ!Solidityとスマートコントラクトの全体像 第2話:準備は1分!ブラウザだけで開発できる「Remix IDE」の基本操作 第3話:記念すべき初コント ...

続きを見る

はじめに

第11話では、msg.sendermsg.valueを駆使し、コントラクトに所有権の概念を導入し、さらにはETHを受け取る機能まで実装しました。特に、constructoronlyOwner修飾子を使った所有者管理のパターンは、多くのDAppで必要となる、非常に汎用性の高い機能です。

ここで、賢いエンジニアであるあなたは、当然こう考えるはずです。 「この便利な所有者管理のロジック、次に新しいコントラクトを作るときも絶対使うよな…。そのたびに、また同じコードをコピー&ペーストするのか? もし元のコードにバグが見つかったら、コピペした全てのコントラクトを修正して回るのか?」

その通り。コピー&ペーストによる開発は、非効率的で、バグの温床となり、メンテナンス性を著しく低下させます。この課題を解決するのが、今回学ぶ**「継承(Inheritance)」**という、オブジェクト指向プログラミング(OOP)の三大原則の一つです。

継承は、既存のコントラクトを「親」として、その機能や性質をまるごと「子」のコントラクトに引き継がせる仕組みです。これにより、あなたのコードは**DRY(Don't Repeat Yourself - 同じことを繰り返すな)**の原則に従い、再利用可能で、整理され、そして拡張しやすい、美しい構造を手に入れることができるのです。

継承とは? コントラクトの親子関係 👨‍👧‍👦

継承とは、あるコントラクト(子コントラクトまたは派生コントラクト)が、別のコントラクト(親コントラクトまたはベースコントラクト)の機能やデータを「受け継ぐ」仕組みです。

子コントラクトは、親コントラクトで定義された状態変数、関数、修飾子を、まるで自分自身で定義したかのように利用できます。

継承がもたらすメリットは絶大です。

  1. コードの再利用性: Ownable(所有者管理)のような共通機能を一度だけ書き、様々なコントラクトで再利用することで、開発効率が飛躍的に向上します。
  2. 整理された構造: 複雑なシステムを、「所有権」「支払い機能」「ユーザー管理」といった、機能ごとの小さな部品(コントラクト)に分割できます。これは、コードを読みやすく、理解しやすく、そしてテストしやすくします。
  3. 安全な拡張: 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つのキーワードが必要です。

  1. virtual: 親コントラクトの関数に付けます。「この関数は、子コントラクトによって上書きされることを許可しますよ」という意思表示です。
  2. 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で外部ファイルを読み込み、モジュール化された開発が可能になる。
  • virtualoverrideで、継承した関数の振る舞いを安全に拡張・変更できる。

継承をマスターしたあなたは、もはや全てのコードをゼロから書く必要はありません。OpenZeppelinのような信頼できるオープンソースのライブラリを継承し、その堅牢な基盤の上に、あなた独自のロジックを構築していくことができます。まさに「巨人の肩の上に立つ」ように、効率的で安全な開発が可能になるのです。

さて、これまでのレッスンで、あなたのコントラクトはETHを受け取れるようになりました。しかし、受け取るだけでなく、コントラクト自身が能動的にETHを送金するにはどうすればよいでしょうか?

次回、**第13話「お金のやり取りを実装!payable修飾子とEtherの送受信」**では、コントラクトから外部アドレスへETHを送金する3つの方法(transfer, send, call)と、それぞれが持つ重要なセキュリティ上の意味合いについて、詳しく解説していきます。ユーザーの資産を直接扱う、極めて重要なトピックです。

目次
Solidity学習講座:最終版 目次(全20話)
Solidity学習講座:最終版 目次(全20話)

基礎編 第1話:未来のインターネットへようこそ!Solidityとスマートコントラクトの全体像 第2話:準備は1分!ブラウザだけで開発できる「Remix IDE」の基本操作 第3話:記念すべき初コント ...

続きを見る

-Solidity