1

我正面临一个让我发疯的严重设计问题。我认为它只能通过多重继承或其他方式来解决。所以这就是我想要做的:

假设我有一个以这种方式定义的名为 OrdinaryUser 的基本用户类:

class OrdinaryUser
{
 private $id,$name;

 public function __construct($id)
 {
  $this->id = $id;
  $this->name = fictionalDB::getUserNameById($id);
 }

 public function getName()
 {
  return $this->name;
 }

 public function getId()
 {
  return $this->id;
 }

}

我有一个名为 AdminUser 的子类,具有附加功能:

class AdminUser extends OrdinaryUser
{
 public function deleteUser($id)
 {
  echo "Deleting user where id=$id";
 }
}

问题:如果我已经实例化了一个“OrdinaryUser”类型的对象,并且想即时将它变成一个AdminUser对象怎么办?有没有一种“扩展对象”的方法,以避免实例化子类并不得不用相同的数据重新填充新对象的字段?

另一个相关问题:我以后可能定义了许多其他类别的用户,每个类别都有自己独特的行为,但始终是基本行为,在这种情况下创建层次结构是没有意义的,因为大多数时候一种类型的对象应该不是从另一种类型继承方法,尽管可能希望将一种类型的附加功能动态地“导入”到另一种类型中。

4

4 回答 4

3

现在这在 PHP 中是不可能的。为了便于实施,这里有一些替代方案。

首先,如果您知道相关类之间所有可能的转换,您可以轻松地创建一个方法来获取当前对象,填充新类的干净实例并返回它。这是您在原始问题中提到的想法。这是你能做的最直接、最安全的事情。

其次,如果您使用的是足够现代的 PHP 版本,则可以使用Serializable 接口来获得巧妙的技巧。如果您实现该接口,则永远不会调用__sleep/ __wakeup,构造函数也不会。这意味着您可以使用这些方法来获得便宜的技巧。下面是一些愚蠢的演示代码,没有要演示的界面:

[mcg@mcg-workstation ~]$ php -a
Interactive shell

php > class Foo { public $a; public $b; }
php > class Bar extends Foo { public $c; }
php > class Baz extends Foo { public $c; }
php > $one = new Bar();
php > $one_s = serialize($one);
php > echo $one_s;
O:3:"Bar":3:{s:1:"c";N;s:1:"a";N;s:1:"b";N;}
php > $one_s = explode(':', $one_s, 4);
php > print_r($one_s);
Array
(
    [0] => O
    [1] => 3
    [2] => "Bar"
    [3] => 3:{s:1:"c";N;s:1:"a";N;s:1:"b";N;}
)
php > $two_s = $one_s;
php > $two_s[1] = strlen('Baz'); $two_s[2] = '"Baz"';
php > $two_s = join(':', $two_s);
php > echo $two_s;
O:3:"Baz":3:{s:1:"c";N;s:1:"a";N;s:1:"b";N;}
php > $two = unserialize($two_s);
php > echo get_class($two);
Baz

如果您没有遵循,此代码将替换序列化数据中的类名。通过这样做,我刚刚将 aBar转换为 a Baz,具有所有相同的属性。这只有在属性相同时才真正有效。如果属性不匹配,我不确定 PHP 会做什么,并且如果您实现 Serializable,您的序列化和反序列化方法将需要处理转换。

这也是一个巨大的黑客攻击,可能会导致您的代码的未来维护者想要追踪您并伤害您。也可能会有轻微的性能损失。如果您最终使用它,请务必进行基准测试。并聘请保镖。或者至少确保未来的维护者不会是可以找到您地址的凶残精神病患者。

第三,回到基于类的构造而不是基于对象的构造:如果您可以等待 PHP 5.4(或当前主干将成为的任何东西),您将能够将匿名函数放在属性中并像调用它们一样调用它们他们是方法。虽然您可以在早期版本中将匿名函数放在属性中,但至少从 PHP 5.3 开始,这些函数不能引用$this,因此作为对象的一部分毫无用处。这个限制也是阻止您现在使用魔术__call方法来实现相同目标的原因。

第四,这是完全没有答案的,如果你想要这种类型的弯曲体操,可以考虑使用 Ruby 或 Perl。我认为 Python 可以做类似的事情,但我没有在其中工作过,也不能确定。PHP 在 OO 方面不是一门灵活的语言,内部列表中的人对将 OO 提升到其他语言更有趣的标准没有兴趣。


关于您的相关问题,听起来您确实想要实例级特征。PHP 5.4 也将具有特征,但在类级别,而不是实例级别。有关我的评论,请参见#4。

于 2011-03-01T06:09:58.513 回答
1

我认为您的场景可能需要工厂模式

或许你一开始就不应该实例化一个 OrdinaryUser~

于 2011-03-01T04:37:47.440 回答
1

听起来装饰器模式可以帮助你

<?php
/*
   an interface to ensure we only decorate Users
   and possibly some of the most common methods that all users have
   so that we don't always suffer the overhead of the magic __call method
*/
interface User
{
    public function getId();
    public function getName();
}

class OrdinaryUser implements User
{
    private $id,$name;

    public function __construct($id)
    {
        $this->id = $id;
        $this->name = fictionalDB::getUserNameById($id);
    }

    public function getName()
    {
        return $this->name;
    }

    public function getId()
    {
        return $this->id;
    }

}

/*
   There aren't any abstract methods in this class
   but it is declared abstract because there is no point in instantiating one
*/
abstract class UserDecorator implements User
{
    protected $user;

    public function __construct( User $user )
    {
        $this->user = $user;
    }

    public function getId()
    {
        return $this->user->getId();
    }

    public function getName()
    {
        return $this->user->getName();
    }

    /*
       Any methods that aren't implemented by this type of
       user are dealt with by the decorated user
    */
    public function __call( $method, $arguments )
    {
        return call_user_func_array( array( $this->user, $method ), $arguments );
    }
}

class AdminUser extends UserDecorator
{
    /*
       Add any methods that are particular to this type of user
    */
    public function addedMethod()
    {
        // do AdminUser type stuff
        return "doing added method stuff\n";
    }

}

class FooUser extends UserDecorator
{
    public function foo()
    {
        // do some foo
        return "doing fooness\n";
    }
}

// just for testing
class fictionalDB
{
    public static function getUserNameById( $id )
    {
        $db = array(
            1 => 'Peter',
            2 => 'Paul'
        );
        return $db[$id];
    }
}


$user = new OrdinaryUser( 1 );
echo $user->getName();    // Peter

// make Peter into an AdminUser
$user = new AdminUser( $user );

// and also add some fooness
$user = new FooUser( $user );
echo $user->addedMethod(); // doing added method stuff
echo $user->foo();         // doing fooness
于 2011-03-01T08:44:01.633 回答
0

您所描述的不可能,您是否考虑过不同的方法?如果您的 User 类在数组中“包含”各种权限实现怎么办。

将其视为“是 A”和“有 A”的问题;虽然 AdminUser“是”管理员,但它“拥有”特权,所以说 priv 应该存储为成员变量。

于 2011-03-01T04:25:55.817 回答