最近 PHP を勉強しようとしていますが、特性で行き詰まっています。水平方向のコード再利用の概念と、必ずしも抽象クラスから継承したくないことは理解しています。理解できないのは、特性とインターフェースの使用の決定的な違いは何ですか?
どちらを使用するべきかを説明する適切なブログ投稿や記事を探してみましたが、これまでに見つけた例は非常に似ていて、まったく同じもののように思えます。
ベストアンサー1
公共サービスの案内:
記録のために述べておきたいのは、特性はほとんどの場合コード臭なので、避けてコンポジションを使うべきだと私は考えているということです。単一継承は頻繁に乱用されてアンチパターンになり、多重継承はこの問題をさらに悪化させるだけだというのが私の意見です。ほとんどの場合、継承 (単一継承でも多重継承でも) よりもコンポジションを優先したほうがずっと良い結果が得られます。特性とインターフェイスとの関係にまだ興味がある場合は、読み進めてください...
まず、次のことを言ってみましょう。
オブジェクト指向プログラミング (OOP) は、理解するのが難しいパラダイムです。クラスを使用しているからといって、コードがオブジェクト指向 (OO) であるとは限りません。
OO コードを書くには、OOP が実際にはオブジェクトの機能に関するものであることを理解する必要があります。クラスについては、実際に何を行うかではなく、何ができるかという観点から考える必要があります。これは、コードの一部に「何かを行う」ことに重点が置かれている従来の手続き型プログラミングとはまったく対照的です。
OOP コードが計画と設計に関するものであるとすると、インターフェースは青写真であり、オブジェクトは完全に構築された家です。一方、特性は単に青写真 (インターフェース) によってレイアウトされた家を建てるのに役立つ手段です。
インターフェース
では、なぜインターフェースを使うべきなのでしょうか? 簡単に言えば、インターフェースを使うとコードの脆弱性が軽減されるからです。この主張に疑問を感じるなら、インターフェースを無視して書かれたレガシー コードを保守せざるを得なかった人に聞いてみてください。
インターフェースは、プログラマーとそのコードの間の契約です。インターフェースは、「私のルールに従っている限り、好きなように実装できます。他のコードを壊さないことを約束します」と言っています。
例として、現実世界のシナリオ(車やウィジェットなし)を考えてみましょう。
サーバーの負荷を軽減するために、Webアプリケーションにキャッシュシステムを実装したい
まず、APC を使用してリクエスト応答をキャッシュするクラスを作成します。
class ApcCacher
{
public function fetch($key) {
return apc_fetch($key);
}
public function store($key, $data) {
return apc_store($key, $data);
}
public function delete($key) {
return apc_delete($key);
}
}
次に、HTTP 応答オブジェクトで、実際の応答を生成するためのすべての作業を実行する前に、キャッシュ ヒットをチェックします。
class Controller
{
protected $req;
protected $resp;
protected $cacher;
public function __construct(Request $req, Response $resp, ApcCacher $cacher=NULL) {
$this->req = $req;
$this->resp = $resp;
$this->cacher = $cacher;
$this->buildResponse();
}
public function buildResponse() {
if (NULL !== $this->cacher && $response = $this->cacher->fetch($this->req->uri()) {
$this->resp = $response;
} else {
// Build the response manually
}
}
public function getResponse() {
return $this->resp;
}
}
このアプローチはうまく機能します。しかし、数週間後に、APC ではなくファイルベースのキャッシュ システムを使用することに決めたとします。クラスの機能を表現するインターフェイスではなく、クラスの機能と連携するようにコントローラーをプログラムしたため、コントローラー コードを変更する必要があります。上記の代わりに、次のように、クラスApcCacher
が具体的な ではなくに依存するようにApcCacher
したとします。Controller
CacherInterface
ApcCacher
// Your controller's constructor using the interface as a dependency
public function __construct(Request $req, Response $resp, CacherInterface $cacher=NULL)
それに合わせて、インターフェースを次のように定義します。
interface CacherInterface
{
public function fetch($key);
public function store($key, $data);
public function delete($key);
}
次に、現在のクラスApcCacher
と新しいFileCacher
クラスの両方に を実装し、インターフェイスに必要な機能を使用するようにクラスCacherInterface
をプログラムします。Controller
この例では、インターフェイスへのプログラミングによって、変更によって他のコードが壊れるかどうかを心配せずに、クラスの内部実装を変更できることを示しています (うまくいけば)。
特性
一方、特性は単にコードを再利用するための方法です。インターフェースは、特性と相互に排他的な代替手段と考えるべきではありません。実際、インターフェースに必要な機能を満たす特性を作成することが理想的な使用例です。
複数のクラスが同じ機能を共有する場合 (おそらく同じインターフェースによって決定される) にのみ、特性を使用する必要があります。単一のクラスに機能を提供するために特性を使用するのは意味がありません。クラスの機能がわかりにくくなるだけであり、より優れた設計では、特性の機能を関連するクラスに移動します。
次の特性の実装を検討してください。
interface Person
{
public function greet();
public function eat($food);
}
trait EatingTrait
{
public function eat($food)
{
$this->putInMouth($food);
}
private function putInMouth($food)
{
// Digest delicious food
}
}
class NicePerson implements Person
{
use EatingTrait;
public function greet()
{
echo 'Good day, good sir!';
}
}
class MeanPerson implements Person
{
use EatingTrait;
public function greet()
{
echo 'Your mother was a hamster!';
}
}
より具体的な例: インターフェースの説明にある、あなたFileCacher
とあなたの両方ApcCacher
が同じメソッドを使用して、キャッシュ エントリが古くて削除する必要があるかどうかを判断していると想像してください (もちろん、現実にはそうではありませんが、そのままにしておきます)。共通のインターフェース要件のために、特性を記述して、両方のクラスがそれを使用できるようにすることができます。
最後に注意点を 1 つ。特性を使いすぎないように注意してください。多くの場合、特性は、固有のクラス実装で十分な場合に、貧弱な設計の言い訳として使用されます。最適なコード設計のためには、特性をインターフェイス要件を満たすものに限定する必要があります。