简体   繁体   English

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

[英]Is it possible to extend the functionality of an object or to Import methods into an object in PHP?

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: 假设我以这种方式定义了一个称为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;
 }

}

And I have a subclass called AdminUser with additional functionality: 我有一个名为AdminUser的子类,具有其他功能:

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? 问题 :如果我已经实例化了一个类型为“ OrdinaryUser”的对象并将其即时变为AdminUser对象,该怎么办? 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. 另一个相关的问题 :以后可能定义了许多其他类别的用户,每种类别都有自己独特的行为,但始终是基本行为,在这种情况下创建层次结构没有任何意义,因为大多数情况下,一种类型的对象应该最好不要从另一种类型继承方法,尽管可能希望将一种类型的附加功能动态地“导入”到另一种类型。

This is not immediately possible in PHP right now. 目前无法在PHP中立即实现。 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. 其次 ,如果您使用的是足够现代的PHP版本,则可以使用Serializable接口进行巧妙的操作。 If you implement that interface, __sleep / __wakeup are never called, and neither is the constructor. 如果实现该接口,则永远不会调用__sleep / __wakeup__sleep __wakeup也不会调用。 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. 通过这样做,我将Bar转换为具有所有相同属性的Baz 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. 我不确定如果属性不匹配,PHP将做什么,并且如果您实现Serializable,则您的serialize和unserialize方法将需要处理转换。

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. 第三 ,回到基于类的构造,而不是基于对象的构造:如果您可以等待PHP 5.4(或当前的主干最终成为现实),则可以在属性中放置匿名函数并像调用它们一样他们是方法。 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. 尽管您可以在早期版本的属性中放置匿名函数,但至少从PHP 5.3起,这些函数不能引用$this ,因此作为对象的一部分是完全没有用的。 This limitation is also what prevents you from using the __call magic method to achieve the same thing right now. 此限制也是导致您无法使用__call magic方法立即实现同一功能的原因。

Fourth , and this is a total non-answer, consider Ruby or Perl if you want these kind of class-bending gymnastics. 第四 ,这是一个完全没有答案的方法,如果您想使用此类弯曲类体操,请考虑使用Ruby或Perl。 I think Python can do similar things, but I haven't worked in it and can't be sure. 我认为Python可以做类似的事情,但是我还没有做过,也不能确定。 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. 在面向对象方面,PHP并不是一种灵活的语言,内部人员名单上的人对将OO提升到其他语言的更有趣的标准没有兴趣。


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. PHP 5.4也具有特征,但在类级别,而不在实例级别。 See #4 for my commentary about that. 有关此内容的评论,请参见#4。

I think your scenario might call for a Factory Pattern 我认为您的方案可能需要工厂模式

Perhaps you shouldn't have instantiated a OrdinaryUser in the first place~ 也许您本来不应该实例化OrdinaryUser的〜

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

What you describe is not possible, have you considered a different approach? 您描述的内容是不可能的,您是否考虑过其他方法? What if your User class 'contained' various privilege implementations in an array. 如果您的User类在数组中“包含”了各种特权实现,该怎么办?

Consider it as an 'Is A' and 'Has A' problem; 将其视为“是A”和“具有A”问题; while an AdminUser 'is' an admin, it 'has' privileges, so said priv's should be stored as member variables. 当AdminUser是管理员时,它具有特权,因此priv应当作为成员变量存储。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM