Upgrade to Pro — share decks privately, control downloads, hide ads and more …

traitは本当に悪者か?

Avatar for akshimo akshimo
June 07, 2026
20

 traitは本当に悪者か?

Avatar for akshimo

akshimo

June 07, 2026

More Decks by akshimo

Transcript

  1. • 五十嵐 進士『WEB+DB PRESS Vol.130 PHPで複雑さに立ち向かう 第8回 トレイトでのコードの再利用とどう向き合うか』.技術評論社 • Jeremy

    Evans『研鑽Rubyプログラミング』.ラムダノート • meihei『改めて学ぶ Trait の使い方』 https://speakerdeck.com/meihei3/phpcon-odawara-2025 今回参考にさせていただいたもの
  2. PHP は、コードを再利用するための「トレイト」という仕組みを実装しています。 トレイトは、PHP のような単一継承言語でコードを再利用するための仕組みのひとつです。 トレイトは、単一継承の制約を減らすために作られたもので、 いくつかのメソッド群を異なるク ラス階層にある独立したクラスで再利用できるようにします。 トレイトとクラスを組み合わせた 構文は複雑さを軽減させてくれ、 多重継承や

    Mixin に関連するありがちな問題を回避する こともできます。 トレイトはクラスと似ていますが、トレイトは単にいくつかの機能をまとめるためだけのもので す。 トレイト自身のインスタンスを作成することはできません。 昔ながらの継承に機能を加え て、振る舞いを水平方向で構成できるようになります。 つまり、継承しなくてもクラスのメン バーに追加できるようになります。 https://www.php.net/manual/ja/language.oop5.traits.php
  3. 単一継承 Class A method() Class C method() Class D method()

    Class B method() 水平方向の再利用
  4. trait Flyable { public function fly() { echo 'パタパタ'; }

    } class Hawk extends Bird { use Flyable; public function searchesForFood() { $this->fly(); … } } traitの例
  5. 暗黙的な依存 trait AddressFormattable { public function fullAddress(): string { //

    traitの利用側にこれらのプロパティがあることに依存している return $this->address1 . $this->address2 . $this->address3; } } class DeliveryAdress { use AddressFormattable; private string $address1; … }
  6. 不透明 class DeliveryAdress { use AddressFormattable; } $deliveryAddress = new

    DeliveryAdress(); // このメソッドはどこから来た? $deliveryAddress->fullAddress();
  7. 密結合 trait AddressFormattable { public function fullAddress(): string { //

    たとえprivateなプロパティだろうと、お互いに”壊す”ことが可能 $this->address1 = $this->address1 . $this->address2 . $this->address3; return $this->address1; } } class DeliveryAdress { use AddressFormattable; private string $address1; … }
  8. 名前衝突が (やや)わかりづらい trait PropertiesTrait { public $same = true; public

    $different1 = false; public bool $different2; public bool $different3; } class PropertiesExample { use PropertiesTrait; public $same = true; public $different1 = true; public string $different2; readonly protected bool $different3; }
  9. 名前衝突が (やや)わかりづらい trait PropertiesTrait { public $same = true; public

    $different1 = false; public bool $different2; public bool $different3; } class PropertiesExample { use PropertiesTrait; public $same = true; public $different1 = true; // Fatal error public string $different2; // Fatal error readonly protected bool $different3; // Fatal error }
  10. Concerns are also a helpful way of extracting a slice

    of model that doesn’t seem part of its essence (what is and isn’t in the essence of a model is a fuzzy line and a longer discussion) without going full-bore Single Responsibility Principle and running the risk of ballooning your object inventory. David Heinemeier Hansson『Put chubby models on a diet with concerns』 https://signalvnoise.com/posts/3372-put-chubby-models-on-a-diet-with-concerns
  11. trait Norifiable { public function notify(string $email): void { //

    通知処理 } } class OrderAction { use Norifiable; } できるなら DI・コンポジションの方が良い
  12. class Norifier { public function notify(string $email): void { //

    通知処理 } } class OrderAction { public function __construct (private Norifier $notifier) {} } できるなら DI・コンポジションの方が良い
  13. interface Norifiable { public function notify(string $email): void; } class

    Norifier implements Norifiable { // 通知処理の実装 } class OrderAction { public function __construct (private Norifiable $notifier) {} } できるなら DI・コンポジションの方が良い
  14. 値オブジェクトも良い class Address { public function __construct ( private string

    $address1, private string $address2, private string $address3 ) {} public function fullAddress(): string { return $this->address1 . $this->address2 . $this->address3; } }
  15. 暗黙的な依存 trait AddressFormattable { public function fullAddress(): void { //

    traitの利用側にこれらの変数があることに依存している return $this->address1 . $this->address2 . $this->address3; } } class DeliveryAdress { use AddressFormattable; … }
  16. trait AddressFormattable { public function fullAddress(): string { return $this->address1()

    . $this->address2() . $this->address3(); } abstract private function address1(): string; abstract private function address2(): string; abstract private function address3(): string; } 抽象メソッドを介する
  17. interface AddressFormatInterface { public function fullAddress(): string; } trait AddressFormatTrait

    { public function fullAddress(): string { return $this->address1() . $this->address2() . $this->address3(); } abstract private function address1(): string; abstract private function address2(): string; abstract private function address3(): string; } interfaceのdefault実装
  18. class Adress implements AddressFormatInterface { use AddressFormatTrait; public function fullAddress():

    string { // デフォルト以外の振る舞いをしたければオーバーライド } } interfaceのdefault実装