2

我有一个基本用户类,它负责处理基本用户信息:姓名、年龄、位置等。我想用组功能扩展我的系统,然后用项目和会议扩展我的系统。例如:

class User
{
    public function getName() {
        ...
    }

    public function getAge() {
        ...
    }
}

然后使用组功能对其进行扩展:

class GroupableUser extends User
{
    public function join($groupId) {
        ...
    }

    public function leave($groupId) {

    }

    public function requestGroupInvitation($groupId) {
        ...
    }

    public function acceptGroupInvitation($groupId) {
        ...
    }
}

GroupableUser 这个名字对我来说似乎很奇怪,但是将加入和离开方法添加到 Group 类是没有意义的,因为它的用户加入和离开一个组,而不是相反。

后来我会有像UserThatCanHaveProjectsUserThatCanParticipateInMeetings这样的课程。如何命名这些类?

您通常如何处理这些情况?

4

4 回答 4

7

您似乎无法找到命名类的方法,因为层次结构不是很自然。实际上,一个用户可能参与一个以上的组,并且每个组可能有一个以上的用户。但是一个用户可能没有一个组而存在,而没有一个用户就不可能有一个组。我会说这意味着用户应该是一个组的成员,addUser并且removeUser应该被放置在Group. 为什么?因为用户甚至可能不“知道”他可能加入或离开一个组并且非常高兴,而不可能有一个组不“知道”用户可以加入它。

于 2013-01-22T13:45:59.183 回答
4

参加小组/会议和拥有项目的能力是用户可能能够的事情,但它并不能定义用户什么。这是一个非常明显的迹象,表明使用附加类对这些选项进行建模并不是一个好的设计选择。

静态方法 #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$realUserIGrouppableUser

于 2013-01-22T13:56:31.907 回答
1

考虑到数据库驱动的 Web 应用程序,我可能会使用 5 个类来做到这一点:

1) 用户
2) 组
3) GroupAssignment
4) GroupInvitation
5) GroupInvitationRequest

第 3 类只是将一个用户映射到一个组。第 4 类和第 5 类更为明显。无论如何,此设置与您在数据库中的设置非常匹配。这提供了很大的灵活性,因为用户可以是许多组的成员(或一个,或没有)。

编辑:根据有问题的新信息添加了其他类。

于 2013-01-22T13:47:47.643 回答
0

Groupable将是接口的好名称,接口应该描述类的一个方面,这可能是某些不同类的实例所需要的。接口是类之间的契约。

只是不要在它前面加上“我”,除非你对所有东西都使用匈牙利符号。只需对类名使用名词,对接口使用形容词。

至于班级的名称,我会选择:

class Participant extends User implements Groupable
{
    // methods that will be expected by from instance of this class
    // by any class that requires an object to have Groupable interfrace
}
于 2013-01-22T19:15:45.807 回答