ホームページ > バックエンド開発 > PHPチュートリアル > サブタイプの多型 - 実行時に実装を交換します

サブタイプの多型 - 実行時に実装を交換します

尊渡假赌尊渡假赌尊渡假赌
リリース: 2025-02-25 18:15:16
オリジナル
607 人が閲覧しました

Subtype Polymorphism - Swapping Implementation at Runtime

コアポイント

    オブジェクト指向設計のサブタイプの多型とは、システムが一連の契約またはインターフェイスを定義し、異なるサブタイプで実装する能力を指します。これは、実装者が予想されるタイプであるかどうかを確認せずに特定の契約を消費できるスケーラブルなシステムを設計するために重要です。
  • この記事では、追加のキャッシュドライバーを開発することでユーザーのニーズに合わせて拡張できる挿入可能なキャッシュコンポーネントを開発することにより、サブタイプの多型の使用を示しています。
  • キャッシュコンポーネントの重要な機能は、クライアントコードを変更せずに実行時に異なるキャッシュドライバーを交換する機能です。これは、キャッシュ契約を定義することで達成され、その後、異なる実装が続くため、多型を利用します。
  • キャッシュコンポーネントは、実行時にバックエンドを切り替えることができ、高度に分離されたモジュールを設計する際の多型の重要性を強調します。これにより、システムの他の部分で脆弱性や厳格な関連の問題を引き起こすことなく、実行時に簡単に再接続できます。
  • サブタイプの多型は、システムをより直交し、拡張しやすくするだけでなく、オープン/クローズド原理や「インターフェイス指向のプログラミング」原理などのコアパラダイムに違反する可能性も低くなります。これは、コードの柔軟性と再利用性を可能にするオブジェクト指向プログラミングの基本的な側面です。
多くの人々は、オブジェクト指向の設計における継承と多型との相関関係を疑うかもしれませんか?おそらく、それらのほとんどは無知または狭い思考によるものかもしれません。しかし、ここでは無視できない小さな問題があります。相続の論理を理解するのは簡単ですが、多型の詳細を掘り下げると物事がより困難になります。 「多型」という用語はそれ自体が困難であり、その学術的な定義は異なる視点に満ちているため、実際にその背後にあるものを理解することがさらに困難になります。パラメーター多型やアドホック多型などの周辺概念(通常、メソッドオーバーライド/オーバーロードによって実装される)は、いくつかのプログラミング言語で重要なアプリケーションを持っていますが、設計では、抽象的な場合は特定の契約(読み取り)を消費することができます。実装者が予想されるタイプであるかどうかを確認せずに放棄する必要があります。要するに、ほとんどの場合、オブジェクト指向プログラミングにおける多型への一般的な言及は、暗黙的に、さまざまな実装が続く一連の契約またはインターフェイスのセットまたはインターフェイスを定義するために使用されるシステムと明示的な機能と暗黙的に考慮されます。この「標準的な」多型は、実際の階層があるかどうかに関係なく、インターフェイスの実装者はそれらのサブタイプであると見なされるため、サブタイプの多型と呼ばれることがよくあります。予想されるように、多型の性質を理解することは、学習プロセスの半分にすぎません。 「コード」(多くの場合、それは玩具コードにとって安価なup曲表現です)。この記事では、挿入可能なキャッシュコンポーネントを開発することにより、多型によって提供される利点を活用する方法を紹介します。コア機能は、追加のキャッシュドライバーを開発することにより、ニーズに合わせて後で拡張できます。

コンポーネントのインターフェイスと実装を定義します

拡張可能なキャッシュコンポーネントを構築するときに選択できるオプションのメニューは決してありません(これについて懐疑的な場合は、いくつかの一般的なフレームワークの背後にあるものを見てください)。ただし、ここでは、私が提供するコンポーネントには、クライアントコードを変更せずに、実行時に異なるキャッシュドライバーを交換する巧妙な能力があります。それで、開発プロセス中に多くの努力なしでこれをどのように行うことができますか?まあ、最初のステップは...はい、後で異なる実装が続く孤立したキャッシュ契約を定義し、それによって多型の利点を利用します。最も基本的なレベルでは、上記の契約は次のとおりです。

<?php namespace LibraryCache;

interface CacheInterface
{
    public function set($id, $data);
    public function get($id);
    public function delete($id);
    public function exists($id);
}
ログイン後にコピー
ログイン後にコピー

インターフェイスは、一般的なキャッシュ要素の動作を抽象化するスケルトン契約です。インターフェイスを使用すると、契約に準拠する特定のキャッシュ実装を簡単に作成できます。私はそれをシンプルで理解しやすいものにしたいので、私が設定したキャッシュドライバーは単なる無駄のないデュオになります:最初のものはファイルシステムをキャッシュの基礎となるバックエンドとして使用します/データを取得します。舞台裏。以下は、ファイルベースのキャッシュの実装です CacheInterface クラスの運転ロジックは理解しやすいはずです。これまでのところ最も関連性の高いことは、初期の

を忠実に達成するため、きちんとした多型の行動を暴露することです。この能力は甘くて魅力的ですが、ここでの目標は、実行時にバックエンドを切り替えることができるキャッシュコンポーネントを作成することであることを考えると、感謝しません。教育目的のために余分な努力を払って、
<?php namespace LibraryCache;

class FileCache implements CacheInterface
{
    const DEFAULT_CACHE_DIRECTORY = 'Cache/';
    private $cacheDir;

    public function __construct($cacheDir = self::DEFAULT_CACHE_DIRECTORY) {
        $this->setCacheDir($cacheDir);
    }

    public function setCacheDir($cacheDir) {
        if (!is_dir($cacheDir)) {
            if (!mkdir($cacheDir, 0644)) {
                throw InvalidArgumentException('The cache directory is invalid.');
            }
        }
        $this->cacheDir = $cacheDir;
        return $this;
    }

    public function set($id, $data) {
        if (!file_put_contents($this->cacheDir . $id, serialize($data), LOCK_EX)) {
            throw new RuntimeException("Unable to cache the data with ID '$id'.");
        }
        return $this;
    }

    public function get($id) {
        if (!$data = unserialize(@file_get_contents($this->cacheDir . $id, false))) {
            throw new RuntimeException("Unable to get the data with ID '$id'.");
        }
        return $data;
    }

    public function delete($id) {
        if (!@unlink($this->cacheDir . $id)) {
            throw new RuntimeException("Unable to delete the data with ID '$id'.");
        }
        return $this;
    }

    public function exists($id) {
        return file_exists($this->cacheDir . $id);
    }
}
ログイン後にコピー
ログイン後にコピー
の別の合理化された実装を実現しましょう。次の実装はインターフェイス契約に準拠していますが、今回はAPCを使用してバンドル方法を拡張することです。

FileCacheCacheInterfaceクラスは、キャリアで見た中で最も見事なAPCラッパーではなく、メモリからデータを保存、取得、削除するために必要なすべての機能を梱包します。私たちは、特定のバックエンドがポリモーフィズムのために実行時に簡単に交換できるだけでなく、将来バックエンドを追加することも非常に簡単な軽量キャッシュモジュールを正常に実装したため、自分自身を称賛しましょう。 CacheInterfaceに準拠した別の実装を書くだけです。ただし、実際のサブタイプの多型は、非常に一般的なアプローチであるインターフェイス構造を通じて定義された契約を実装することで達成されることを強調する必要があります。ただし、抽象的なメソッドのセットとして宣言されたインターフェイスを切り替えることで、正統派の方が少ないことを妨げ、同じ結果を得ることができません。危険を感じ、そのバイパスに行きたい場合は、次のように契約と対応する実装を再構築できます。

<?php namespace LibraryCache;

interface CacheInterface
{
    public function set($id, $data);
    public function get($id);
    public function delete($id);
    public function exists($id);
}
ログイン後にコピー
ログイン後にコピー
<?php namespace LibraryCache;

class FileCache implements CacheInterface
{
    const DEFAULT_CACHE_DIRECTORY = 'Cache/';
    private $cacheDir;

    public function __construct($cacheDir = self::DEFAULT_CACHE_DIRECTORY) {
        $this->setCacheDir($cacheDir);
    }

    public function setCacheDir($cacheDir) {
        if (!is_dir($cacheDir)) {
            if (!mkdir($cacheDir, 0644)) {
                throw InvalidArgumentException('The cache directory is invalid.');
            }
        }
        $this->cacheDir = $cacheDir;
        return $this;
    }

    public function set($id, $data) {
        if (!file_put_contents($this->cacheDir . $id, serialize($data), LOCK_EX)) {
            throw new RuntimeException("Unable to cache the data with ID '$id'.");
        }
        return $this;
    }

    public function get($id) {
        if (!$data = unserialize(@file_get_contents($this->cacheDir . $id, false))) {
            throw new RuntimeException("Unable to get the data with ID '$id'.");
        }
        return $data;
    }

    public function delete($id) {
        if (!@unlink($this->cacheDir . $id)) {
            throw new RuntimeException("Unable to delete the data with ID '$id'.");
        }
        return $this;
    }

    public function exists($id) {
        return file_exists($this->cacheDir . $id);
    }
}
ログイン後にコピー
ログイン後にコピー
<?php namespace LibraryCache;

class ApcCache implements CacheInterface
{
    public function set($id, $data, $lifeTime = 0) {
        if (!apc_store($id, $data, (int) $lifeTime)) {
            throw new RuntimeException("Unable to cache the data with ID '$id'.");
        }
    }

    public function get($id) {
        if (!$data = apc_fetch($id)) {
            throw new RuntimeException("Unable to get the data with ID '$id'.");
        } 
        return $data;
    }

    public function delete($id) {
        if (!apc_delete($id)) {
            throw new RuntimeException("Unable to delete the data with ID '$id'.");
        }
    }

    public function exists($id) {
        return apc_exists($id);
    }
}
ログイン後にコピー

上から下へ、これは実際にはポリ型アプローチであり、以前に説明した方法に反しています。個人的には、これは私の個人的な声明であり、インターフェイスコンストラクトを使用して契約を定義し、いくつかのサブタイプで共有されたボイラープレートの実装をカプセル化する場合にのみ抽象クラスを使用することを好みます。ニーズに最適な方法を選択できます。この時点で、私はカーテンを置き、いくつかの派手なエンディングコメントを書いたり、印象的なコーディングスキルについて自慢したり、キャッシュコンポーネントの柔軟性について自慢したりすることができましたが、それは私たちにとって前かがみになります。複数の実装を使用できるクライアントコードがある場合、これらの実装が予想される契約を満たしている限り、これらの実装が何らかのタイプのインスタンスであるかどうかを確認せずに、多型が最も魅力的な側面を示します。それでは、キャッシュコンポーネントを基本的なクライアントビュークラスに接続することで側面を明らかにしましょう。これにより、きちんとしたHTMLキャッシュを簡単に実行できます。

キャッシュドライバーを使用する

キャッシュモジュールを介した

キャッシュHTML出力は非常に簡単であり、他の時間に長い説明を保存します。キャッシュプロセス全体を、以下と同様に、単純なビュークラスに簡素化できます。

<?php namespace LibraryCache;

abstract class AbstractCache
{
    abstract public function set($id, $data);
    abstract public function get($id);
    abstract public function delete($id);
    abstract public function exists($id);
}
ログイン後にコピー
最も見事な男はクラスコンストラクターであり、
<?php namespace LibraryCache;

class FileCache extends AbstractCache
{
    // the same implementation goes here
}
ログイン後にコピー
メソッドの初期の実装者と

メソッドを使用しています。最後の方法の責任は、ビューのテンプレートが出力バッファーにプッシュされた後にキャッシュすることであるため、この機能を利用してHTMLドキュメント全体をキャッシュすることは良いことです。ビューのデフォルトテンプレートには次の構造があると仮定します。 CacheInterface render()さあ、ビューに

クラスのインスタンスを提供することで、少し楽しくてドキュメントをキャッシュしましょう。
<?php namespace LibraryCache;

class ApcCache extends AbstractCache
{
    // the same implementation goes here 
}
ログイン後にコピー

とても良いですよね?でも待って!私はとても興奮していたので、上記のコードスニペットがAPC拡張機能をインストールしていないシステムで爆発することを忘れていました(Naughty System Administrator!)。これは、慎重に作成されたキャッシュモジュールが再利用できなくなったことを意味しますか?これはまさにファイルベースのドライバーが出てくる場所です。これは、苦情を受け取らずにクライアントコードに入れることができます:ApcCache

<?php namespace LibraryView;

interface ViewInterface
{
    public function setTemplate($template);
    public function __set($field, $value);
    public function __get($field);
    public function render();
}
ログイン後にコピー
上記の単一行のコードは、ビューが共有メモリの代わりにファイルシステムを使用して出力をキャッシュすることを明示的に述べています。この動的スイッチングキャッシュバックエンドは、高度に分離されたモジュールを設計する際に多型が非常に重要である理由を簡単に示しています。これにより、脆弱性/剛性関連のアーティファクトをシステムの他の部分に広めることなく、実行時に物事を簡単に再接続できます。

<?php namespace LibraryView;
use LibraryCacheCacheInterface;

class View implements ViewInterface
{
    const DEFAULT_TEMPLATE = 'default';    
    private $template;
    private $fields = array();
    private $cache;

    public function __construct(CacheInterface $cache, $template = self::DEFAULT_TEMPLATE) {
        $this->cache = $cache;
        $this->setTemplate($template);
    }

    public function setTemplate($template) {
        $template = $template . '.php';
        if (!is_file($template) || !is_readable($template)) {
            throw new InvalidArgumentException(
                "The template '$template' is invalid.");   
        }
        $this->template = $template;
        return $this;
    }

    public function __set($name, $value) {
        $this->fields[$name] = $value;
        return $this;
    }

    public function __get($name) {
        if (!isset($this->fields[$name])) {
            throw new InvalidArgumentException(
                "Unable to get the field '$field'.");
        }
        return $this->fields[$name];
    }

    public function render() {
        try {
            if (!$this->cache->exists($this->template)) {
                extract($this->fields);
                ob_start();
                include $this->template;
                $this->cache->set($this->template, ob_get_clean());
            }
            return $this->cache->get($this->template);
        }
        catch (RuntimeException $e) {
            throw new Exception($e->getMessage());
        } 
    }
}
ログイン後にコピー
結論

多型は実際に人生の良いことの1つであり、一度それを理解すると、そのケースが長く続くことなくどうすればいいのかと思うようになります。多型システムは、本質的により直交的で、縮尺が容易であり、オープン/クローズド原理や賢明な「インターフェイス指向プログラミング」の原則などのコアパラダイムに違反する傾向がありません。むしろ原始的ですが、キャッシュモジュールはこれらの利点の顕著な例です。多型の利点を活用するためにアプリケーションをリファクタリングしていない場合は、ジャックポットを逃したので急いでください! Fotoliaの写真

に関する

FAQ サブタイプの多型とパラメーター多型の主な違いは何ですか?

包含多型とも呼ばれるサブタイプの多型は、公共のスーパークラスに関連付けられている多くの異なるカテゴリの例を表すポリモーフィズムの形式です。一方、パラメーターの多型により、関数またはデータ型がそのタイプに依存することなく同じ方法で値を処理できます。パラメーターの多型は、完全な静的タイプの安全性を維持しながら、言語をより表現力豊かにする方法です。

サブタイプの多型はJavaでどのように機能しますか?

Javaでは、継承とインターフェイスを使用することにより、サブタイプの多型が達成されます。スーパークラス参照変数は、サブクラスオブジェクトを指すことができます。これにより、Javaは実行時にどの方法を呼び出すかを決定することができます。これは動的メソッドスケジューリングと呼ばれます。これは、Javaの強力な特徴の1つであり、動的な多型をサポートできます。

サブタイプの多型の例を提供できますか?

もちろん、Javaの簡単な例を考えてみましょう。 「動物」と呼ばれるスーパークラスと2つのサブクラス「犬」と「猫」があるとします。 「犬」と「猫」のクラスの両方が、「動物」クラスの「サウンド」方法を書き直します。これで、「犬」または「猫」のオブジェクトを指す「動物」参照を作成し、「サウンド」メソッドを呼び出すと、Javaは実行時にどのクラスの「サウンド」メソッドを呼び出すかを決定します。これは、サブタイプの多型の例です。

プログラミングにおけるサブタイプの多型の重要性は何ですか?

サブタイプの多型は、オブジェクト指向プログラミングの基本的な側面です。これにより、コードの柔軟性と再利用性が可能になります。サブタイプの多型を使用して、一連のクラスの共通インターフェイスを設計し、このインターフェイスを使用して、それらのクラスのオブジェクトと統一された方法で対話できます。これにより、コードをよりクリーンで直感的で容易にしやすくなります。

サブタイプの多型とリスコフ置換の原則との関係は何ですか?

リスコフ代替原理(LSP)は、プログラムが基本クラスを使用している場合、プログラムがそれを知らずにサブクラスを使用できるはずであると述べているオブジェクト指向設計の原理です。言い換えれば、スーパークラスのオブジェクトは、プログラムの正しさに影響を与えることなく、サブクラスのオブジェクトに置き換えることができるはずです。サブタイプの多型は、LSPの直接的な応用です。

すべてのプログラミング言語はサブタイプの多型をサポートしていますか?

いいえ、すべてのプログラミング言語がサブタイプの多型をサポートしているわけではありません。これは主に、Java、C、C#などの静的に型付けられたオブジェクト指向プログラミング言語の機能です。 PythonやJavaScriptのような動的に型付けされた言語は、Duckタイプと呼ばれるさまざまな形態の多型を持っています。

静的多型と動的多型の違いは何ですか?

コンパイル時間多型としても知られる静的多型は、メソッドの過負荷によって達成されます。どの方法を呼び出すかについての決定は、コンパイル時に行われます。一方、ランタイム多型としても知られる動的多型は、メソッド書き換えを通じて実装されます。どの方法を呼び出すかについての決定は、実行時に行われます。サブタイプの多型は動的な多型です。

サブタイプの多型におけるアップコンバージョンの概念を説明できますか?

UpConversionは、派生クラスオブジェクトをベースクラスオブジェクトとして扱うプロセスです。これは、サブタイプの多型の重要な側面です。派生クラスオブジェクトをアップコンバートすると、ベースクラスで定義されているメソッドを呼び出すことができます。ただし、メソッドが派生クラスで書き換えられている場合、書き換えバージョンが呼び出されます。

サブタイプの多型のコンテキストでのダウンコンバージョンとは何ですか?

ダウン変換は、アップ変換の反対です。これは、スーパークラスオブジェクトをサブクラスに変換するプロセスです。サブクラスにのみ存在するメソッドにアクセスする必要がある場合、ダウンコンバージョンを使用できます。ただし、変換されているオブジェクトに実際に変換されているタイプがない場合、ClassCastExceptionを引き起こす可能性があるため、ダウンコンバージョンは危険です。

サブタイプの多型は、コードの再利用性をどのように促進しますか?

サブタイプの多型により、より一般的で再利用可能なコードを書くことができます。スーパークラス参照を使用してサブクラスオブジェクトと対話することにより、同じスーパークラスのサブクラスに属している限り、さまざまなオブジェクトのコードを記述できます。これは、スーパークラスを使用するコードを変更せずに新しいサブクラスを追加できることを意味します。これにより、コードがより柔軟でメンテナンスが容易になります。

以上がサブタイプの多型 - 実行時に実装を交換しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート