は、現在の問題の例です:
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:この混乱のいずれかを気にせず、消費契約を変更し、あなたが意図していたような共同インターフェイスを使用するだけです。
どのようなものがインタフェースであるかは、エレガントで何ですか?クラスが特定のAPI(またはメソッドのセット)を提供することを保証する。古いコードにインターフェイスを追加したくない場合は、クラスベース(is_a())とインターフェイス(class_implements())を手動で確認できます。http://php.net/manual/en/function.is- a.phpとhttp://php.net/manual/en/function.class-implements.php、これはきれいではないことを覚えておいてください。関数名を変更せずに古いクラスにインターフェイスを実装してから、新しいもの。 – ArtisticPhoenix
こんにちは@ArtisiticPhoenix:それは通常は公正な点ですが、 "古い"階層は私には利用できません(実際にはいくつかのシステムクラスです)。可能であれば、タイプヒントに頼ることはお勧めできませんか?ヒントでは、手動の型チェックが必要でない場合は、「複雑な」複雑さが回避されます(新しいヒント付きインターフェイスが「古い」クラスのコントラクトと一致する場合はそうではありません)。 – feamsr00
はい、それが望ましいですが、私はあなたのインターフェイスchekが古いコードを許可することに失敗することを意味しました。しかし、私はあなたがすでに知っていると思う=) – ArtisticPhoenix