グループ/会議に参加してプロジェクトを行う機能は、ユーザーが実行できる可能性があるものですが、ユーザーが何であるかを定義するものではありません。これは、追加のクラスを使用してこれらのオプションをモデル化することは、適切な設計上の選択ではないことを示す非常に明確な兆候です。
静的アプローチ#1:インターフェース
静的に型付けされた言語では、単純な実装は次のようになります。
interface IGrouppableUser {
public function join(...);
}
class GroupableUser implements IGrouppableUser {
public function join(...) { /* implementation */ }
}
また、グループ化可能なユーザーのコンシューマーはこれを受け入れIGrouppableUser
、必要な数のクラスを作成できるようにします。これはPHPでも実行できますが、前述のように、言語に関係なく、おそらく適切な設計ではありません。
脚注として、PHP 5.4以降の言語にトレイトを追加すると、上記のシナリオをもう少し便利に実装できることを追加する必要があります(クラスはインターフェイスを実装する代わりにトレイトを使用できます。つまり、join
コードベース全体の実装をコピーして貼り付けます)。しかし、概念的にはまったく同じアプローチです。
このアプローチの主な欠点は、拡張性がないことです。2つまたは3つのタイプのユーザーのみが必要な場合は、問題ない可能性があります。
静的アプローチ#2:「サポートされていない」例外
ほとんどのユーザーがグループ化可能でプロジェクトを持つことができる場合、クラスの地獄のような階層を作成することはあまり意味がありません。必要なメンバーをクラスに追加するだけUser
で、ファットなインターフェースになります。
class GroupableUser implements IGrouppableUser {
private $isGrouppable = true; // default, can be changed at runtime
public function join(...) {
if (!$this->isGrouppable) throw new Exception("User is not grouppable!");
// real implementation
}
}
このアプローチの主な欠点は、クラスUser
が無条件に広範囲の操作をサポートしているように見えることですが、実際にはサポートしておらず、その結果、コーディングが面倒でエラーが発生しやすくなります(多くのtry
/ catch
)。大多数のユーザーが大多数の操作をサポートしていれば問題ないかもしれません。
動的アプローチ#1:行動
User
インスタンスがこれらの操作に参加できるように条件付きで許可する方がはるかに優れています。これは、オブジェクトに「動作」を動的にアタッチできる必要があることを意味しますUser
。これは、幸い、動的に型指定された言語で非常に簡単に実行できます。
確立されたオープンソースプロジェクトから「動作」の実装を調べることをお勧めしますが、ここに簡単で汚い例があります。
動作の基本クラスとサンプルの実装
abstract class Behavior {
public function provides($name) {
return method_exists($this, $name);
}
public function invoke($target, $name, $arguments) {
array_unshift($arguments, $target);
return call_user_func_array(array($this, $name), $arguments);
}
}
class GrouppableBehavior extends Behavior {
public function join(User $user, $groupName) {
echo "The user has joined group $groupName.";
}
}
構成可能な(動作を使用できる)基本クラスとユーザー実装
class Composable {
private $behaviors = array();
public function __call($name, $arguments) {
foreach ($this->behaviors as $behavior) {
if ($behavior->provides($name)) {
return $behavior->invoke($this, $name, $arguments);
}
}
throw new Exception("No method $name and no behavior that implements it");
}
public function attach($behavior) {
$this->behaviors[] = $behavior;
}
}
class User extends Composable {}
テストドライバー
$user1 = new User;
$user2 = new User;
$user1->attach(new GrouppableBehavior);
$user1->join('Test Group'); // works
$user2->join('Test Group'); // throws
実際の動作をご覧ください。
このアプローチの主な欠点は、より多くのランタイムリソースを消費し、動作public
がアタッチしているクラスのメンバーにしかアクセスできないことです。場合によっては、動作を機能させるために非公開にする必要がある実装の詳細を公開することを余儀なくされることがあります。
動的アプローチ#2:デコレータ
動作のバリエーションはデコレータパターンです:
interface IUser {}
interface IGrouppableUser extends IUser {
public function join(...);
}
class User implements IUser {}
class UserGroupingDecorator implements IGrouppableUser {
private $realUser;
public function __construct(IUser $realUser) {
$this->realUser = $realUser;
}
public function join(...) { /* implementation */ }
/* now you need to implement all IUser methods
and forward the calls to $this->realUser */
/* if IUser exposes bare properties we have a problem! */
}
このパターンを使用すると、自由にUserGroupingDecorator
ラップするを作成し、またはのIUser
いずれかを受け入れるものにデコレータを渡すことができます。IUser
IGrouppableUser
このアプローチの主な欠点は、の非公開メンバーへのアクセスも提供しないことですUser
。さらに、プロパティが前者でも定義されている場合、からベアプロパティアクセスを「転送」する方法がないため、からベアプロパティを公開することは除外されます。実際に定義されていない限り、実装することはできませIUser
ん。この状況は、プロパティを個別のゲッター/セッターメソッドとして公開することで回避できますが、それはさらに多くのコードを記述することを意味します。UserGroupingDecorator
$realUser
IGrouppableUser