4

PHP 5.x で静的クラスを使用して Adapter パターンを実装する良い方法を探しています。

これを使用したい例の 1 つは、Python のos.path.join(). Windows と Linux のアダプティ クラスの 2 つのアダプティが必要です。

これらのクラスには「コンテキスト」がないため、これらのクラスを静的クラスとして実装することは合理的だと思います。状態を保存する必要はなく、インスタンスが必要になるたびにインスタンスを作成するのは不必要に思えます。したがって、これを実装するクリーンな方法を探しています。

次の偽の実装を考えてみましょう。

    static public function join(){
        $parts = func_get_args();
        $joined = array(MY_MAGICALLY_PASSED_DEFAULT_PATH_PREFIX);
        foreach($parts as $part){
            $part = self::$adaptee->cleanPath($path);
            if(self::$adaptee->isAbsolute($part)){
                $joined = array($part);
            }
            else{
                $joined[] = $part;
            }
        }
        return implode(PATH_SEPARATOR, $joined);
    }

最初に気付くのは、必要な OS 依存の実装の詳細を保持する、adaptee と呼ばれる初期化された静的メンバーを想定していることです。

これには、クラスの宣言の直後に呼び出す任意の名前の静的コンストラクターのような関数が必要です。(このアプローチで気になるもう1つのこと)。

もちろん、$adaptee各メソッド呼び出しでローカル変数を初期化することもできますが、それは不適切なようで、アダプティーを必要とする他の静的関数ごとに複製する必要があります。

さて... PHP のクラス実装の詳細: これらはファーストクラスのオブジェクトではないため、クラスを引数として渡すことはできませんでした。この例では、アダプティーを非静的(これを表す用語は何ですか?) クラスとして作成し、それをインスタンス化し、最終的に$adapteeアダプター クラスの静的メンバー変数に割り当てる必要があります。

たぶん、これは私が持っているこの奇妙で完全に主観的な考えにすぎないのかもしれませんが、このようにするのは適切ではないと本当に感じています。より良い実装についてのアイデアはありますか?

私が持っていたもう1つのアイデアは、代わりにアダプティーのクラス名を保存し、代わりに使用するcall_user_funcことですが、このアプローチを使用するのはあまり快適ではありません。

アップデート

これを適切に説明していない可能性があるため、更新でこれを説明しようとします。

基礎となるオペレーティング システムを取得する方法については調べていませんが、OS が Linux、Windows、FreeBSD、またはその他のいずれであるかによって、静的クラスが異なる動作をするためのきちんとした方法が必要です。

アダプターのパターンを考えたのですが、静的コンストラクターがないため、実際にはクラスを初期化できません。1 つの方法は、すべての public static メソッド呼び出しの開始時に初期化することです (または、初期化されているかどうかを確認するだけです)。

もう 1 つの可能性は、静的コンストラクターのようなメソッドを作成し、宣言の直後に呼び出すことです。それはうまくいくかもしれませんが、これを達成するために、他に、おそらくもっとエレガントな方法があるのではないかと思っています。

私の最初の例については、ユーティリティ関数であると想定されており、実際には状態を保持する必要がないため、どのような種類のパスオブジェクトも探していません。私が望むのは、呼び出されるたびに異なる OS を区別する必要なく、文字列を返す Path ファクトリ関数です。「ライブラリ」のことにより、関連するユーティリティ関数の疑似名前空間として静的クラスを作成し、アダプターパターンでサポートする必要があるさまざまな実装の詳細を作成しました。今、私は2つを組み合わせるエレガントな方法を探しています。

4

2 に答える 2

4

それらを静的にすると、自分の足を撃ちます。静的クラスを注入することはできないため、常にグローバル スコープに結合されます。また、あらゆる場所で静的呼び出しをハードコーディングするため、それらを維持することは悪夢になります。また、それらをモックすることもできません (OK、PHPUnit はできますが、それ以外の場合はテストできないコードのテストを有効にするためだけです)。

インスタンスを作成して通常の関数を使用するだけで、心配する必要がなくなります。静的を使用する利点はありません。また、パフォーマンスへの影響はまったく無視できます。

おそらく、アダプティーとアダプターが実装するためのインターフェースを作成します

interface IPathAdapter
{
    public function cleanPath($path);
    public function isAbsolutePath($part);
    // more …
}

そして、おそらく次のようなことをします

class Path implements IPathAdapter
{
    protected $_adapter;

    public function __construct(IPathAdapter $adapter)
    {
        $this->_adapter = $adapter;
    }

    public function cleanPath($path)
    {
        $this->_adapter->cleanPath($part);
    }

    public function isAbsolutePath($part)
    {
        $this->_adapter->isAbsolutePath($part);
    }

    // more …

    public function join(){
        $parts = func_get_args();
        $joined = array($this->getScriptPath());
        foreach($parts as $part){
            $part = $this->cleanPath($path);
            if ($this->isAbsolutePath($part)){
                $joined = array($part);
            } else{
                $joined[] = $part;
            }
        }
        return implode($this->getPathSeparator(), $joined);
    }
}

したがって、パスを使用する場合は、次のようにする必要があります

$path = new Path(new PathAdapter_Windows);

アダプターを挿入できない場合は、おそらく既に提案されているルートに進み、アダプタークラス名を引数として渡して、パス内からインスタンス化します。または、適切なアダプターの検出を Path クラスに完全に任せます。たとえば、OS を検出してから、必要なものをインスタンス化します。

自動検出したい場合は、Does a PHP has a function to detect the OS it's running on? をご覧ください。. おそらく、識別を処理する別のクラスを作成し、それを Path クラスへの依存関係にするでしょう。

public function __construct(IDetector $detector = NULL)
{
    if($detector === NULL){
        $detector = new OSDetector;
    }
    $this->_detector = $detector; 
}

注入する理由は、実装を変更できるようにするためです。たとえば、UnitTests で検出器をモックするだけでなく、実行時に注入することを無視することもできます。その場合、デフォルトの OSDetector が使用されます。ディテクタを使用して、OS を検出し、Path または専用の Factory のどこかに適切なアダプタを作成します。

于 2010-12-16T14:38:55.490 に答える
0

これを行うことができると思います。たとえば、composer autoload.php のように、名前空間パスをグローバル var に入れるだけです。

$GLOBALS['ADAPTED_CLASS_NAMESPACE'] = 'MyComponent\AdapterFoo\VendorBar';

依存性注入を使用できないコンテキスト、つまり検証用のエンティティでは、これは良いアプローチだと思います (分離された検証クラスの方が優れていることに留意してください)。

<?php

namespace MyComponent;

use MyComponent\AdaptedInterface;
use ReflectionClass;

class Adapter
{
    /**
     * @var AdaptedInterface
     */
    protected $adaptedClass;

    public function __construct(AdaptedInterface $validator = null)
    {
        if (null == $validator && $this->validateClassPath($GLOBALS['ADAPTED_CLASS_NAMESPACE'])) {
            $this->adaptedClass = new $GLOBALS['ADAPTED_CLASS_NAMESPACE'];
        } else {
            $this->adaptedClass = $validator;
        }
    }

    protected function validateClassPath($classPath)
    {
        $reflection = new ReflectionClass($classPath);

        if (!$reflection->implementsInterface(
            'MyComponent\AdaptedInterface'
        )) {
            throw new \Exception('Your adapted class have not a valid class path :' . $classPath . 'given');
        }

        return true;
    }
}

だからどこでも:

(new Adapter())->foo($bar);
于 2016-01-08T14:59:20.900 に答える