発端
Traitは、メソッドの実装のコピペを回避するのに多用します。でも、マジックメソッド(特に __construct()
)を引き上げると、思いの外「再利用可能な機会が狭まる」ので、やめたほうがいいなぁって感じました。
もっともらしい理由
特に使用頻度が高い(と思う)、__construct()
について考えました。他のものについては、深く考察していないです。
メソッドの使用率(メソッド名の重複率)が高い(と思う)
マジックメソッドなので、「メソッド名が同じ」だから、存在すれば即重複します。複数のTraitを利用する場合に、重複していないことを毎回確認するのはつらいです。
- 「コンストラクタ」マジックメソッドの使用率が高いので、メソッドの重複率が高い。
- メソッドの重複率が高いと、意図しない上書きが発生する。
- 特に、複数のTraitをuseした時に、発生しやすい。
特に統計とかはとってないです。ただの"感覚"。
コンストラクタは改修するのがつらい
- コンストラクタは、クラスのデータ構造に密接に関係するので、改修することが難しい。
- コンストラクタは、使用率が高いので、「Method Pull Down」する場合にコストが高い。
データ構造に密接に関係する
Getter/Sestterの乱用はアンチパターンなので、一旦論外とします。私の意見としては、「コンストラクタは、そのクラスのデータにおける主キーを明確にする」から、「コンストラクタの引数で一切の値を渡さずに、すべてをSetterに頼るのはダメ」という論調です。
で、これを前提にすると、コンストラクタに「主キーを明確にする」責任が発生する。「主キーの明確にする」のは、DDLの範疇になります。つまり、コンストラクタの実装とは、「そのクラスの主キーの"定義"」である、とも言えます。**"定義"である以上、基本的に「変更するな」**と。
Method Pull Downするときに、変更コストが高くつく
同じコードを、そのTraitを利用しているすべてのクラスにMethod Pull Downするのだと、(IDEの機能で自動でやる場合を除き)クラスの数だけコピペを繰り返すことになります。心理的なハードルが高いです。
各マジックメソッドは、何に使われるのか
自分が、各マジックメソッドを何に使ってるのか、一応考えました。結果として、どれも「データ構造とかデータの内容」に依存してて、インターフェースへの依存ではないので、ダメでしょう。
-
__construct()
はそのクラスの主キーを定義する。 -
__destruct()
はインスタンスが破棄される時の"状態"に依存する処理を頻繁に書く。 -
__wakeup()
と__sleep()
は、シリアライズ前後のデータに依存した処理を頻繁に書く。 -
__toString()
は、インスタンスプロパティに依存した処理を頻繁に書く。固定値を返すだけなら、別に良いだろう。 -
__invoke()
は、多分、インスタンスのプロパティに依存した処理を書く。コマンドパターンのInvokerとして使うこともある。(monologのProcessorとか。) -
__set_state()
と__debugInfo()
は、多分、インスタンスのプロパティに依存した処理を書くと思う。
useしてる先のクラスで上書きすればいいんじゃね?
ダメだろ。「後から自由に上書きする」という点では、.htaccessみたいなもんだと思います。
一般的に、サーバの主設定ファイルにアクセスできない場合を除いて、 .htaccess ファイルの使用は極力避けてください。
普通は可能であれば .htaccess ファイルの使用は 避けてください。
この理論で行くと、「使用元のTraitにアクセス出来ない場合」となるわけだが、どういう状態だよ?ということになりますが。「Traitがスパゲッティ化しすぎて変更不可能だから、やむを得ず上書きする」くらいしか理由が思いつかないです。
上記のリンク先にある「.htaccessファイルの使用を避ける」理由を、".htaccess"を"traitやクラス"に置き換えて読んだ場合と、だいたい同じ理由になると思います。
あと、以下の様な点で、この手法はダメだと思います。
- 各Traitが実装している処理の要件を満たす実装を、繰り返す
- use元のTraitに変更が加わった場合に、変更し忘れるかもしれない。
- 共通化した理由とはなんだったのか。
おわり