はじめに
脱グローバル変数、脱グローバル関数を目指してクラスを設計する際に出会ったデザインパターンの一つ「シングルトン」とPHP5.4以降で使用できる「トレイト」を組み合わせたクラス設計について書いてみます。
この組み合わせでクラス作成をして少し幸せになれたように思います。
トレイトとは
トレイトは、PHP のような単一継承言語でコードを再利用するための仕組みのひとつです。
トレイトは、単一継承の制約を減らすために作られたもので、 いくつかのメソッド群を異なるクラス階層にある独立したクラスで再利用できるようにします。
PHP:トレイト
要点としては、PHPの多重継承が出来ないという問題を解決するために搭載された機能です。
一定の振る舞いをモジュール化し、クラスに実装するという類似の機能では、RubyのMix-inやScalaのtraitがありますが、仕様は微妙に異なります。
⇒水平方向の拡張により、継承関係と関係なくモジュール化したメソッドやプロパティを追加できる機能がトレイトとなります。
シングルトンとは
ウェブアプリケーションを設計するときには、概念的そして構造的に、 特定のクラスのたったひとつのインスタンスにだけアクセスさせるようにしたいということがよくある。 そんなときに使えるのがシングルトンパターンだ。
PHP The Right Way:デザインパターン
今回は紹介しませんが、シングルトンを使用する場合、依存性の注入を使うべきです。
シングルトンとトレイトから得られること
- コピペの量がかなり減る(DRYをまもれる)
- クラスの機能の拡張がかなり楽になる
- インスタンス作成のコストをかなり減らせる(場合による)
- インスタンスが複数存在する懸念をかなり減らせる(そもそも作られない)
コード例
- ロボット(Robot):「話す」、「移動する」などの基本動作が可能
- 探索ロボット(SearchRobot):基本動作に加えて「ものを探索する」が可能
- 飛行ロボット(FlightRobot):基本動作に加えて「飛ぶ」が可能
上の実装を普通に書いてみると、、
<?php
class Robot
{
function talk() {
# code..
}
function move() {
# code..
}
}
class SearchRobot extends Robot
{
function search() {
# code..
}
}
class FlightRobot extends Robot
{
function fly() {
# code..
}
}
追加要件で「飛行探索ロボット(SearchFlightRobot)を追加したい」ときたら
飛行探索ロボットなので、FlyRobotとSearchRobotの2つを継承したいが多重継承の問題が...
素直にRobotを継承してクラスを作っても良いがDRYを守りたい...
シングルトンとトレイトを使うと
<?php
trait Searchable
{
function search() {
# code..
}
}
trait Flyable
{
function fly() {
# code..
}
}
class Robot
{
public static function getInstance()
{
static $instance = null;
if (null === $instance) {
$instance = new static();
}
return $instance;
}
protected function __construct() { }
private function __clone() { }
function talk() {
# code..
}
function move() {
# code..
}
}
class SearchRobot
{
use Searchable;
}
class FlightRobot
{
use Flyable;
}
class SearchFlightRobot
{
use Searchable, Flyable;
}
解説
- 静的メソッド*getInstance()*による遅延静的束縛を使用して新しくインスタンスを作る、またはインスタンスを呼び出す。
- トレイトにより、飛行機能、探索機能をモジュール化できているので「低空飛行」を飛行機能をもつすべてのクラスに適応する場合など、機能単位での変更に対応し易い。
まとめ
シングルトンとトレイトを使って実際にクラス作成をすると、水平方向の拡張の容易さと扱いやすさ、インスタンス管理が楽になったイメージです。
シングルトンを使用する場合、依存性の注入を使わないと行けないなどテストがし易いよう気を配らないといけないのが少し面倒になるのでこれから検討したいです。
また、例えば、Singletonを避けるより
- TDD で開発することで設計上の問題点に気づきやすくなる
- Singleton はグローバル変数である
- Singleton の使用はできる限り避けるべきである
などとシングルトンパターンを避けるべきという意見もあるので、これらも織り込んだクラス設計を見つけていくのが今後の課題です。