2016-07-05 6 views
2

したがって、私は変更できない既存のクラス階層を持っています。その階層のクラスの既存のコンシューマは、自分のコードベースだけではありません。私は、類似しているが機能が改善された異なる契約(クラスプロトタイプ)を持つ別のクラスを(新しい、しかし外部のライブラリに)持っている。古いコードの既存のコンシューマーに新しい機能を提供したいと思います。具体的なクラス階層にインターフェイスを挿入する方法

class OldBase {} 

class OldSubClass extends OldBase{} 

class NewCode {} 

//consumers 
existingMethod(OldSubClass $c) {} 
alsoExistingMethod(OldBase $c) {} 

洗練、おそらく、私がAdapterInterfaceを使用するのではと思ったが、これはそうです。

interface NewCodeAdapterInterface 
{ 
    //interface that mimics the contract from OldBase 
} 
class NewCodeImplementation implements NewCodeAdapterInterface{} 

//now this code can not be used with any existing OldBase objects :-\ 
existingMethod(NewCodeAdapterInterface $c) {} 

私はクリーンな方法は、可能な限り少数の波及効果を持つ新しいを使用することができますが、どのようにしながら、古いコードを使用することができるようにする後方互換性のある方法を確保したいのですが?

+0

どのようなものがインタフェースであるかは、エレガントで何ですか?クラスが特定のAPI(またはメソッドのセット)を提供することを保証する。古いコードにインターフェイスを追加したくない場合は、クラスベース(is_a())とインターフェイス(class_implements())を手動で確認できます。http://php.net/manual/en/function.is- a.phpとhttp://php.net/manual/en/function.class-implements.php、これはきれいではないことを覚えておいてください。関数名を変更せずに古いクラスにインターフェイスを実装してから、新しいもの。 – ArtisticPhoenix

+0

こんにちは@ArtisiticPhoenix:それは通常は公正な点ですが、 "古い"階層は私には利用できません(実際にはいくつかのシステムクラスです)。可能であれば、タイプヒントに頼ることはお勧めできませんか?ヒントでは、手動の型チェックが必要でない場合は、「複雑な」複雑さが回避されます(新しいヒント付きインターフェイスが「古い」クラスのコントラクトと一致する場合はそうではありません)。 – feamsr00

+0

はい、それが望ましいですが、私はあなたのインターフェイスchekが古いコードを許可することに失敗することを意味しました。しかし、私はあなたがすでに知っていると思う=) – ArtisticPhoenix

答えて

0

既存の消費者を変更することなく、既存のコード、で消費される異種のクラスの統一交換を実装する前提で開始し、その後、私は...「ソリューション」を持っています。ここで

は、現在の問題の例です:

class A 
{ 
    public function test() 
    { 
     echo "A\n"; 
    } 
} 

class B 
{ 
    public function test() 
    { 
     echo "B\n"; 
    } 
} 

class Consumer 
{ 
    public function runTestA(A $a) 
    { 
     $a->test(); 
    } 

    public function runTestB(B $b) 
    { 
     $b->test(); 
    } 
} 

$con = new Consumer(); 

$a = new A(); 
$b = new B(); 

$con->runTestA($a); 
$con->runTestB($b); 

あなたは消費者には何も変更せずに、のようなものができるようになります解決策を見つけるためにしようとしている:

$con = new Consumer(); 

$c = new C(); 

$con->runTestA($c); 
$con->runTestB($c); 

私は行きますよ私が概説しようとしていることをすることに重くアドバイスする。共同機能を持つ新しいクラスを渡すことができるように、Consumerのメソッドシグネチャを変更する方がよいでしょう。しかし、私は尋ねられたように質問に答えるつもりです...

まず、既存のメソッドシグネチャを渡すことができるいくつかのクラスが必要です。私は、共同機能を定義するために特性を使用します。

trait ExtensionTrait 
{ 
    public function test() 
    { 
     echo "New Functionality\n"; 
    } 
} 

class ExtendedA extends A 
{ 
    use ExtensionTrait; 
} 

class ExtendedB extends B 
{ 
    use ExtensionTrait; 
} 

は今、我々は正しいものを渡すと...メソッドのチェックを渡すことができる新機能、といくつかのクラスを持っています。では、どうすればいいのですか?

まず、2つのクラスを簡単に切り替えることができるクイックユーティリティクラスを作成しましょう。

class ModeSwitcher 
{ 
    private $a; 
    private $b; 
    public $mode; 

    public function __construct($a, $b) 
    { 
     $this->a = $a; 
     $this->b = $b; 
     $this->mode = $this->a; 
    } 

    public function switchMode() 
    { 
     if ($this->mode instanceof ExtendedA) 
     { 
      $this->mode = $this->b; 
     } 
     elseif ($this->mode instanceof ExtendedB) 
     { 
      $this->mode = $this->a; 
     } 
    } 

    public function __set($name, $value) 
    { 
     $this->a->$name = $value; 
     $this->b->$name = $value; 
    } 

    public function __isset($name) 
    { 
     return isset($this->mode->$name); 
    } 

    public function __unset($name) 
    { 
     unset($this->a->$name); 
     unset($this->b->$name); 
    } 

    public function __call($meth, $args) 
    { 
     return call_user_func_array([$this->mode, $meth], $args); 
    } 

} 

このモードスイッチャクラスは、取得と呼び出しを通過する現在のモードクラスを維持します。セットとセット解除は両方のクラスに適用されるため、変更されたプロパティはモード切り替え時に失われません。

ここで、コンシューマのコンシューマを変更することができれば、モードを自動的に切り替えて正しいモードを見つける変換レイヤーを組み込むことができます。

$con = new Consumer(); 
$t = new ConsumerTranslator($con); 
$a = new ExtendedA(); 
$b = new ExtendedB(); 
$m = new ModeSwitcher($a, $b); 

$t->runTestA($m); 
$t->runTestB($m); 

これは、あなたは、交換可能に一切消費者のいかなる修正もしなくてもクラスツリーを利用、またの使用プロファイルに大きな変更することができます:

class ConsumerTranslator 
{ 
    private $consumer; 

    public function __construct(Consumer $consumer) 
    { 
     $this->consumer = $consumer; 
    } 

    public function __get($name) 
    { 
     return $this->consumer->$name; 
    } 

    public function __set($name, $value) 
    { 
     $this->consumer->$name = $value; 
    } 

    public function __isset($name) 
    { 
     return isset($this->consumer->$name); 
    } 

    public function __unset($name) 
    { 
     unset($this->consumer->$name); 
    } 

    public function __call($methName, $arguments) 
    { 
     try 
     { 
      $tempArgs = $arguments; 
      foreach ($tempArgs as $i => $arg) 
      { 
       if ($arg instanceof ModeSwitcher) 
       { 
        $tempArgs[$i] = $arg->mode; 
       } 
      } 
      return call_user_func_array([$this->consumer, $methName], $tempArgs); 
     } 
     catch (\TypeError $e) 
     { 
      $tempArgs = $arguments; 
      foreach ($tempArgs as $i => $arg) 
      { 
       if ($arg instanceof ModeSwitcher) 
       { 
        $arg->switchMode(); 
        $tempArgs[$i] = $arg->mode; 
       } 
      } 
      return call_user_func_array([$this->consumer, $methName], $tempArgs); 
     } 
    } 
} 

その後、我々はそうのような組み合わせの機能を使用することができますコンシューマ、トランスレータは基本的にパススルーラッパーです。

シグネチャのミスマッチによってスローされたTypeErrorをキャッチし、ペアになったクラスに切り替えてもう一度試行します。

実際に実装することは推奨されません。宣言された制約は興味深いパズルを提供しましたが、ここではそうです。

TL; DR:この混乱のいずれかを気にせず、消費契約を変更し、あなたが意図していたような共同インターフェイスを使用するだけです。

関連する問題