是否可以扩展对象的功能或将方法导入到 PHP 中的对象中?

发布于 2024-10-19 16:30:06 字数 870 浏览 1 评论 0原文

我面临着一个严重的设计问题,这让我发疯。我认为只能通过多重继承什么的来解决。所以这就是我想要做的:

假设我有一个名为 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 对象?是否有一种“扩展对象”的方法,以避免实例化子类并必须使用相同的数据重新填充新对象的字段?

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

I am facing a serious design problem that is driving me crazy. I think it can only be solved with multiple inheritance or something. So here is what I want to do:

Say I have a basic user class called OrdinaryUser defined in this way:

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;
 }

}

And I have a subclass called AdminUser with additional functionality:

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

The problem: What if I have already instantiated an object of type "OrdinaryUser" and want to make it into an AdminUser object on-the-fly? Is there a way of "extending objects" so to avoid instantiating a subclass and having to re-populate the new object's fields with the same data?

Another related problem: I might have many other categories of users defined later, each having their own unique bahaviour, but always a basic one, and it wouldn't make sense to create a hierarchy in this case because most times one type of object should not be inheriting methods from the other type, although it might be desirable to have additional functionality from one type being "imported" into the other dinamically.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

凉城已无爱 2024-10-26 16:30:06

目前在 PHP 中这还不可能立即实现。为了便于实施,这里有一些替代方案。

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

第二,如果您使用的是足够现代的 PHP 版本,则可以使用 可序列化接口是一个巧妙的技巧。如果实现该接口,则永远不会调用 __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

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

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

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

第四,这是一个完全没有答案的问题,如果您想要这种跨类别的体操,请考虑 Ruby 或 Perl。我认为Python可以做类似的事情,但我没有使用过,不能确定。就 OO 而言,PHP 并不是一种灵活的语言,内部人员名单上的人也没有兴趣将 OO 提升到其他语言更有趣的标准。


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

This is not immediately possible in PHP right now. Here are some alternatives, in order of ease of implementation.

First, if you know all of the possible transformations between related classes, you can easily create a method that takes the current object, populates a clean instance of the new class and returns it. This is the idea you mentioned in your original question. It's the most straightforward and safe thing you could do.

Second, if you're using a modern enough version of PHP, you can use the Serializable interface for a neat trick. If you implement that interface, __sleep/__wakeup are never called, and neither is the constructor. This means that you can use those methods for a cheap trick. Here's some silly demo code without the interface to demonstrate:

[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

If you didn't follow, this code replaces the class name in the serialized data. By doing this, I've just transformed a Bar into a Baz, with all of the same properties. This only really works if the properties are identical. I'm not sure what PHP would do if the properties don't match, and if you implement Serializable, your serialize and unserialize methods will need to handle the transformation.

This is also a tremendous hack that may cause future maintainers of your code to want to track you down and hurt you. There's also probably a minor performance penalty. If you end up using it, be sure to benchmark. And hire bodyguards. Or at least make sure the future maintainers won't be murderous psychopaths that can find your address.

Third, going back to class-based construction instead of object-based construction: If you can wait for PHP 5.4 (or whatever the current trunk will end up being), you will be able to place anonymous functions in properties and call them as if they were methods. While you can place anonymous functions in properties in earlier versions, at least as of PHP 5.3, those functions can not reference $this, and are therefore quite useless as part of an object. This limitation is also what prevents you from using the __call magic method to achieve the same thing right now.

Fourth, and this is a total non-answer, consider Ruby or Perl if you want these kind of class-bending gymnastics. I think Python can do similar things, but I haven't worked in it and can't be sure. PHP is not a flexible language when it comes to OO, and the people on the internals list have no interest in bringing OO up to the more interesting standards of other languages.


With regard to your related problem, it sounds like you really want instance-level traits. PHP 5.4 will also have traits, but at the class level, not the instance level. See #4 for my commentary about that.

奈何桥上唱咆哮 2024-10-26 16:30:06

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

<?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

Sounds like the Decorator Pattern may help you

<?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
乖乖兔^ω^ 2024-10-26 16:30:06

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

也许您不应该在第一名~

I think your scenario might call for a Factory Pattern

Perhaps you shouldn't have instantiated a OrdinaryUser in the first place~

雨落星ぅ辰 2024-10-26 16:30:06

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

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

What you describe is not possible, have you considered a different approach? What if your User class 'contained' various privilege implementations in an array.

Consider it as an 'Is A' and 'Has A' problem; while an AdminUser 'is' an admin, it 'has' privileges, so said priv's should be stored as member variables.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文