Можно ли расширить функциональность объекта или импортировать методы в объект в PHP? - PullRequest
1 голос
/ 01 марта 2011

Я столкнулся с серьезной проблемой дизайна, которая сводит меня с ума.Я думаю, что это может быть решено только с множественным наследованием или чем-то еще.Итак, вот что я хочу сделать:

Скажем, у меня есть базовый пользовательский класс с именем 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 ]

3 голосов
/ 01 марта 2011

Это не сразу возможно в 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, если свойства не совпадают, и если вы реализуете Serializable, ваши методы сериализации и десериализации должны будут обрабатывать преобразование.

Это также огромный взлом, который может заставить будущих разработчиков вашего кода захотеть отследить вас и причинить вам вред. Также возможно незначительное снижение производительности. Если вы в конечном итоге используете его, обязательно сравните тест. И нанять телохранителей. Или, по крайней мере, убедитесь, что будущие сопровождающие не будут психопатами-убийцами, которые смогут найти ваш адрес.

Третий , возвращаясь к построению на основе классов вместо построения на основе объектов: если вы можете ждать PHP 5.4 (или чего-то еще, чем в конечном итоге будет текущий ствол), вы сможете разместить анонимные функции в свойствах и вызывать их, как если бы они были методами. Хотя в более ранних версиях можно размещать анонимные функции в свойствах, по крайней мере, начиная с PHP 5.3, эти функции не могут ссылаться на $this и поэтому совершенно бесполезны как часть объекта. Это ограничение также препятствует использованию магического метода __call для достижения того же самого результата прямо сейчас.

Четвёртый , и это полный не ответ, рассмотрите Ruby или Perl, если вы хотите такого рода изгибающую гимнастику. Я думаю, что Python может делать подобные вещи, но я не работал в этом и не уверен. PHP не является гибким языком, когда дело доходит до ОО, и люди из внутреннего списка не заинтересованы в том, чтобы привести ОО к более интересным стандартам других языков.


Что касается вашей связанной проблемы, похоже, вы действительно хотите получить черты уровня экземпляра. PHP 5.4 также будет иметь черты, но на уровне класса, а не на уровне экземпляра. См. # 4 для моего комментария об этом.

1 голос
/ 01 марта 2011

Похоже, Шаблон декоратора может помочь вам

<?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
1 голос
/ 01 марта 2011

Я думаю, что ваш сценарий может потребовать Factory Pattern

Возможно, вам не следовало создавать экземпляр OrdinaryUser во-первых ~

0 голосов
/ 01 марта 2011

То, что вы описываете, невозможно, вы рассматривали другой подход? Что если ваш класс User «содержит» различные реализации привилегий в массиве.

Рассматривайте это как проблему «Есть А» и «Есть А»; в то время как AdminUser «является» администратором, он «имеет» привилегии, поэтому указанные привилегии следует хранить как переменные-члены.

...