参加小组/会议和拥有项目的能力是用户可能能够做的事情,但它并不能定义用户是什么。这是一个非常明显的迹象,表明使用附加类对这些选项进行建模并不是一个好的设计选择。
静态方法 #1:接口
在静态类型语言中,简单的实现看起来像
interface IGrouppableUser {
public function join(...);
}
class GroupableUser implements IGrouppableUser {
public function join(...) { /* implementation */ }
}
并且 grouppable 用户的消费者会接受IGrouppableUser
,允许您根据需要制作尽可能多的类。您也可以在 PHP 中执行此操作,但如前所述,无论使用哪种语言,它都可能不是一个好的设计。
作为脚注,我应该补充一点,从 PHP 5.4 开始,通过向语言添加特征,可以更方便地实现上述场景(类可以使用特征而不是实现接口,这意味着您不需要复制/粘贴join
代码库周围的所有实现)。但从概念上讲,这是完全相同的方法。
这种方法的主要缺点是它不能扩展。如果您只需要两种或三种类型的用户,这可能没问题。
静态方法 #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
包装 anIUser
并将装饰器传递给任何接受 anIUser
或 an 的东西IGrouppableUser
。
这种方法的主要缺点是它也不提供对User
. 此外,它排除了暴露裸属性的可能性,因为如果属性也在前者上定义,则IUser
无法“转发”裸属性访问-除非确实定义了它们,否则您无法实现。可以通过将属性公开为不同的 getter/setter 方法来回避这种情况,但这意味着要编写更多代码。UserGroupingDecorator
$realUser
IGrouppableUser