trait を使って
楽したい話
Infiniteloop Masaru - capy - Yamagishi 2014/08/01
こういう経験
ありませんか?
シングルトンするマネージャクラス作ったよ!
↓
じゃあその調子でもう一個同じようなの作って
↓
オーケー!楽勝だぜ!
/lib/managers/SomeManager.php
<?php
class SomeManager {
static private $_instance = null;
private function __construct() {}
public function getSomething() {
// ...
}
static public function getInstance() {
if (!isset(self::$_instance)) {
self::$_instance = new self();
}
return self::$_instance;
}
}
<?php
class OtherManager {
static private $_instance = null;
private function __construct() {}
public function getAnything() {
// ...
}
static public function getInstance() {
if (!isset(self::$_instance)) {
self::$_instance = new self();
}
return self::$_instance;
}
}
/lib/managers/SomeManager.php /lib/managers/OtherManager.php
コピペ
コピペ
コピペ
コピペ
コピペ
コピペ
コピペ
コピペ
<?php
class SomeManager {
static private $_instance = null;
private function __construct() {}
public function getSomething() {
// ...
}
static public function getInstance() {
if (!isset(self::$_instance)) {
self::$_instance = new self();
}
return self::$_instance;
}
}
<?php
class OtherManager {
static private $_instance = null;
private function __construct() {}
public function getAnything() {
// ...
}
static public function getInstance() {
if (!isset(self::$_instance)) {
self::$_instance = new self();
}
return self::$_instance;
}
}
/lib/managers/SomeManager.php /lib/managers/OtherManager.php
コピペ
コピペ
コピペ
コピペ
コピペ
コピペ
コピペ
コピペ
<?php
class SomeManager {
static private $_instance = null;
private function __construct() {}
public function getSomething() {
// ...
}
static public function getInstance() {
if (!isset(self::$_instance)) {
self::$_instance = new self();
}
return self::$_instance;
}
}
これを
trait
化すると
<?php
class SomeManager {
use Singletonnable;
public function getSomething() {
// ...
}
}
<?php
class OtherManager {
use Singletonnable;
public function getAnything() {
// ...
}
}
実際シンプル!!
/lib/managers/SomeManager.php /lib/managers/OtherManager.php
What is trait?
“トレイトは、単一継承の制約を減らすために作られたも
ので、 いくつかのメソッド群を異なるクラス階層にある
独立したクラスで再利用できるようにします。”
http://php.net/manual/ja/language.oop5.traits.php
ちなみに trait : 発音記号/tréɪt|tréɪ, tréɪt/【名詞】【可算名詞】(人・ものの)特性,特色,特徴.
つまり
コピペ代行
fin.
fin.
trait を調べると良く出てくる
サンプル
<?php
trait Singletonnable {
static private $_instance = null;
private function __construct() {}
static public function getInstance() {
if (!isset(self::$_instance)) {
self::$_instance = new self();
}
return self::$_instance;
}
}
Singleton trait
<?php
class SomeManager {
use Singletonnable;
public function getSomething() {
// ...
}
}
<?php
$mngr = SomeManager::getInstance();
$mngr->getSomething();
/lib/traits/Singletonnable.php /lib/managers/SomeManager.php
/main.php
適用
利用
usage
大体クラスと同じ
○ インターフェイスと異なり実装が出来る
○ 継承と異なり単体クラス内で複数のトレイトを利用出来る
○ トレイトを使うトレイトを実装出来る
○ メンバに abstract, static が使える
× トレイト実装に対して implement, extends, const は不可
× トレイトのインスタンス化は不可
optional usage
複数トレイトの利用で method 名が被った場合も、
などして衝突回避可能
重複メソッド(override)の優先順位は 現クラス実装->trait実装->親クラス実装
use TraitA, TraitB {
TraitA::methodA insteadof TraitB;
TraitB::methodC as methodM;
}
Why we use trait?
1. “複数クラスで実装される同一機能”を単位化し、再利用可能にする
<?php
// シングルトンデザインパターンは共通の機能
SomeClass::getInstance();
OtherClass::getInstance();
// 何かの抽選はガチャやドロップ品などのランダム要素で共通の機能
$got_item_id = $gacha->lot($lot_item_list);
$got_item_id = $drop_reward->lot($lot_item_list);
// DB取得や設定はDBのレコード一行を保持するクラスで共通の機能
// マジックメソッドでうまいことやったり
$user_name = $user_table->getUserName();
$unit_attack = $unit_table->getAttack();
Why we use trait?
2. 共通機能を抜き出すことでメンテナンス性の向上
こんなコードがいくつかのクラスにあったとして、
「randではなくmt_randを使うように変更」
となった場合に、traitで抜き出しておけば 一か所の変更 でOK
function lot($item_list) {
$r = rand(0, count($item_list) - 1);
foreach ($item_list as $key => $item) {
if ($key == $r) { return $item; }
}
}
要するに
コピペ代行
Why we use insteadof delegate?
trait で出来ることは、委譲や静的メソッドでも実装出来る
<?php
class Player {
private $attacker;
public function __construct() {
$attacker = new Attacker();
}
public function attackTo($to) {
$attacker->attack($to);
}
}
<?php
class Player {
public function attackTo($to) {
Attacker::attack($this, $to);
}
}
でも
めんどくさいじゃん
コピペ代行
に任せればいいじゃん
When we use trait?
共通に使える単機能をパーツ化して、クラスにガチャコンするイメージ
a. デザインパターンの実装(singleton, composite, …)
b. 様々な場所で使われる処理(DBアクセス、バリデート、特定データ保持…)
c. 上記機能を提供するようなフレームワーク・ライブラリ作成
(CakePHP3は trait が活用されているそうです)
Implements Design Pattern
パターン化された処理の実装
<?php
trait Compositable {
private $_childs = [];
public function addChild($child) {
$this->_childs[] = $child;
}
public function removeChild($child) {
foreach ($i = 0; $i < count($this->_childs); $i++) {
if ($this->_childs[$i] === $child) {
$this->_childs = array_splice($this->_childs, $i, 1);
break;
}
}
}
}
<?php
trait Observable {
private $_listeners = [];
public function addListener($listener) {
$this->_listeners[] = $listener;
}
public function notify() {
foreach ($this->_listeners as $listener) {
$listener->notified();
}
}
}
Implements Common Behavior
様々な場所で使われるライブラリ的な振る舞いの実装
<?php
trait Queriable {
private $_db_accessor;
private function query($select, $from, $where, $option = '') {
if (!isset($this->_db_accessor)) {
$this->_db_accessor = DatabaseAccessor::getInstance();
}
// クエリを投げる処理...
}
}
<?php
trait Loggable {
private function logError($str, $throw_exception = false) {
// ログを出力したり、例外を排出したり
}
}
Summary of trait
ソースコードをコピペしようとした時に
「これって trait になりませんか?(CV:八嶋智人)」
と考えてみる
※ 但し PHP 5.4 以降に限る
P.S. Solarized colorscheme
http://ethanschoonover.com/solarized
P.S. Solarized colorscheme
P.S. Solarized colorscheme
色々なエディタ・ターミナル用の設定が配布中
• vim
• emacs
• IntelliJ IDEA
• NetBeans
• Visual Studio
• Xcode
• iTeam2
• OS X Terminal.app
• …
fin.
※
When we shouldn’t use trait?
trait はオブジェクト指向的な「関係」を構築しない
あくまで「水平方向」での拡張
trait は
abstract class / interface
を置き換えうる?
trait replaces ‘abstract class’
Template Method デザインパターンは trait で十分
<?php
trait GachaPlayer {
private $lot_items = array();
private $elected = null;
public function lot() {
$this->setLotItems();
$this->lot();
return $this->elected;
abstract private function setLotItems();
private function lot() { /* 抽選する処理 */ }
}
class BattleGachaPlayer {
use GachaPlayer;
private function setLotItems() { /* バトル用の抽選データ設定 */ }
}
class AdventureGachaPlayer {
use GachaPlayer;
private function setLotItems() { /* 冒険用の抽選データ設定 */ }
}
trait replaces ‘abstract class’?
但し、「機能を再利用するための継承」ではなく、「オブジェクトの派生と
しての継承(本質的)」の場合は使わない方が良い
(オブジェクト指向の考え方の問題)
<?php
abstract class CharacterJob {
private $job_name;
public function acquireSkill($skill_type) { /* ... */ }
public function addExperience($amount) { /* ... */ }
// ...
}
class WarriorJob extends CharacterJob {
const SKILL_TYPE_SLASH = 1;
// ...
}
trait > extends
PHPの継承は「多重継承」が出来ない
<?php
class Singletonnable { /* ... */ }
class WorldMapBase { /* ... */ }
class DQ1WorldMap extends Singletonnable, WorldMapBase {
// ...
}
trait > implements
PHPのインターフェイスは「実装」が出来ない
<?php
interface Singletonnable {
static public function getInstance();
}
class SomeManager implements Singletonnable {
static public function getInstance() {
// 結局実装しないといけない
}
}
class OtherManager implements Singletonnable {
static public function getInstance() {
// 結局実装しないといけない
}
}
trait > static
クラス間の依存性が高まり保守性に欠ける
<?php
final class SomeManager {
static public getSomeDataList($data_type) {
// ...
}
}
class SomeProcessor {
public function process() {
// 引数が変わる?スコープ変わってアクセス出来なくなる?
$data = SomeManager::getSomeDataList(DATA_TYPE_SOMETHING);
}
}
逆に
extends > trait
定数を持てない
インターフェイスを implement 出来ない
タイプヒントに使えない
トレイト自身をインスタンス化出来ない
<?php
trait SomeFunc implements SomeInterface {
const SOME_DATA = 'somesome';
public function someInterfaceMethod() { }
}
function getSomeFunc(SomeFunc some) {
some->someInterfaceMethod();
}
SomeFunc::SOME_DATA;
implements > trait
絶対にそこで実装しなければならないメソッドを定義出来ない
※ trait は “実装付きインターフェイス” とも呼べる
http://stackoverflow.com/questions/9205083/php-traits-vs-interfaces
<?php
interface SomeInterface {
public function SomeInterfaceMethod();
}
trait SomeTrait {
public function SomeTraitMethod() { /* ... */ }
}
class SomeClass implements SomeInterface {
use SomeTrait;
public function SomeInterfaceMethod() { /* must implement */ }
public function SomeTraitMethod() { /* don’t have to implement */ }
}
static > trait
同一 trait を使っても、変数を共有出来るわけではない

traitを使って楽したい話

  • 1.
  • 2.
  • 3.
    シングルトンするマネージャクラス作ったよ! ↓ じゃあその調子でもう一個同じようなの作って ↓ オーケー!楽勝だぜ! /lib/managers/SomeManager.php <?php class SomeManager { staticprivate $_instance = null; private function __construct() {} public function getSomething() { // ... } static public function getInstance() { if (!isset(self::$_instance)) { self::$_instance = new self(); } return self::$_instance; } }
  • 4.
    <?php class OtherManager { staticprivate $_instance = null; private function __construct() {} public function getAnything() { // ... } static public function getInstance() { if (!isset(self::$_instance)) { self::$_instance = new self(); } return self::$_instance; } } /lib/managers/SomeManager.php /lib/managers/OtherManager.php コピペ コピペ コピペ コピペ コピペ コピペ コピペ コピペ <?php class SomeManager { static private $_instance = null; private function __construct() {} public function getSomething() { // ... } static public function getInstance() { if (!isset(self::$_instance)) { self::$_instance = new self(); } return self::$_instance; } }
  • 5.
    <?php class OtherManager { staticprivate $_instance = null; private function __construct() {} public function getAnything() { // ... } static public function getInstance() { if (!isset(self::$_instance)) { self::$_instance = new self(); } return self::$_instance; } } /lib/managers/SomeManager.php /lib/managers/OtherManager.php コピペ コピペ コピペ コピペ コピペ コピペ コピペ コピペ <?php class SomeManager { static private $_instance = null; private function __construct() {} public function getSomething() { // ... } static public function getInstance() { if (!isset(self::$_instance)) { self::$_instance = new self(); } return self::$_instance; } }
  • 6.
  • 7.
    <?php class SomeManager { useSingletonnable; public function getSomething() { // ... } } <?php class OtherManager { use Singletonnable; public function getAnything() { // ... } } 実際シンプル!! /lib/managers/SomeManager.php /lib/managers/OtherManager.php
  • 8.
    What is trait? “トレイトは、単一継承の制約を減らすために作られたも ので、いくつかのメソッド群を異なるクラス階層にある 独立したクラスで再利用できるようにします。” http://php.net/manual/ja/language.oop5.traits.php ちなみに trait : 発音記号/tréɪt|tréɪ, tréɪt/【名詞】【可算名詞】(人・ものの)特性,特色,特徴.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
    <?php trait Singletonnable { staticprivate $_instance = null; private function __construct() {} static public function getInstance() { if (!isset(self::$_instance)) { self::$_instance = new self(); } return self::$_instance; } } Singleton trait <?php class SomeManager { use Singletonnable; public function getSomething() { // ... } } <?php $mngr = SomeManager::getInstance(); $mngr->getSomething(); /lib/traits/Singletonnable.php /lib/managers/SomeManager.php /main.php 適用 利用
  • 15.
    usage 大体クラスと同じ ○ インターフェイスと異なり実装が出来る ○ 継承と異なり単体クラス内で複数のトレイトを利用出来る ○トレイトを使うトレイトを実装出来る ○ メンバに abstract, static が使える × トレイト実装に対して implement, extends, const は不可 × トレイトのインスタンス化は不可
  • 16.
    optional usage 複数トレイトの利用で method名が被った場合も、 などして衝突回避可能 重複メソッド(override)の優先順位は 現クラス実装->trait実装->親クラス実装 use TraitA, TraitB { TraitA::methodA insteadof TraitB; TraitB::methodC as methodM; }
  • 17.
    Why we usetrait? 1. “複数クラスで実装される同一機能”を単位化し、再利用可能にする <?php // シングルトンデザインパターンは共通の機能 SomeClass::getInstance(); OtherClass::getInstance(); // 何かの抽選はガチャやドロップ品などのランダム要素で共通の機能 $got_item_id = $gacha->lot($lot_item_list); $got_item_id = $drop_reward->lot($lot_item_list); // DB取得や設定はDBのレコード一行を保持するクラスで共通の機能 // マジックメソッドでうまいことやったり $user_name = $user_table->getUserName(); $unit_attack = $unit_table->getAttack();
  • 18.
    Why we usetrait? 2. 共通機能を抜き出すことでメンテナンス性の向上 こんなコードがいくつかのクラスにあったとして、 「randではなくmt_randを使うように変更」 となった場合に、traitで抜き出しておけば 一か所の変更 でOK function lot($item_list) { $r = rand(0, count($item_list) - 1); foreach ($item_list as $key => $item) { if ($key == $r) { return $item; } } }
  • 19.
  • 20.
  • 21.
    Why we useinsteadof delegate? trait で出来ることは、委譲や静的メソッドでも実装出来る <?php class Player { private $attacker; public function __construct() { $attacker = new Attacker(); } public function attackTo($to) { $attacker->attack($to); } } <?php class Player { public function attackTo($to) { Attacker::attack($this, $to); } }
  • 22.
  • 23.
  • 24.
  • 25.
    When we usetrait? 共通に使える単機能をパーツ化して、クラスにガチャコンするイメージ a. デザインパターンの実装(singleton, composite, …) b. 様々な場所で使われる処理(DBアクセス、バリデート、特定データ保持…) c. 上記機能を提供するようなフレームワーク・ライブラリ作成 (CakePHP3は trait が活用されているそうです)
  • 26.
    Implements Design Pattern パターン化された処理の実装 <?php traitCompositable { private $_childs = []; public function addChild($child) { $this->_childs[] = $child; } public function removeChild($child) { foreach ($i = 0; $i < count($this->_childs); $i++) { if ($this->_childs[$i] === $child) { $this->_childs = array_splice($this->_childs, $i, 1); break; } } } } <?php trait Observable { private $_listeners = []; public function addListener($listener) { $this->_listeners[] = $listener; } public function notify() { foreach ($this->_listeners as $listener) { $listener->notified(); } } }
  • 27.
    Implements Common Behavior 様々な場所で使われるライブラリ的な振る舞いの実装 <?php traitQueriable { private $_db_accessor; private function query($select, $from, $where, $option = '') { if (!isset($this->_db_accessor)) { $this->_db_accessor = DatabaseAccessor::getInstance(); } // クエリを投げる処理... } } <?php trait Loggable { private function logError($str, $throw_exception = false) { // ログを出力したり、例外を排出したり } }
  • 28.
    Summary of trait ソースコードをコピペしようとした時に 「これってtrait になりませんか?(CV:八嶋智人)」 と考えてみる ※ 但し PHP 5.4 以降に限る
  • 29.
  • 30.
  • 31.
    P.S. Solarized colorscheme 色々なエディタ・ターミナル用の設定が配布中 •vim • emacs • IntelliJ IDEA • NetBeans • Visual Studio • Xcode • iTeam2 • OS X Terminal.app • …
  • 32.
  • 33.
  • 34.
    When we shouldn’tuse trait? trait はオブジェクト指向的な「関係」を構築しない あくまで「水平方向」での拡張
  • 35.
    trait は abstract class/ interface を置き換えうる?
  • 36.
    trait replaces ‘abstractclass’ Template Method デザインパターンは trait で十分 <?php trait GachaPlayer { private $lot_items = array(); private $elected = null; public function lot() { $this->setLotItems(); $this->lot(); return $this->elected; abstract private function setLotItems(); private function lot() { /* 抽選する処理 */ } } class BattleGachaPlayer { use GachaPlayer; private function setLotItems() { /* バトル用の抽選データ設定 */ } } class AdventureGachaPlayer { use GachaPlayer; private function setLotItems() { /* 冒険用の抽選データ設定 */ } }
  • 37.
    trait replaces ‘abstractclass’? 但し、「機能を再利用するための継承」ではなく、「オブジェクトの派生と しての継承(本質的)」の場合は使わない方が良い (オブジェクト指向の考え方の問題) <?php abstract class CharacterJob { private $job_name; public function acquireSkill($skill_type) { /* ... */ } public function addExperience($amount) { /* ... */ } // ... } class WarriorJob extends CharacterJob { const SKILL_TYPE_SLASH = 1; // ... }
  • 38.
    trait > extends PHPの継承は「多重継承」が出来ない <?php classSingletonnable { /* ... */ } class WorldMapBase { /* ... */ } class DQ1WorldMap extends Singletonnable, WorldMapBase { // ... }
  • 39.
    trait > implements PHPのインターフェイスは「実装」が出来ない <?php interfaceSingletonnable { static public function getInstance(); } class SomeManager implements Singletonnable { static public function getInstance() { // 結局実装しないといけない } } class OtherManager implements Singletonnable { static public function getInstance() { // 結局実装しないといけない } }
  • 40.
    trait > static クラス間の依存性が高まり保守性に欠ける <?php finalclass SomeManager { static public getSomeDataList($data_type) { // ... } } class SomeProcessor { public function process() { // 引数が変わる?スコープ変わってアクセス出来なくなる? $data = SomeManager::getSomeDataList(DATA_TYPE_SOMETHING); } }
  • 41.
  • 42.
    extends > trait 定数を持てない インターフェイスをimplement 出来ない タイプヒントに使えない トレイト自身をインスタンス化出来ない <?php trait SomeFunc implements SomeInterface { const SOME_DATA = 'somesome'; public function someInterfaceMethod() { } } function getSomeFunc(SomeFunc some) { some->someInterfaceMethod(); } SomeFunc::SOME_DATA;
  • 43.
    implements > trait 絶対にそこで実装しなければならないメソッドを定義出来ない ※trait は “実装付きインターフェイス” とも呼べる http://stackoverflow.com/questions/9205083/php-traits-vs-interfaces <?php interface SomeInterface { public function SomeInterfaceMethod(); } trait SomeTrait { public function SomeTraitMethod() { /* ... */ } } class SomeClass implements SomeInterface { use SomeTrait; public function SomeInterfaceMethod() { /* must implement */ } public function SomeTraitMethod() { /* don’t have to implement */ } }
  • 44.
    static > trait 同一trait を使っても、変数を共有出来るわけではない